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>