The Back hold started at millis() | 2, up to 2 ms ahead of the clock. With the
screen off the loop comes round in under that, the unsigned difference wrapped
and read as 3 s held: the countdown started and the release cancelled it. The
same "| 1" sentinel ahead of millis() is fixed in the other places it was used
(L1 status screen, L2 prefs flush, WiFi test, live tiles, home edge flip, SD
retry).
A lock sequence left half done by a long Back hold (no click on release) kept
the SOS hold off until the next click: cleared 500 ms after Back is let go.
Picking an SOS contact no longer saves prefs on the way there and back (a flash
write stalls the L1); leaving the SOS screen saves it with the rest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Duplicates found between the L1 and L2 frontends, and inside the L2 one:
- geo::fmtLocShare / fmtLatLon / turnDeg / isAhead (GeoUtils.h): the [LOC]
text was built in five places, the turn angle and its "ahead" threshold in
both navigations, coordinates by hand in nine. Tested.
- LiveShareEngine::setEnabled: live share was switched in three places.
- L1 waypoint share uses WaypointModel::shareText; Trail's "Share my pos"
goes through UITask::quickShareMyLocation, so it now sends to a running
live-share session as the home Map page does.
- L2 Widgets.h: switchCreate (one switch size, was four), switchRow with a
state and callback (16 hand-wired copies), switchRowOpens (Bluetooth, WiFi,
GPS), primaryButton (one height for Save / Measure / New area / Update).
GPS: with it off, starting the trail, live share or track back, sharing or
marking a position, or centring the map asks first ("GPS is off") on both
L1 and L2; Cancel leaves it undone. A share or a mark waits for a fresh fix
(a switched-off module may still report its last one), which also fixes the
L1 "Share my pos" sending a stale position. SOS still turns the GPS on itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings: Name and Radio first, then Bluetooth, WiFi and GPS side by side
(a tap on each row opens its details: the pairing PIN, the networks, the GPS
screen with a new Options tab for power saving and the fixed position), then
Device, Mesh & data (Adverts & privacy now holds Auto-advert) and System.
Time & units, Power as one group. Row symbols get a column of their own, value
labels are as wide as their text, group headings are uppercase everywhere, the
auto-add node types show only with "By type". SOS in three groups, WiFi with
its switch, Map tools with the layers (Packet flow among them) under the trail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Discover: a reply after its try timed out, was stopped or was overtaken
went on to the normal handling and became the contact's route (and sent
a path back); the last tags sent are kept and a late reply is dropped.
Leaving Route on the L2 stops a discovery as it stops a trace.
- Discover: a route whose reply came back another way offers that way too
(L2 There / Back, L1 a Back row), turned round into a route there, so a
trace checks it; the Discover button no longer sits on its group.
- Screenshots: numbered one past the highest (one pass over the folder,
not a stat per number), so one taken after a delete is still the newest.
- Gallery: the newest 300 kept whatever order the folder lists them in;
BMP rows read in the file's order and several at a time; the thumbnail
buffer sized to the page; the folder read once per visit; a failed
open frees or restores the picture.
- NTP: the slot the router's server goes in emptied before joining, so a
cached address isn't taken for it; "Clock set from the network" judged
against the clock a tick before (SNTP has set the system time already).
- Sim: sim_scroll up stops at the top.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Gallery: thumbnails 30 to a page, built only while the screen is still (a decode held the loop and a scroll read as a tap)
- segmented(): CLICK_TRIG, so a swipe starting on a screen's tabs doesn't switch them
- Settings > Device > Home apps removed (hold an app on Home instead)
- platformio.ini: a note that the L2 has no microphone on GPIO15 (verified on hardware)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WiFi starts and joins on internal RAM: ~42 KB the first time, ~25 KB after,
more for WPA. Free internal RAM had shrunk to ~20 KB after opening the map
(the tile decoder's 12 KB stack, the 32 KB I-cache, the DMA draw buffers,
UI statics), so a scan failed at once (esp_wifi_init: no memory) and a join
during a tile download timed out (AUTH_EXPIRE).
- ESP-IDF: CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY, and the esp_timer
task stack 8 -> 4 KB (it uses ~0.4 KB). Rebuild the framework libs:
pio run -e Wio_Tracker_L2_idf_libs.
- PSRAM_BSS (EXT_RAM_BSS_ATTR) on the UI's big statics: QR codes, map
state, charts, lists. .dram0.bss 115 -> 66 KB.
- The tile decoder's and fetcher's task stacks in PSRAM.
Free internal RAM, idle: 48 -> 105 KB; WiFi up: 57 KB, 29 KB at the
lowest while downloading tiles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ACCENT_DIM (the accent mixed toward black) came out brown on selected
tabs, chips, segments and own bubbles. They take the accent with dark
text now, as primary actions and the quick panel tiles do:
styleSelected() sets the fill, the pressed shade and every label under
it. Own bubbles: dark text, age and delivery mark (green or red can't be
read on amber); the melody editor's selected note is outlined, the
sounding one filled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A group row's text column (and an info row's key) grows into what the
controls on its right leave, and LVGL lays out children before their
parent, so its labels were first measured 1 px wide; LVGL also works out a
new label's height at its old width (0) before the real one. Each height
that changed then took another pass up the screen: 4-6 passes per screen.
rowsFit(), run before a frame's layout (LV_EVENT_REFR_START) and by
layoutNow() in place of every explicit lv_obj_update_layout(), gives each
new row's growing child the width the flex will give it, worked out from
the other children's styles and text, and labelFit() measures wrapping
labels at their real width up front. The grow stays, so a control that
can't be sized ahead only falls back to the old passes.
Layout on the device: Display page 34 -> 24 ms, Bot 33 -> 18, Quick
37 -> 22, Radio 26 -> 18, Diagnostics 20 -> 12. Sim screenshots of 26
screens unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by counting flushes on idle screens in the sim:
- The compose field's cursor blinked (a redraw every 400 ms) as soon as
a conversation opened, the keyboard hidden: lv_keyboard_set_textarea()
focuses the field, and LVGL only stops a blink when the cursor moves.
fieldFocus() sets the state and nudges the blink; also used for the
WiFi and channel fields, which left theirs running.
- lv_obj_align_to() restyles on every call, so the status bar's icons
redrew every second; placeLeftOf() moves them only when they move.
- Text colours set every second (nav bar, GPS status, Diag errors) go
through setTextColor(), which skips an unchanged colour.
- A LV_LABEL_LONG_DOT label writes its "..." into its own text, so
setText() never matched and rewrote it every refresh (the nav bar's
hint); labelShows() reads past the dots.
- The map area, the area-outline, trail and vector-label layers were
invalidated on every map refresh (every 3 s, a full-map redraw); now
only when the view or what they draw changed.
Idle map: 17 -> 1 flush in 5 s; list and settings screens: 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured on the L2 (steady full frames): Settings page 37.7 -> 31.4 ms,
Display 41.5 -> 37.9, Contacts 34.6 -> 32.9, Home 37.3 -> 32.4.
- styleSurface() leaves boxes in the page colour unpainted; styleOpaque()
for the screen and the status bar, which things slide under.
- group() cards clip their corners only while a row is held
(rowPressable(), shared by the tappable rows).
- rowSlider(): the track an opaque tint (theme::mix(), also behind
ACCENT_DIM) instead of 20 % accent blended every frame.
- Map buttons, pills, scale, nav bar and the Home minimap labels opaque.
- Screens drift in without the fading cover: its full-screen blend was in
every entrance frame (page 39 -> 29 ms).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
- The WiFi and Bluetooth rows rebuilt the whole Settings page to update
their hint, cutting the switch's animation short; the hint is now
rewritten in place (rowHintSet). The "Bluetooth on" echo toast is gone.
- Map download: a 404 gets the same one retry as other errors
(OpenTopoMap sends the odd 404 for a tile not rendered yet), and a
download that ends with gaps says "Done, N missing - download again".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- USB through TinyUSB (ARDUINO_USB_MODE=0) instead of USB-Serial/JTAG: the
serial port stays, and plugged into a computer (enumerated, the bus not
suspended -- on battery an unplug only shows as a suspend) the device asks
"Charge only / USB drive". As a drive the firmware lets go of the card and
the computer gets its sectors; ejected or unplugged, the device restarts.
- Incoming messages: a banner slides down from under the status bar (icon,
sender or channel, two lines of the text from history); a tap opens the
conversation (not while locked), a swipe up dismisses it, not shown for
the conversation already open. Toasts moved up there too, under the banner
when both are up; the ones only echoing a switch or a choice are gone.
- Storage > Format card: a popup saying what goes, then Format tapped twice;
one FAT32 volume over the whole card (readable or not), then a restart.
- A deliberate restart shows a full "Restarting..." screen with the reason
instead of the screen just freezing.
- The screenshot flash is a thin frame round the edges: a full-screen fade
redrew all 320x240 each step and tore.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Settings > WiFi keeps up to 8 networks (NVS "s0".."s7" / "p0".."p7"; the
single one older builds saved moves over). A tap loads one to change its
password, Forget asks with a second tap, a scan marks the known ones.
- Map downloads, live tiles and updates join the strongest saved network in
range (a short scan first when more than one is saved), else the newest
one -- which also covers a hidden network.
- Save joins the network once: "password OK", turned away (forgotten again,
the form keeps it to fix) or not in range (kept, checked later).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
group() / settingRow / listRow / actionRow / choiceRow / groupNote: one card
per section, hairlines between rows, hints that wrap. Dropdowns replaced by
a centred picker list. Settings regrouped (Device, Connections, Map & data,
System); Display & power split into Display, Power and Time on L2 only
(the shared schema is unchanged), Keyboard and About get their own pages.
Radio and Repeater share one LoRa parameter block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Power: the UI loop sleeps until something is due (never with packets
queued); screen off puts the CPU at 80 MHz, the GT911 to sleep (unless a
tap wakes the screen) and the LP5814 in standby. GPS off now cuts the
L76K's rail and UART on the L2 (L2GpsProvider). The battery divider is
powered only for a reading (measured: settles at once); readings within
2 s are reused. The SD mount is retried at most every 5 s.
Fixes: WiFi left on after leaving a scan, live tiles' WiFi kept with the
screen off on the map, an ADC read every second for the status bar, touch
polled on every pass while dark, a scan cutting an update's WiFi, relay
count not refreshing in a channel thread, raw @[nick] in room previews.
Labels skip unchanged text (no redraw).
Helpers: scrollList, dimOverlay, settingRow, flexBox, nvs::get/put,
trailFilePath (a second save in the same minute no longer overwrites),
unreadTotal. Dead code removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Anim.h: screens emerge from the middle (background cover fades, content
drifts 6 px), Home pages slide, popups and toasts rise and fade in
- Theme.h: accent rules (fill = primary action, dim fill = selected, text =
names/counts/modes), theme layer over the default theme, radius tokens,
accent colour picker (Settings > Display & power > LOOK, NVS mc_ui)
- Splash with the Solo and upstream versions
- Status icons in equal cells, L1-style relay marks, path window with a hop
diagram, tile download popup fits the screen (WiFi only in Settings)
- Perf: uncompressed fonts, two 120-line PSRAM buffers (~48 -> ~33 ms per
transition frame); UI_PERF_TEST harness
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The map's download button fetches the visible area (overview levels up to
a chosen zoom) into /sdcard/maps from OpenTopoMap or maps/source.txt,
skipping tiles already on the card and rejecting placeholder answers.
Settings > WiFi stores the network in NVS (with scan); WiFi is on only
while a download runs. HTTP runs on a worker task on core 0; the popup
shows link, internal heap and the last TLS/HTTP error.
Internal RAM was the blocker (~13 KB free with WiFi + Bluedroid): the L2
LVGL env now allocates the_mesh in PSRAM (MESH_IN_PSRAM, static RAM
210 -> 87 KB) and mbedTLS allocates from PSRAM. Confirmed on hardware.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>