mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
perf(ui-lvgl): lower clock when idle, fewer redraws, wake on BLE frames
- CPU at 80 MHz with the screen on after 2 s without touch or animation; a touch restores the full clock before its frame is drawn - setText(): periodic refreshes no longer redraw unchanged labels (status bar, Home, lock, Diag, GPS, compass, clock, map, nav, nearby) - map markers kept across the 3 s rebuild when unchanged - app connected: the loop blocks until a BLE frame arrives (serialFrameArrived / hasPendingFrames) instead of polling every 2 ms - PSRAM: boot frame sprite and the HTTP response buffer released Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -226,16 +226,16 @@ void UITask::refreshClock() {
|
||||
if (s_big) {
|
||||
char b[16];
|
||||
fmtRemaining(b, sizeof(b), _core->clock.timerRemainingMs());
|
||||
lv_label_set_text(s_big, b);
|
||||
setText(s_big, b);
|
||||
}
|
||||
if (s_go_lbl) lv_label_set_text(s_go_lbl, running ? LV_SYMBOL_STOP " Stop" : LV_SYMBOL_PLAY " Start");
|
||||
if (s_go_lbl) setText(s_go_lbl, running ? LV_SYMBOL_STOP " Stop" : LV_SYMBOL_PLAY " Start");
|
||||
} else if (s_tab == TAB_STOPWATCH) {
|
||||
if (s_big) {
|
||||
char b[16];
|
||||
fmtStopwatch(b, sizeof(b), swElapsed());
|
||||
lv_label_set_text(s_big, b);
|
||||
setText(s_big, b);
|
||||
}
|
||||
if (s_go_lbl) lv_label_set_text(s_go_lbl, s_sw_running ? LV_SYMBOL_PAUSE " Stop" : LV_SYMBOL_PLAY " Start");
|
||||
if (s_go_lbl) setText(s_go_lbl, s_sw_running ? LV_SYMBOL_PAUSE " Stop" : LV_SYMBOL_PLAY " Start");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,14 +127,14 @@ void UITask::refreshCompass() {
|
||||
lv_obj_invalidate(s_dial);
|
||||
}
|
||||
if (have) {
|
||||
lv_label_set_text_fmt(s_deg, "%03d", cog);
|
||||
lv_label_set_text(s_degsign, "\xC2\xB0");
|
||||
lv_label_set_text(s_card, geo::bearingCardinal(cog));
|
||||
lv_label_set_text(s_hint, "Course over ground");
|
||||
setTextFmt(s_deg, "%03d", cog);
|
||||
setText(s_degsign, "\xC2\xB0");
|
||||
setText(s_card, geo::bearingCardinal(cog));
|
||||
setText(s_hint, "Course over ground");
|
||||
} else {
|
||||
lv_label_set_text(s_deg, "");
|
||||
lv_label_set_text(s_degsign, "");
|
||||
lv_label_set_text(s_card, "--");
|
||||
lv_label_set_text(s_hint, hint);
|
||||
setText(s_deg, "");
|
||||
setText(s_degsign, "");
|
||||
setText(s_card, "--");
|
||||
setText(s_hint, hint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,21 +288,21 @@ void UITask::refreshLock() {
|
||||
clockFaceSet(s_lock_clock, &ti, _prefs);
|
||||
char date[48];
|
||||
fmtDate(date, sizeof(date), ti);
|
||||
if (s_lock_date) lv_label_set_text(s_lock_date, date);
|
||||
if (s_lock_date) setText(s_lock_date, date);
|
||||
}
|
||||
int unread = unreadTotal();
|
||||
if (!s_lock_date) { // PIN card: the count next to the clock, the keypad's line
|
||||
if (unread > 0) lv_label_set_text_fmt(s_lock_unread, LV_SYMBOL_ENVELOPE " %d", unread);
|
||||
else lv_label_set_text(s_lock_unread, "");
|
||||
if (unread > 0) setTextFmt(s_lock_unread, LV_SYMBOL_ENVELOPE " %d", unread);
|
||||
else setText(s_lock_unread, "");
|
||||
if (!s_pin_msg) return;
|
||||
bool was = _pin_tries.block_until != 0;
|
||||
if (_pin_tries.blocked()) lv_label_set_text_fmt(s_pin_msg, "Too many tries - wait %lu s", (unsigned long)_pin_tries.secondsLeft());
|
||||
else if (was) lv_label_set_text(s_pin_msg, "Enter PIN");
|
||||
else if (!lv_label_get_text(s_pin_msg)[0]) lv_label_set_text(s_pin_msg, "Enter PIN");
|
||||
if (_pin_tries.blocked()) setTextFmt(s_pin_msg, "Too many tries - wait %lu s", (unsigned long)_pin_tries.secondsLeft());
|
||||
else if (was) setText(s_pin_msg, "Enter PIN");
|
||||
else if (!lv_label_get_text(s_pin_msg)[0]) setText(s_pin_msg, "Enter PIN");
|
||||
return;
|
||||
}
|
||||
if (unread > 0) lv_label_set_text_fmt(s_lock_unread, LV_SYMBOL_ENVELOPE " %d new message%s", unread, unread == 1 ? "" : "s");
|
||||
else lv_label_set_text(s_lock_unread, "");
|
||||
if (unread > 0) setTextFmt(s_lock_unread, LV_SYMBOL_ENVELOPE " %d new message%s", unread, unread == 1 ? "" : "s");
|
||||
else setText(s_lock_unread, "");
|
||||
}
|
||||
|
||||
// ── Screen PIN ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -91,13 +91,13 @@ static void setLive(int i, const diag::Row& r) {
|
||||
const char* slash = strchr(r.value, '/');
|
||||
char a[12];
|
||||
snprintf(a, sizeof(a), "%.*s", slash ? (int)(slash - r.value) : (int)strlen(r.value), r.value);
|
||||
lv_label_set_text(lv.in, a);
|
||||
lv_label_set_text(lv.out, slash ? slash + 1 : "");
|
||||
setText(lv.in, a);
|
||||
setText(lv.out, slash ? slash + 1 : "");
|
||||
return;
|
||||
}
|
||||
char v[40];
|
||||
showValue(r.label, r.value, v, sizeof(v));
|
||||
lv_label_set_text(lv.val, v);
|
||||
setText(lv.val, v);
|
||||
if (!strcmp(r.label, "Errors")) lv_obj_set_style_text_color(lv.val, lv_color_hex(strcmp(r.value, "OK") ? theme::FAIL : theme::OK), 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -324,13 +324,13 @@ void UITask::refreshGps() {
|
||||
char pos[32] = "", alt[16] = "", speed[16] = "", dop[32] = "";
|
||||
bool fix = false;
|
||||
if (!g) {
|
||||
lv_label_set_text(s_status, "Not available");
|
||||
setText(s_status, "Not available");
|
||||
lv_obj_set_style_text_color(s_status, lv_color_hex(theme::TEXT_MUTED), 0);
|
||||
} else if (off) {
|
||||
lv_label_set_text(s_status, "GPS is off");
|
||||
setText(s_status, "GPS is off");
|
||||
lv_obj_set_style_text_color(s_status, lv_color_hex(theme::TEXT_MUTED), 0);
|
||||
} else if (!data) {
|
||||
lv_label_set_text(s_status, "No data");
|
||||
setText(s_status, "No data");
|
||||
lv_obj_set_style_text_color(s_status, lv_color_hex(theme::FAIL), 0);
|
||||
#if defined(SEEED_WIO_TRACKER_L2)
|
||||
snprintf(used, sizeof(used), "Nothing from the receiver (%lu B)", (unsigned long)gps.rxChars());
|
||||
@@ -342,10 +342,10 @@ void UITask::refreshGps() {
|
||||
for (int i = 0; i < s_n; i++) { if (s_sats[i].snr >= 0) tracked++; if (s_used_flag[i]) used_n++; }
|
||||
fix = g->hasFix();
|
||||
if (fix) {
|
||||
lv_label_set_text(s_status, g->fix_mode == 2 ? "2D fix" : "3D fix");
|
||||
setText(s_status, g->fix_mode == 2 ? "2D fix" : "3D fix");
|
||||
lv_obj_set_style_text_color(s_status, lv_color_hex(theme::OK), 0);
|
||||
} else {
|
||||
lv_label_set_text(s_status, "Searching");
|
||||
setText(s_status, "Searching");
|
||||
lv_obj_set_style_text_color(s_status, lv_color_hex(theme::ACCENT), 0);
|
||||
}
|
||||
snprintf(used, sizeof(used), "%d of %d used", fix ? (used_n ? used_n : g->sats_used) : 0, s_n);
|
||||
@@ -362,7 +362,7 @@ void UITask::refreshGps() {
|
||||
}
|
||||
if (fix && g->hdop > 0) snprintf(dop, sizeof(dop), "H %.1f V %.1f P %.1f", g->hdop, g->vdop, g->pdop);
|
||||
}
|
||||
lv_label_set_text(s_used, used);
|
||||
setText(s_used, used);
|
||||
infoSet(s_inview, inview);
|
||||
infoSet(s_search, search);
|
||||
infoSet(s_ttff, ttff);
|
||||
@@ -378,7 +378,7 @@ void UITask::refreshGps() {
|
||||
int n = 0, u = 0;
|
||||
for (int i = 0; i < s_n; i++) if (s_sats[i].sys == k) { n++; if (s_used_flag[i]) u++; }
|
||||
if (!n) { lv_obj_add_flag(s_sys_pill[k], LV_OBJ_FLAG_HIDDEN); continue; }
|
||||
lv_label_set_text_fmt(s_sys_pill[k], "%c %s %d/%d", GpsSky::sysLetter(k), GpsSky::sysName(k), u, n);
|
||||
setTextFmt(s_sys_pill[k], "%c %s %d/%d", GpsSky::sysLetter(k), GpsSky::sysName(k), u, n);
|
||||
lv_obj_remove_flag(s_sys_pill[k], LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
|
||||
@@ -729,7 +729,7 @@ void UITask::homeMapLayout() {
|
||||
}
|
||||
const char* hint = !mapview::s_available ? "No map on the SD card"
|
||||
: !shown && !s_pending ? "No map tiles for this area" : nullptr;
|
||||
if (hint) { lv_label_set_text(s_hint, hint); lv_obj_remove_flag(s_hint, LV_OBJ_FLAG_HIDDEN); }
|
||||
if (hint) { setText(s_hint, hint); lv_obj_remove_flag(s_hint, LV_OBJ_FLAG_HIDDEN); }
|
||||
else lv_obj_add_flag(s_hint, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
@@ -786,14 +786,14 @@ void UITask::refreshHome() {
|
||||
if (known) {
|
||||
char date[48];
|
||||
fmtDate(date, sizeof(date), ti);
|
||||
lv_label_set_text(_home_date, date);
|
||||
setText(_home_date, date);
|
||||
} else {
|
||||
lv_label_set_text(_home_date, "time not synced");
|
||||
setText(_home_date, "time not synced");
|
||||
}
|
||||
for (int i = 0; i < FIELDS; i++) {
|
||||
if (!s_field_val[i] || !_prefs) continue;
|
||||
char v[24];
|
||||
homeFieldText(_prefs->dashboard_fields[i], v, sizeof(v));
|
||||
lv_label_set_text(s_field_val[i], v);
|
||||
setText(s_field_val[i], v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,19 @@ static lgfx::LGFX_Device* s_gfx = nullptr;
|
||||
static bool s_swallow = false; // ignore the touch that woke the display until it lifts
|
||||
static uint32_t s_flush_us = 0; // UI_PERF_TEST: time spent in flushCb
|
||||
|
||||
// The CPU clock down to 80 MHz (LoRa, BLE and WiFi all run at that) with the
|
||||
// screen off (powerSave()), and with it on while nobody touches it and nothing
|
||||
// moves (the loop decides); a touch brings the full clock back before the
|
||||
// frame it causes is drawn.
|
||||
static uint32_t s_full_mhz = 0;
|
||||
static bool s_cpu_slow = false;
|
||||
static void cpuSlow(bool on) {
|
||||
if (on == s_cpu_slow) return;
|
||||
if (!s_full_mhz) s_full_mhz = getCpuFrequencyMhz();
|
||||
s_cpu_slow = on;
|
||||
setCpuFrequencyMhz(on ? 80 : s_full_mhz);
|
||||
}
|
||||
|
||||
static void flushCb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) {
|
||||
#ifdef UI_PERF_TEST
|
||||
uint32_t t = micros();
|
||||
@@ -128,6 +141,7 @@ static void touchCb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
return;
|
||||
}
|
||||
if (down) {
|
||||
cpuSlow(false); // the frame this touch causes: at the full clock
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
data->point.x = tp.x;
|
||||
data->point.y = tp.y;
|
||||
@@ -139,6 +153,7 @@ static void touchCb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
static bool begin() {
|
||||
s_gfx = display.lgfxDevice();
|
||||
s_gfx->setColorDepth(16);
|
||||
display.releaseFrameBuffer();
|
||||
|
||||
lv_display_t* disp = lv_display_create(s_gfx->width(), s_gfx->height());
|
||||
lv_display_set_flush_cb(disp, flushCb);
|
||||
@@ -169,19 +184,22 @@ static bool touched() {
|
||||
static void swallowTouch() { s_swallow = true; }
|
||||
|
||||
// ── Power ────────────────────────────────────────────────────────────────────
|
||||
// Screen off: the CPU clock down (LoRa, BLE and WiFi all run at 80 MHz) and
|
||||
// the touch panel asleep unless a tap is to wake the screen. Screen on: back.
|
||||
// Screen off: the clock down and the touch panel asleep unless a tap is to
|
||||
// wake the screen. Screen on: back.
|
||||
static bool s_touch_asleep = false;
|
||||
static void powerSave(bool on, bool keep_touch) {
|
||||
static uint32_t full_mhz = 0;
|
||||
if (!full_mhz) full_mhz = getCpuFrequencyMhz();
|
||||
setCpuFrequencyMhz(on ? 80 : full_mhz);
|
||||
cpuSlow(on);
|
||||
if (on && !keep_touch) { board.touchSleep(); s_touch_asleep = true; }
|
||||
else if (!on && s_touch_asleep) { board.touchWake(); s_touch_asleep = false; }
|
||||
}
|
||||
// Nothing due for `ms`: the loop task blocks and the core idles, clock-gated,
|
||||
// instead of spinning.
|
||||
static void idle(uint32_t ms) { if (ms) vTaskDelay(pdMS_TO_TICKS(ms)); }
|
||||
// instead of spinning -- woken early by a frame from the app (below).
|
||||
static TaskHandle_t s_loop_task = nullptr;
|
||||
static void idle(uint32_t ms) {
|
||||
if (!ms) return;
|
||||
if (!s_loop_task) s_loop_task = xTaskGetCurrentTaskHandle();
|
||||
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(ms));
|
||||
}
|
||||
|
||||
// Backlight 1-100 % (the brightness slider), on the LP5814 PWM.
|
||||
static void setBacklightPct(uint8_t pct) {
|
||||
@@ -496,10 +514,12 @@ static int netState() {
|
||||
default: return NET_CONNECTING;
|
||||
}
|
||||
}
|
||||
static void fetchFreeBuffer();
|
||||
static void netEnd() {
|
||||
if (s_net_scanning) { s_net_scanning = false; WiFi.scanDelete(); }
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
fetchFreeBuffer();
|
||||
}
|
||||
// The network joined (Settings > WiFi marks it), "" when not connected.
|
||||
static void netSsid(char* out, size_t n) {
|
||||
@@ -651,6 +671,14 @@ static void fetchAbandon() {
|
||||
else fetchRelease();
|
||||
}
|
||||
static const char* fetchError() { return s_ferr; }
|
||||
// WiFi off: the response buffer (grown to the biggest tile or update chunk,
|
||||
// up to 512 KB of PSRAM) given back, unless a request is still finishing.
|
||||
static void fetchFreeBuffer() {
|
||||
if (s_fstate != F_IDLE) return;
|
||||
heap_caps_free(s_fbuf);
|
||||
s_fbuf = nullptr;
|
||||
s_fcap = s_flen = 0;
|
||||
}
|
||||
|
||||
#elif defined(SIM_PLATFORM) && defined(__EMSCRIPTEN__)
|
||||
// Browser simulator (variants/sim/build_wasm_lvgl.sh): SimLcdDisplay blits to
|
||||
@@ -719,6 +747,7 @@ static bool begin() {
|
||||
static bool touched() { return SimLcdDisplay::touchState().down; }
|
||||
static void swallowTouch() { s_swallow = true; }
|
||||
static void powerSave(bool, bool) {} // the browser's page loop runs the sim
|
||||
static void cpuSlow(bool) {}
|
||||
static void idle(uint32_t) {}
|
||||
static void setBacklightPct(uint8_t pct) { (void)pct; } // the browser canvas has no backlight
|
||||
|
||||
@@ -870,3 +899,11 @@ static void loadHomeApps(char* out, size_t n) { nvs::getStr("mc_ui", "apps", out
|
||||
static void saveHomeApps(const char* v) { nvs::putStr("mc_ui", "apps", v); }
|
||||
|
||||
} // namespace lvport
|
||||
|
||||
#if defined(SEEED_WIO_TRACKER_L2)
|
||||
// A frame from the app (SerialBLEInterface, the BLE stack's task): the loop,
|
||||
// blocked in lvport::idle(), reads it now rather than at the next timeout.
|
||||
void serialFrameArrived() {
|
||||
if (TaskHandle_t t = lvport::s_loop_task) xTaskNotifyGive(t);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -116,11 +116,12 @@ static void drawGrid(lv_event_t* e) {
|
||||
// Markers, remembered so layout can reposition them. `idx` is the Nearby row
|
||||
// (MK_NODE), the waypoint index (MK_WAYPOINT) or the live-share slot (MK_LIVE).
|
||||
enum : uint8_t { MK_NODE, MK_WAYPOINT, MK_LIVE };
|
||||
struct Mark { int32_t lat_e6, lon_e6; int idx; uint8_t kind; lv_obj_t* obj; lv_obj_t* dot; };
|
||||
struct Mark { int32_t lat_e6, lon_e6; int idx; uint8_t kind; uint32_t col, text_hash; lv_obj_t* obj; lv_obj_t* dot; };
|
||||
static const int MAX_MARKS = NearbyModel::MAX_NEARBY > WaypointStore::CAPACITY + LiveTrackStore::CAPACITY
|
||||
? NearbyModel::MAX_NEARBY : WaypointStore::CAPACITY + LiveTrackStore::CAPACITY; // Nearby map, or waypoints + live shares
|
||||
static Mark* s_marks = psramBuf<Mark>(MAX_MARKS);
|
||||
static int s_mark_count = 0;
|
||||
static int s_mark_prev = 0; // during a rebuild: how many the last one left, reused where unchanged
|
||||
static int s_drag = 0; // px moved in the current press: a drag isn't a tap
|
||||
static bool s_available = false; // provider has data; checked when the map opens, not per frame
|
||||
static double s_left = 0, s_top = 0; // world px (at the current zoom) of the view's top-left
|
||||
@@ -419,7 +420,7 @@ void UITask::layoutMap() {
|
||||
double lat = atan(sinh(M_PI * (1 - 2 * _map_cy / (double)(1 << _map_z)))) * 180.0 / M_PI;
|
||||
mapview::s_grid = { left, top, mapview::gridStep(_map_z, lat, _prefs && _prefs->units_imperial, 56, sl, sizeof(sl)) };
|
||||
if (mapview::s_scale_lbl) {
|
||||
lv_label_set_text(mapview::s_scale_lbl, sl);
|
||||
setText(mapview::s_scale_lbl, sl);
|
||||
lv_obj_set_width(mapview::s_scale_bar, (int)lround(mapview::s_grid.step_px));
|
||||
}
|
||||
lv_obj_invalidate(_map_area);
|
||||
@@ -494,20 +495,20 @@ void UITask::layoutMap() {
|
||||
}
|
||||
if (_map_nav) layoutNav();
|
||||
|
||||
if (over) lv_label_set_text_fmt(_map_zoom_lbl, "z%d (map z%d)", _map_z, _map_z - over); // magnified
|
||||
if (over) setTextFmt(_map_zoom_lbl, "z%d (map z%d)", _map_z, _map_z - over); // magnified
|
||||
else if (mapview::s_provider == &mapview::s_vector) // spike: how long a tile takes
|
||||
{
|
||||
uint32_t ld, fl, ln;
|
||||
mapview::s_vector.lastSplit(ld, fl, ln);
|
||||
lv_label_set_text_fmt(_map_zoom_lbl, "z%d vt %lu ms (r%lu a%lu l%lu)", _map_z, (unsigned long)mapview::s_vector.lastMs(),
|
||||
setTextFmt(_map_zoom_lbl, "z%d vt %lu ms (r%lu a%lu l%lu)", _map_z, (unsigned long)mapview::s_vector.lastMs(),
|
||||
(unsigned long)ld, (unsigned long)fl, (unsigned long)ln);
|
||||
}
|
||||
else lv_label_set_text_fmt(_map_zoom_lbl, "z%d", _map_z);
|
||||
else setTextFmt(_map_zoom_lbl, "z%d", _map_z);
|
||||
const char* hint = !have_provider ? "No map on the SD card.\nPut tiles in /maps (tools/maps)."
|
||||
: mapview::s_dl.liveQueued() > 0 ? nullptr // being fetched
|
||||
: (!_map_pending && shown == 0 && missing > 0)
|
||||
? "No map here at this zoom -\nzoom out or download the area" : nullptr;
|
||||
if (hint && !areaSelecting()) { lv_label_set_text(_map_hint, hint); lv_obj_remove_flag(_map_hint, LV_OBJ_FLAG_HIDDEN); }
|
||||
if (hint && !areaSelecting()) { setText(_map_hint, hint); lv_obj_remove_flag(_map_hint, LV_OBJ_FLAG_HIDDEN); }
|
||||
else lv_obj_add_flag(_map_hint, LV_OBJ_FLAG_HIDDEN);
|
||||
areaLayout();
|
||||
}
|
||||
@@ -585,10 +586,12 @@ void UITask::mapLoop() {
|
||||
|
||||
void UITask::rebuildMapMarkers() {
|
||||
if (!_map_marks) return;
|
||||
for (int k = 0; k < mapview::s_mark_count; k++) lv_obj_delete(mapview::s_marks[k].obj);
|
||||
mapview::s_mark_prev = mapview::s_mark_count;
|
||||
mapview::s_mark_count = 0;
|
||||
if (_map_nav) rebuildNavMarkers();
|
||||
else rebuildNodeMarkers();
|
||||
for (int k = mapview::s_mark_count; k < mapview::s_mark_prev; k++) lv_obj_delete(mapview::s_marks[k].obj);
|
||||
mapview::s_mark_prev = 0;
|
||||
lv_obj_update_layout(_map_marks); // so layoutMap() knows where each dot sits in its marker
|
||||
}
|
||||
|
||||
@@ -604,9 +607,22 @@ void UITask::rebuildNodeMarkers() {
|
||||
|
||||
// One clickable object per marker: a dot plus the name beside it. The object
|
||||
// is placed so the dot's centre is on the spot, from where layout actually put
|
||||
// the dot inside it (Mark::dot) -- the name may be taller than the dot.
|
||||
// the dot inside it (Mark::dot) -- the name may be taller than the dot. The
|
||||
// markers are rebuilt every few seconds: one the same as the last time in its
|
||||
// slot is kept as it is, so an unchanged map isn't redrawn under it.
|
||||
void UITask::addMapMark(uint8_t kind, int idx, int32_t lat_e6, int32_t lon_e6, uint32_t col, const char* text) {
|
||||
if (!_map_marks || mapview::s_mark_count >= mapview::MAX_MARKS) return;
|
||||
uint32_t h = 2166136261u; // FNV-1a of the name
|
||||
for (const char* c = text; *c; c++) h = (h ^ (uint8_t)*c) * 16777619u;
|
||||
int k = mapview::s_mark_count;
|
||||
if (k < mapview::s_mark_prev) {
|
||||
mapview::Mark& old = mapview::s_marks[k];
|
||||
if (old.kind == kind && old.idx == idx && old.lat_e6 == lat_e6 && old.lon_e6 == lon_e6 && old.col == col && old.text_hash == h) {
|
||||
mapview::s_mark_count++;
|
||||
return;
|
||||
}
|
||||
lv_obj_delete(old.obj);
|
||||
}
|
||||
lv_obj_t* m = lv_obj_create(_map_marks);
|
||||
lv_obj_remove_style_all(m);
|
||||
lv_obj_set_size(m, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
@@ -628,7 +644,7 @@ void UITask::addMapMark(uint8_t kind, int idx, int32_t lat_e6, int32_t lon_e6, u
|
||||
lv_obj_remove_flag(name, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
mapview::Mark& mk = mapview::s_marks[mapview::s_mark_count++];
|
||||
mk.lat_e6 = lat_e6; mk.lon_e6 = lon_e6; mk.idx = idx; mk.kind = kind; mk.obj = m; mk.dot = dot;
|
||||
mk.lat_e6 = lat_e6; mk.lon_e6 = lon_e6; mk.idx = idx; mk.kind = kind; mk.col = col; mk.text_hash = h; mk.obj = m; mk.dot = dot;
|
||||
}
|
||||
|
||||
void UITask::mapPan(int dx, int dy) {
|
||||
@@ -968,24 +984,24 @@ void UITask::mapDownloadTick() {
|
||||
next_ui = millis() + 500;
|
||||
if (_map_dl_pill) {
|
||||
if (dl.state() == mapview::TileDownloader::CONNECTING) {
|
||||
lv_label_set_text(_map_dl_pill, LV_SYMBOL_WIFI " Connecting...");
|
||||
setText(_map_dl_pill, LV_SYMBOL_WIFI " Connecting...");
|
||||
lv_obj_remove_flag(_map_dl_pill, LV_OBJ_FLAG_HIDDEN);
|
||||
} else if (dl.active()) {
|
||||
if (dl.failed())
|
||||
lv_label_set_text_fmt(_map_dl_pill, LV_SYMBOL_DOWNLOAD " %lu / %lu " LV_SYMBOL_WARNING " %lu",
|
||||
setTextFmt(_map_dl_pill, LV_SYMBOL_DOWNLOAD " %lu / %lu " LV_SYMBOL_WARNING " %lu",
|
||||
(unsigned long)dl.processed(), (unsigned long)dl.total(), (unsigned long)dl.failed());
|
||||
else
|
||||
lv_label_set_text_fmt(_map_dl_pill, LV_SYMBOL_DOWNLOAD " %lu / %lu", (unsigned long)dl.processed(),
|
||||
setTextFmt(_map_dl_pill, LV_SYMBOL_DOWNLOAD " %lu / %lu", (unsigned long)dl.processed(),
|
||||
(unsigned long)dl.total());
|
||||
lv_obj_remove_flag(_map_dl_pill, LV_OBJ_FLAG_HIDDEN);
|
||||
} else if (dl.liveConnecting()) {
|
||||
lv_label_set_text(_map_dl_pill, LV_SYMBOL_WIFI " Connecting...");
|
||||
setText(_map_dl_pill, LV_SYMBOL_WIFI " Connecting...");
|
||||
lv_obj_remove_flag(_map_dl_pill, LV_OBJ_FLAG_HIDDEN);
|
||||
} else if (dl.liveQueued() > 0) {
|
||||
lv_label_set_text_fmt(_map_dl_pill, LV_SYMBOL_DOWNLOAD " live %d", dl.liveQueued());
|
||||
setTextFmt(_map_dl_pill, LV_SYMBOL_DOWNLOAD " live %d", dl.liveQueued());
|
||||
lv_obj_remove_flag(_map_dl_pill, LV_OBJ_FLAG_HIDDEN);
|
||||
} else if (mapview::s_available && dl.savedJob(s_job_tmp)) {
|
||||
lv_label_set_text(_map_dl_pill, LV_SYMBOL_DOWNLOAD " Resume?");
|
||||
setText(_map_dl_pill, LV_SYMBOL_DOWNLOAD " Resume?");
|
||||
lv_obj_remove_flag(_map_dl_pill, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(_map_dl_pill, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
@@ -290,7 +290,7 @@ void UITask::refreshNavBar() {
|
||||
navmap::fmtDuration(left, sizeof(left), _core->live_share.remainingSecs());
|
||||
o += snprintf(t + o, sizeof(t) - o, "%sLIVE %s", o ? " " : "", left);
|
||||
}
|
||||
lv_label_set_text(_nav_rec, t);
|
||||
setText(_nav_rec, t);
|
||||
if (o) lv_obj_remove_flag(_nav_rec, LV_OBJ_FLAG_HIDDEN); else lv_obj_add_flag(_nav_rec, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
int32_t tlat, tlon;
|
||||
@@ -298,23 +298,23 @@ void UITask::refreshNavBar() {
|
||||
bool person = false, set = false;
|
||||
bool pos = navCurrentTarget(tlat, tlon, name, sizeof(name), person, set);
|
||||
if (!set) {
|
||||
lv_label_set_text(_nav_title, "No target");
|
||||
setText(_nav_title, "No target");
|
||||
lv_obj_set_style_text_color(_nav_title, lv_color_hex(theme::TEXT_MUTED), 0);
|
||||
lv_label_set_text(_nav_info, "Tap here to pick a target, or hold the map on a spot");
|
||||
setText(_nav_info, "Tap here to pick a target, or hold the map on a spot");
|
||||
lv_obj_add_flag(_nav_clear, LV_OBJ_FLAG_HIDDEN);
|
||||
return;
|
||||
}
|
||||
lv_obj_set_style_text_color(_nav_title, lv_color_hex(theme::ACCENT), 0);
|
||||
lv_obj_remove_flag(_nav_clear, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_label_set_text_fmt(_nav_title, "%s %s", navmap::s_tb.active() ? LV_SYMBOL_LOOP : person ? UI_SYMBOL_USERS : UI_SYMBOL_FLAG,
|
||||
setTextFmt(_nav_title, "%s %s", navmap::s_tb.active() ? LV_SYMBOL_LOOP : person ? UI_SYMBOL_USERS : UI_SYMBOL_FLAG,
|
||||
name);
|
||||
if (!pos) {
|
||||
lv_label_set_text(_nav_info, "Position unknown (not shared recently)");
|
||||
setText(_nav_info, "Position unknown (not shared recently)");
|
||||
return;
|
||||
}
|
||||
int32_t lat, lon;
|
||||
if (!_core->course.currentLocation(lat, lon)) {
|
||||
lv_label_set_text(_nav_info, _core->gpsEnabled() || !_core->gpsAvailable()
|
||||
setText(_nav_info, _core->gpsEnabled() || !_core->gpsAvailable()
|
||||
? "Waiting for a GPS fix..." : "GPS is off - tap the crosshair to turn it on");
|
||||
return;
|
||||
}
|
||||
@@ -327,9 +327,9 @@ void UITask::refreshNavBar() {
|
||||
int cog;
|
||||
if (_core->course.currentCourse(cog)) snprintf(hdg, sizeof(hdg), " - you %d\xC2\xB0", cog);
|
||||
if (navmap::s_eta.eta(km, eta, sizeof(eta)))
|
||||
lv_label_set_text_fmt(_nav_info, "%s %d\xC2\xB0 %s%s - ETA %s", dist, to, geo::bearingCardinal(to), hdg, eta);
|
||||
setTextFmt(_nav_info, "%s %d\xC2\xB0 %s%s - ETA %s", dist, to, geo::bearingCardinal(to), hdg, eta);
|
||||
else
|
||||
lv_label_set_text_fmt(_nav_info, "%s %d\xC2\xB0 %s%s", dist, to, geo::bearingCardinal(to), hdg);
|
||||
setTextFmt(_nav_info, "%s %d\xC2\xB0 %s%s", dist, to, geo::bearingCardinal(to), hdg);
|
||||
}
|
||||
|
||||
// ── Target selection ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -350,7 +350,7 @@ void UITask::refreshMelody() {
|
||||
lightNote(s_play_all ? melodyNote() : -1);
|
||||
if (!s_play_lbl || playing == s_playing_shown) return;
|
||||
s_playing_shown = playing;
|
||||
lv_label_set_text(s_play_lbl, playing ? LV_SYMBOL_STOP " Stop" : LV_SYMBOL_PLAY " Play");
|
||||
setText(s_play_lbl, playing ? LV_SYMBOL_STOP " Stop" : LV_SYMBOL_PLAY " Play");
|
||||
}
|
||||
|
||||
void UITask::melodySave() {
|
||||
|
||||
@@ -313,7 +313,7 @@ void UITask::pollStorage() {
|
||||
if (s_sd_bar && !s_sd_read) {
|
||||
s_sd_read = true;
|
||||
s_sd_ok = lvport::sdInfo(s_sd_total, s_sd_used, s_sd_card);
|
||||
if (!s_sd_ok) lv_label_set_text(s_sd_lbl, "Can't read the card");
|
||||
if (!s_sd_ok) setText(s_sd_lbl, "Can't read the card");
|
||||
else if (s_sd_card > s_sd_total + s_sd_total / 4) { // most of the card sits outside the partition
|
||||
char c[16], t[16], line[160];
|
||||
fmtBytes(c, sizeof(c), s_sd_card);
|
||||
@@ -337,7 +337,7 @@ void UITask::pollStorage() {
|
||||
fmtBytes(b, sizeof(b), mapview::s_live_cache.bytes());
|
||||
if (mapview::s_live_cache.clearing()) snprintf(t, sizeof(t), "Deleting... %lu left", (unsigned long)mapview::s_live_cache.count());
|
||||
else snprintf(t, sizeof(t), "%lu tiles, %s", (unsigned long)mapview::s_live_cache.count(), b);
|
||||
lv_label_set_text(s_live_info, t);
|
||||
setText(s_live_info, t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,23 @@ static lv_obj_t* label(lv_obj_t* parent, const char* text, const lv_font_t* font
|
||||
lv_obj_set_style_text_color(l, lv_color_hex(color), 0);
|
||||
return l;
|
||||
}
|
||||
// A label's text, set only when it differs: LVGL redraws a label on every
|
||||
// set_text, the same text or not, and the refreshes that run every second
|
||||
// mostly write back what's already there.
|
||||
static void setText(lv_obj_t* l, const char* text) {
|
||||
const char* cur = lv_label_get_text(l);
|
||||
if (cur && strcmp(cur, text) == 0) return;
|
||||
lv_label_set_text(l, text);
|
||||
}
|
||||
static void setTextFmt(lv_obj_t* l, const char* fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||
static void setTextFmt(lv_obj_t* l, const char* fmt, ...) {
|
||||
char b[160];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(b, sizeof(b), fmt, ap);
|
||||
va_end(ap);
|
||||
setText(l, b);
|
||||
}
|
||||
// A sentence across the parent's width, wrapping: a hint, a note, a status.
|
||||
static lv_obj_t* noteLabel(lv_obj_t* parent, const char* text, const lv_font_t* font = THEME_FONT_SMALL,
|
||||
uint32_t color = theme::TEXT_MUTED) {
|
||||
@@ -296,7 +313,7 @@ static void infoSet(lv_obj_t* value, const char* text) {
|
||||
lv_obj_t* row = lv_obj_get_parent(value);
|
||||
if (!text || !text[0]) { lv_obj_add_flag(row, LV_OBJ_FLAG_HIDDEN); return; }
|
||||
lv_obj_remove_flag(row, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_label_set_text(value, text);
|
||||
setText(value, text);
|
||||
}
|
||||
|
||||
// A bare flex row / column sized to its content, taps passing through.
|
||||
@@ -828,9 +845,9 @@ static void clockFaceSet(lv_obj_t* f, const struct tm* ti, const NodePrefs* p) {
|
||||
lv_obj_t* ap = lv_obj_get_child(f, 1);
|
||||
char clk[12] = "--:--";
|
||||
if (ti) fmtClock(clk, sizeof(clk), *ti, p, false, true);
|
||||
lv_label_set_text(lv_obj_get_child(f, 0), clk);
|
||||
setText(lv_obj_get_child(f, 0), clk);
|
||||
bool h12 = ti && p && p->clock_12h;
|
||||
if (h12) lv_label_set_text(ap, ti->tm_hour < 12 ? "AM" : "PM");
|
||||
if (h12) setText(ap, ti->tm_hour < 12 ? "AM" : "PM");
|
||||
if (h12 != !lv_obj_has_flag(ap, LV_OBJ_FLAG_HIDDEN)) {
|
||||
if (h12) lv_obj_remove_flag(ap, LV_OBJ_FLAG_HIDDEN);
|
||||
else lv_obj_add_flag(ap, LV_OBJ_FLAG_HIDDEN);
|
||||
@@ -1110,6 +1127,7 @@ void UITask::loop() {
|
||||
uint32_t sig = threadSignature();
|
||||
if (_thread_dirty || sig != _thread_sig) refreshThread();
|
||||
}
|
||||
lvport::cpuSlow(!cpuNeeded());
|
||||
lv_next = lv_timer_handler();
|
||||
}
|
||||
lvport::idle(idleMillis(lv_next));
|
||||
@@ -1117,13 +1135,14 @@ void UITask::loop() {
|
||||
|
||||
// How long loop() may sleep: nothing is due before then. Not at all while the
|
||||
// mesh has packets queued; briefly while a melody plays (its notes are timed
|
||||
// here) or the app is connected (its frames are read one a pass).
|
||||
// here) or frames to or from the app wait (read one a pass, sent spaced out).
|
||||
// A frame arriving from the app over BLE wakes the loop (serialFrameArrived).
|
||||
uint32_t UITask::idleMillis(uint32_t lv_next) {
|
||||
if (the_mesh.hasPendingWork()) return 0;
|
||||
#ifdef PIN_BUZZER
|
||||
if (_buzzer.isPlaying()) return 1;
|
||||
#endif
|
||||
if (isClientConnected()) return 2;
|
||||
if (isClientConnected() && _serial->hasPendingFrames()) return 2;
|
||||
if (_asleep) return 50; // as often as the buttons and tap to wake are polled
|
||||
return lv_next < 10 ? lv_next : 10; // LVGL's next timer: a refresh, an animation, input
|
||||
}
|
||||
@@ -1429,9 +1448,9 @@ void UITask::refreshStatusBar() {
|
||||
if (localTime(_prefs, ti)) {
|
||||
char clk[12];
|
||||
fmtClock(clk, sizeof(clk), ti, _prefs, true);
|
||||
lv_label_set_text(_status_time, clk);
|
||||
setText(_status_time, clk);
|
||||
} else {
|
||||
lv_label_set_text(_status_time, "--:--");
|
||||
setText(_status_time, "--:--");
|
||||
}
|
||||
|
||||
// Battery: the icon, then % or volts per Settings > Battery display. The
|
||||
@@ -1446,7 +1465,7 @@ void UITask::refreshStatusBar() {
|
||||
case battery::VOLTAGE: snprintf(level, sizeof(level), " %u.%02u V", mv / 1000, (mv % 1000) / 10); break;
|
||||
default: break;
|
||||
}
|
||||
lv_label_set_text_fmt(_status_batt, "%s%s", batt, level);
|
||||
setTextFmt(_status_batt, "%s%s", batt, level);
|
||||
lv_obj_t* left_of = _status_batt; // the icons pack up to this
|
||||
if (_board->isExternalPowered()) {
|
||||
lv_obj_remove_flag(_status_chg, LV_OBJ_FLAG_HIDDEN);
|
||||
@@ -2246,8 +2265,8 @@ void UITask::refreshScanPopup() {
|
||||
if (!_scan_list) return;
|
||||
_scan->refreshScan();
|
||||
int n = _scan->count();
|
||||
if (_scanning) lv_label_set_text_fmt(_scan_status, LV_SYMBOL_REFRESH " Listening for replies... %d", n);
|
||||
else lv_label_set_text_fmt(_scan_status, "%d node%s answered the discover request", n, n == 1 ? "" : "s");
|
||||
if (_scanning) setTextFmt(_scan_status, LV_SYMBOL_REFRESH " Listening for replies... %d", n);
|
||||
else setTextFmt(_scan_status, "%d node%s answered the discover request", n, n == 1 ? "" : "s");
|
||||
|
||||
uint32_t sig = (uint32_t)n + (_scanning ? 0x10000u : 0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
@@ -2309,10 +2328,10 @@ void UITask::refreshNearbyList() {
|
||||
int n = _nearby->count();
|
||||
|
||||
if (_nearby_sort_lbl)
|
||||
lv_label_set_text(_nearby_sort_lbl, _nearby->sortMode() == NearbyModel::SORT_TIME ? "Recent" : "Dist");
|
||||
setText(_nearby_sort_lbl, _nearby->sortMode() == NearbyModel::SORT_TIME ? "Recent" : "Dist");
|
||||
int32_t lat, lon;
|
||||
bool gps = _nearby->ownPosition(lat, lon);
|
||||
lv_label_set_text_fmt(_nearby_status, "%d node%s%s", n, n == 1 ? "" : "s",
|
||||
setTextFmt(_nearby_status, "%d node%s%s", n, n == 1 ? "" : "s",
|
||||
gps ? "" : " - no GPS fix, distances unknown");
|
||||
if (sig == _nearby_sig) return;
|
||||
_nearby_sig = sig;
|
||||
@@ -2455,12 +2474,12 @@ void UITask::refreshNode() {
|
||||
int16_t out = 0, back = 0; uint32_t rtt = 0;
|
||||
_core->ping.getResult(out, back, rtt);
|
||||
if (!_core->ping.isActive() && (out || back || rtt)) {
|
||||
lv_label_set_text_fmt(_node_ping, "Ping %lu ms - SNR out %.1f back %.1f", (unsigned long)rtt,
|
||||
setTextFmt(_node_ping, "Ping %lu ms - SNR out %.1f back %.1f", (unsigned long)rtt,
|
||||
out / 4.0f, back / 4.0f);
|
||||
_pinging = false;
|
||||
} else if (millis() - _ping_started_ms > 3000) {
|
||||
_core->ping.clear();
|
||||
lv_label_set_text(_node_ping, "Ping: no reply");
|
||||
setText(_node_ping, "Ping: no reply");
|
||||
_pinging = false;
|
||||
}
|
||||
}
|
||||
@@ -3459,6 +3478,19 @@ void UITask::setKeyboardAlphabets(int main_idx, int alt_sel) {
|
||||
#include "StorageScreen.h"
|
||||
#include "Splash.h"
|
||||
|
||||
// The screen on, the full CPU clock while it's worth having: touched in the
|
||||
// last CPU_IDLE_MS, something moving, map tiles to decode, a download or an
|
||||
// update on WiFi. Otherwise 80 MHz (lvport::cpuSlow) -- a clock ticking over,
|
||||
// a message being read.
|
||||
static constexpr uint32_t CPU_IDLE_MS = 2000;
|
||||
bool UITask::cpuNeeded() {
|
||||
if (lv_display_get_inactive_time(NULL) < CPU_IDLE_MS || lv_anim_count_running() > 0) return true;
|
||||
if (wifiInUse() || mapview::s_dl.liveQueued() > 0) return true;
|
||||
if (_screen == SCR_MAP && _map_pending) return true;
|
||||
if (_screen == SCR_HOME && home::mini::s_area && home::mini::s_pending) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(SIM_PLATFORM) && defined(__EMSCRIPTEN__)
|
||||
// Sim tests: straight to a screen by name.
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE void sim_open(const char* name) {
|
||||
|
||||
@@ -419,6 +419,7 @@ private:
|
||||
bool _wake_down = false;
|
||||
uint32_t autoOffMillis() const;
|
||||
uint32_t idleMillis(uint32_t lv_next);
|
||||
bool cpuNeeded();
|
||||
void checkLowBattery();
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
|
||||
@@ -269,7 +269,7 @@ void UITask::pollWifiScan() {
|
||||
lv_obj_center(label(b, text, THEME_FONT_SMALL, known ? theme::ACCENT : theme::TEXT));
|
||||
}
|
||||
if (n > 0) lv_obj_remove_flag(_wifi_list, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_label_set_text(_wifi_status, n > 0 ? "Tap a network, then enter its password." : "No networks found.");
|
||||
setText(_wifi_status, n > 0 ? "Tap a network, then enter its password." : "No networks found.");
|
||||
if (!wifiInUse()) lvport::netEnd(); // the scan switched the radio on
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user