diff --git a/docs/development/plan-next.md b/docs/development/plan-next.md new file mode 100644 index 00000000..c4a5bb15 --- /dev/null +++ b/docs/development/plan-next.md @@ -0,0 +1,109 @@ +# Plan: from here to the merge into dev (2026-09-28) + +Work in order; each item is implemented, checked in the sim, flashed, and +committed only after it's accepted. Tick items off as they land. Working +file: goes in the repo clean-up (stage I). + +## A. Bugs + +- [x] **A1. Restart on a screenshot + "340 B stack free".** Likely one cause. + Diagnostics shows the UI loop task's lowest-ever stack headroom + (`uxTaskGetStackHighWaterMark`); the Arduino loop stack is 8 KB, so + 340 B left is nearly an overflow. `takeScreenshot()` keeps a 960 B line + buffer on that stack, plus the FAT write. + 1. Confirm with Diagnostics > Last crash after a screenshot restart. + 2. Move the line buffer off the stack. + 3. Raise the loop stack (internal RAM). + 4. Measure the headroom on heavy screens (map, history, OTA). + 5. Label the Diagnostics row with the task it measures. + Done 2026-09-28: loop stack 16 KB (SET_LOOP_TASK_STACK_SIZE), line + buffer static, row "UI stack free (lowest)". Measured 8532 B free + after map, history and screenshots, so the UI peaks at ~7.8 KB -- + the old 8 KB left 340 B. Could go down to 12 KB if RAM gets tight. +- [x] **A2. USB popup before the splash screen is gone.** `usbPoll()` waits + for the splash to finish; a cable plugged in at boot shows the popup + right after it. + +## B. Map + +- [x] **B1. Map menu order.** List the current entries, propose groups and + an order (view, overlays, areas & downloads, trails, test), agree on + it before coding. Done: TRAIL / LIVE SHARE / ARRIVAL ALERT (each with + its own options page, one section of PG_NAV), OFFLINE MAPS (Map areas, + Live tiles; "Download an area" dropped, a running / unfinished + download shows at the top of Map areas), LAYERS. +- [x] **B2. Downloaded areas.** Selecting an area previews it at once (no + Show button); leaving the menu hides it and restores the previous map + view. Fixes the area staying on the map after Show. + +## C. Diagnostics: the noise tests (to think over) + +Diagnostics stays as it is. Only the Noise tab is in question: the tests that +found the interference source (noise floor with the board's parts off one by +one, the 850-930 MHz and mesh-channel sweeps, the spike hunt, the 15 s states +for a second radio). They are L2-specific and block the loop for ~90 s. + +- [ ] Decide: remove them, or keep one universal tool (for example a noise + floor sweep round the mesh frequency that works on any board) and drop + the L2 part toggling and spike hunt. + +## D. Quiet hours in the core + +- [ ] A start and end time when the device is silent; in ui-core, so L1, L2 + and other boards get it. New NodePrefs fields go at the end of the + stored layout, so an L1 keeps its settings after the update. + To decide: the Clock alarm rings anyway (proposed yes); whether a + message wakes the screen during quiet hours; with the clock not set, + quiet hours are off (proposed). + +## E. OTA end-to-end test + +- [ ] After the feature changes: a real release with an L2 asset (the v3 env + must be the `*_solo_lvgl` one before tagging), install over WiFi. + Publishing the release needs confirmation first. + +## F. The big review + +- [ ] Dead code; similar elements written several times, merged into one; + places to speed things up or save RAM, flash and battery. + First a list of findings to accept, then fixes in batches, each checked + in the sim and on the device. + +## G. Core parity with L1 SOLO + +- [ ] A feature table: L1 SOLO / ui-core / L2 (ui-lvgl). What's missing goes + into ui-core, not into each UI separately. + +## H. Before the merge into dev + +- [ ] The pull request about the lock screen. +- [ ] The new issues. +- [ ] Merge into dev. +- [ ] A last review. + +## I. Repo, documentation, website, firmware tiers (its own detailed plan) + +- [ ] Repo: remove working files (plans etc.), keep only the project's code; + README down to the minimum; add Buy Me a Coffee. +- [ ] Documentation written from scratch: short, describing the firmware's + features, easy to browse; sections marking where devices differ; no + screenshots of every screen and device. It stays in the repo, its + official entry is the simulator website. +- [ ] Website rebuilt and polished; the simulator lets you pick which + firmware to simulate. +- [ ] Firmware in three tiers, one shared core, extras (map tiles etc.) by + what the hardware can do, making full use of each device: + - minimal -- nRF52, a limited UI, small e-ink and OLED; + - standard -- mostly ESP32, large e-ink and LCD, higher resolutions, + a somewhat richer UI; + - color -- colour touch screens. + +Proposed: write a short tier spec (which boards, which features, what's in +the core) as a document before stage F, so the review consolidates towards +it; implement the tiers, docs and website after the merge. + +## Open decisions + +- C: remove the noise tests or keep a universal version. +- D: alarm during quiet hours; messages waking the screen. +- I: tier spec before stage F, or after the merge. diff --git a/examples/companion_radio/ui-lvgl/DiagScreen.h b/examples/companion_radio/ui-lvgl/DiagScreen.h index c8fde853..e3afd694 100644 --- a/examples/companion_radio/ui-lvgl/DiagScreen.h +++ b/examples/companion_radio/ui-lvgl/DiagScreen.h @@ -66,7 +66,7 @@ static const Name NAMES[] = { { "Noise floor", SEC_RADIO, "Noise floor", false }, { "RSSI/SNR", SEC_RADIO, "Last packet", false }, { "Queue", SEC_RADIO, "Send queue", false }, { "Errors", SEC_RADIO, "Errors", false }, { "RXPS wd s/h", SEC_RADIO, "RX watchdog soft / hard", false }, - { "Heap free", SEC_MEMORY, "Heap free", false }, { "Stack free", SEC_MEMORY, "Stack free", false }, + { "Heap free", SEC_MEMORY, "Heap free", false }, { "Stack free", SEC_MEMORY, "UI stack free (lowest)", false }, { "Pool free", SEC_MEMORY, "Packet pool free", false }, }; static const Name* nameOf(const char* core) { diff --git a/examples/companion_radio/ui-lvgl/Splash.h b/examples/companion_radio/ui-lvgl/Splash.h index 67ca2931..6707c840 100644 --- a/examples/companion_radio/ui-lvgl/Splash.h +++ b/examples/companion_radio/ui-lvgl/Splash.h @@ -92,6 +92,7 @@ static void dismiss() { anim::run(s_root, anim::setOpa, LV_OPA_COVER, LV_OPA_TRANSP, 300, freeAll); } static void onTimer(lv_timer_t* t) { (void)t; dismiss(); } +static bool up() { return s_root != nullptr; } // until its fade-out has finished static void onTap(lv_event_t* e) { (void)e; dismiss(); } // One character of the lettering: its width, and whether (x, y) is inked. diff --git a/examples/companion_radio/ui-lvgl/StorageScreen.h b/examples/companion_radio/ui-lvgl/StorageScreen.h index c254a62e..2fd2017c 100644 --- a/examples/companion_radio/ui-lvgl/StorageScreen.h +++ b/examples/companion_radio/ui-lvgl/StorageScreen.h @@ -504,10 +504,13 @@ static void usbPanel(const char* title, const char* text, bool buttons) { } } +namespace splash { static bool up(); } // Splash.h + void UITask::usbPoll() { using namespace usbview; static uint32_t next = 0; if ((int32_t)(millis() - next) < 0) return; + if (splash::up()) return; // a cable in at boot: the popup comes once the splash is gone next = millis() + 250; if (lvport::usbDriveOn()) { if (!lvport::usbDriveDone()) return; diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 99e4c9e6..c390f678 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -4,6 +4,11 @@ #include "target.h" #if defined(ESP32) #include + // The UI runs on Arduino's loop task: LVGL's renderer, FAT on the card and + // the map's decoders all nest on its stack, and the stock 8 KB left a few + // hundred bytes at the deepest point (Diagnostics > Stack free, the lowest + // ever) -- a screenshot's file write went past it and reset the device. + SET_LOOP_TASK_STACK_SIZE(16 * 1024); #endif #include #include @@ -1496,7 +1501,7 @@ void UITask::takeScreenshot() { put32(2, size); put32(10, 54); put32(14, 40); put32(18, W); put32(22, H); hdr[26] = 1; hdr[28] = 24; put32(34, row * H); ok = fwrite(hdr, 1, 54, f) == 54; - uint8_t line[320 * 3]; + static uint8_t line[320 * 3]; // not on the loop task's stack for (int32_t y = H - 1; ok && y >= 0; y--) { // bottom-up, BGR const uint16_t* s = px + y * W; for (int32_t x = 0; x < W && x < 320; x++) {