Every packet heard runs from its origin through the repeaters with a
position to this device; a packet sent is a ring. ui-core/PacketFlow.h
resolves them (UiCore::flow, MyMesh logRx/logTx -> Listener). ui-lvgl
draws them on the map's marker layer and repaints only their area while
they move; ui-new draws the last minute on the Nodes map ("Flow" in its
options). Setting: Map options > On the map > Packet flow, CLI map.flow.
Off by default. Sim-checked only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core: PathEditor results are links (node k hearing k-1 and back); toNode()
off traces only the repeaters (a companion answers only with repeat on);
probe() traces to each repeater in turn after no reply; failures carry a
reason; an empty list saves "direct". PingEngine takes over a slot left past
its timeout. A kept route: the dropped heard path isn't announced to the app
nor bumps lastmod; an app update keeps the flag only if it leaves the route.
Marker boxes sized for every dot and name (overflow).
ui-lvgl Route: one line per node with out / back dB coloured by quality, tap
a hop to move / remove, tap the node to include it, Keep in the header, Find
where beside a no-reply status. ui-new Path: To node / Find break rows,
result alerts, cursor kept on its action.
Channels: ui-new re-sorts the list on entering it, cursor on the same channel.
Docs: Path and trace, Channel order. Sim hooks: open route, add channel,
open messages.
Checked in the browser sim (L2 + ui-new peer + repeater); not on hardware.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- UITaskBase holds what both UIs' shutdown() and low-battery check share:
the saves before the goodbye sound, the power-down (GPS through its
provider) and the battery EMA and threshold.
- L2 SD archive: a channel's file is keyed by its secret only, so renaming
keeps the history; old files are renamed once at boot.
- L2 fetch: abandoning a request and the task finishing it are one critical
section (it could stay DONE for good); TileCache gets the read-side barrier.
- Text from a repeater or sensor no longer rings on the device (nothing to
open); a settings switch reads any non-zero value as on; the message
toast never cuts a letter in half; ui-new's GPS helpers ask UiCore.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- set af / dutycycle / rxdelay reject nan.
- A TX power change from the app or CLI re-baselines APC.
- CMD_SET_RADIO_PARAMS stores repeat as 0/1.
- The alarm reschedules itself when the time zone changes.
- A late ACK for an earlier DM attempt marks it delivered.
- A new auto-advert interval counts from the last advert.
- Both UIs take the TX power range from radioctl, up to MAX_LORA_TX_POWER
(30 dBm on the GAT562 30S).
- The ping timeout comes from the airtime; 3-byte path hash mode pings with
4-byte trace hashes, and trace flags are right for width 4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pure move: each block now sits in its own header, included where its
code was, so the order of definitions is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up:
- The rows exist only where the build asks for them: Text size with
EINK_FONT_SCALE, Icon size with STATUS_ICON_SCALE. The stock 2.13", the
portrait builds and the L2 don't get them -- their screens are laid out
for one cell size. The text override also applies in landscape only.
- Stored in NodePrefs (ui_text_scale, ui_icon_scale) instead of a
/ui_scale file, as rows in the schema's Display section: the usual row,
save path and factory reset, and console keys display.text.size /
display.icon.size. The hand-written Settings rows and the load/save code
are gone; UITask::applyUiScale() applies the prefs at boot and on change
(applyDisplayPrefs). On opt-in builds ui_text_scale starts at the
build's EINK_FONT_SCALE (DataStore sanitize), so the row shows the scale
in use.
- EINK_FONT_SCALE, when set, now scales whichever landscape font the build
picks (the 6x9, or the EINK_LARGE_FONT one); unset, it is stock as before.
- setUiScale() skips the re-layout and full redraw when nothing changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Nodes: telemetry permissions per contact, reset path, share nearby
(L1 options menu; L2 node card with "Can ask for" switches)
- Settings > Privacy: fixed position (typed in, from a waypoint, cleared)
and the Bluetooth PIN; Settings > System: factory reset, asked twice
- Companion CLI (CMD_RUN_CLI_COMMAND 66 / RESP_CODE_CLI_REPLY 29, as
upstream; FIRMWARE_VER_CODE 14): upstream's command names plus a key for
every schema setting (get / set / keys)
- Screenshots move to 0x7E; tools/screenshot.py falls back to 66 / 29
- Your contact as a QR code (meshcore://contact/add): Status > System on
ui-new, Settings > About on the L2; Nayuki's qrcodegen made header-only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On an 800x480 panel the doubled 6x9 font and the status bar's icons are
tiny. Two opt-in build flags, each also a setting under Settings > Display:
- EINK_FONT_SCALE=<n>: the landscape font's pixel multiplier (stock 2).
Settings > Display > Text size (1x-5x) overrides it at run time
(DisplayDriver::setUiScale -> EinkGfxDisplay re-lays out).
- STATUS_ICON_SCALE=<n>: Home's status icons, battery and page-icon row
drawn at n x their pixel grid, the bar growing to fit. 0 = Auto, 1.5x
the text's pixel scale. Settings > Display > Icon size (Auto, 1x-8x).
Both live in their own /ui_scale file, so NodePrefs is untouched. Icon size
appears only where STATUS_ICON_SCALE is set; Text size appears on every
e-ink build with the full-refresh setting, and builds that set neither
flag draw as before until it is changed. The GDEQ0426T82 env defaults to
4x text with Auto icons.
Tested in the native sim at 800x480.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxAZVAbhhYr18Dd4guTQM1
While the loop task is inside a display refresh (seconds on a big e-ink
panel) the CardKB isn't polled, and the keyboard's own one-key buffer
drops every press but the last.
A small FreeRTOS task polls the CardKB every 10 ms while a refresh is in
progress, into a 16-entry queue that the loop drains afterwards. A mutex
guards the shared Wire1 bus against querySensors(). The task is created
after the screens so its stack doesn't starve their allocations, and the
loop falls back to the old direct polling if it can't be created.
Tested on hardware: Wio Tracker L1 + CardKB, several quick presses during
an 800x480 refresh all arrive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxAZVAbhhYr18Dd4guTQM1
- Nearby › Map (ui-new): the positioned nodes and you on one view, scaled
to fit, with north, a scale bar and the selected node's name and
distance; Left / Right picks a node, Up / Down zooms on it, Enter opens it.
- Clock tools › Calendar (ui-new): the month with today and the alarm's
days; a tight grid on small screens, Left / Right pages the months.
- L2 Settings: a Message banner switch (on / off) in place of ui-new's
three-way alert setting.
- Simulator: sim_nodes() puts eight positioned contacts around Kraków.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui-new's messagesInView() and ui-lvgl's threadInView() hold the
lit-and-unlocked test the viewing checks repeated; the keyboard's three
copies of the backspace call deleteBeforeCursor(). A stale comment on the
cursor-mode hint goes, onDisplayOn() sits after the view helpers it calls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A conversation left open under the lock screen or a dark display no longer
counts as read: the counters and the lock screen's tally go up, and on
waking (L2: on unlocking) the conversation reopens on its first unread
under the "New" line, a typed draft kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hashtag channel names already start with '#'; the tiles added another
("##test"). chanctl::bareName() is now the one place that strips it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contacts and paths are saved a while after the change and never while the
user is active, so a send no longer stutters on the 53 KB contact rewrite
(a path that came back unchanged isn't written at all). The clock is saved
every 30 minutes and restored no earlier than the firmware's build day.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both UIs: a channel, DM or room with unread messages opens on the oldest of
them (MessageHistory::firstUnreadChannel / firstUnreadDm, own messages not
counted), a "New" line above it, so it reads down to the newest. Nothing
unread: the newest, as before.
L1: the first unread is selected and scrolled up top with the line in view;
the channel's unread count now counts down as you read towards the newest.
L2: the page holding it, scrolled to the line; an incoming message scrolls
down only when the list is already at its end. Sim hooks: sim_unread (L1),
sim_open("unread") (L2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Display > Message alert: Normal keeps the sliding banner; Compact shows an
envelope with the unread count beside the battery (in the header line where
there's no status row), blinking for 2 s; Off shows nothing. The blink only
ever brings the next frame earlier, so it no longer holds back the UI's
animations while it runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The limit is what the send leaves of MAX_TEXT_LEN: a channel post carries
"<node name>: ", a direct message resent more than three times keeps two
bytes spare, and placeholders count as they expand (msgtext::limit/sentLen).
Bytes, not characters. The path and packet header sit outside the payload;
static_asserts keep a full message within the radio frame on the longest path.
From 40 bytes left the count shows: on the L2 as a tag on the field's top
edge, on OLED/e-ink at the right of the preview's last line with the text
wrapped short of it. Input stops at the limit; a send still over (a
placeholder grew) is held. Sim hooks to open a compose with text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/prefs holds a header (magic, format version, size, CRC-32), the node's
position and NodePrefs as it is in memory, in place of /new_prefs' two
hand-kept field lists and 0xC0DE00xx schema bumps. A new field goes at the
end of the struct; moving one bumps PREFS_VERSION. sanitize() keeps every
value in range.
The first boot after an update from before 2.0 imports only the first
block of the old file, laid out the same in upstream MeshCore and Solo
(name, position, radio, BLE PIN, scope), then deletes it; everything else
starts from the defaults. Contacts, channels, scopes, waypoints and trails
are untouched.
Gone: the page-order and GPS-interval migrations, the unused
trail_interval_idx / trail_units_idx / repeat_extra_scopes; fav_sort_off
and msg_wake_screen_off become fav_sort and msg_wake (on by default).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OLED gets the arrows towards nodes in Nodes and the Path row with its
hop rings in a stored node's detail (its position on one line to make the
room); no compass rose. Trail gets a height-profile view on both. The
Status history, trail profile and calendar come from ui-core. A sim hook
turns on the advert's position for tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landscape build's 8x13 digits stand 9 px, so its status, page and
check glyphs and the row-heading map markers come in a 7 px set instead
of 5. The battery outline, in the header and on the Status tile, leaves
its corner pixels out.
The page icons go back to a row of their own under the header: beside
them on the header line only three status icons fitted, and the rest
went missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tall screens stack the Sky view and the map over their rows, show more
recent chats, give favourite cards the heard age and distance, and put
the trail map under the trail summary. Wide screens put the page icons
on the header line.
E-ink only (Features::IS_EINK):
- a month calendar under the clock and on the portrait lock screen, with
the alarm's days marked; the lock screen also lists unread messages,
the alarm and the clock fields
- 8-hour charts in Status: battery, noise and SNR, mesh traffic
- an elevation profile under the trail map, with the climb and the top
- a compass rose and the path's hops on a contact's detail, arrows by
the distance in Nodes rows and on favourite cards
- an ordered-dither fade under chart lines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The L1 e-ink solo build splits in two, one per screen shape, so each
screen can be laid out for a single shape. Settings > Rotation now only
turns the screen upside down, and the joystick turns with it.
Landscape writes in misc-fixed 8x13 at the panel's own resolution
(EINK_LARGE_FONT); portrait keeps the 6x9. All drawing moves into
EinkGfxDisplay, which the panel driver and the simulator share; the
FreeSans fonts and the CRC32 library go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ui-core: Diagnostics::systemLines, NearbyModel::typeShort,
rptctl::currentPresetName, RoomSessions::pendingKey, settings::isSwitch,
UiCore::reconcileDMUnread.
- ui-new: the retired Recent page, fmtAlt, the battMvToPercent wrapper,
setFavouriteChannelSlot, AccordionList onHeader/visibleCount, drawBoxedIcon
and seven unused icons; info::row folded into info::valueRow.
- ui-lvgl: onOpenNav, TileDownloader::withTrails; the trail header size is
one constant (TRAIL_HDR) for live and saved trails.
- Home quick panels save prefs only when a value changed.
- Comments brought in line with the code (status bar reserve, settings rows,
page order, SH1106 frame skip, UiCore header).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The redraw, alert, splash and sensor timers compare millis() wrap-safe
(refreshDue(), alertShowing(); 0 = now / none), so the screen keeps
refreshing past the 49.7-day rollover.
- A failed send leaves the keyboard with its text ("Not sent" in the field
label, as alerts don't show over it) or the quick-message list with
"Send failed", instead of dropping to Home. No "Sent" echo.
- The dashboard's Sats field shows "--" with the GPS off, not a stale count.
- Brightness and volume in Settings stop at both ends instead of wrapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Home clock page draws HH:MM in the boot splash's slanted digits, with a
seconds bar, a centred date and the clock fields as columns. The lock screen
keeps the status bar, puts the big clock under it and shows unread messages
in a pill; the unlock hint appears for a few seconds after a key press on the
lit screen (not the waking one). Settings > Display > Lock clock adds a
compact look: a small clock with the date beside it and the clock fields as
rows (NodePrefs lock_compact, schema 0xC0DE0033). 12-hour time sets AM / PM
beside the digits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Battery curve: NodePrefs batt_curve_mv[11] (voltage at 0..100 %, all
zeros = built-in LiPo curve, sentinel 0xC0DE0032). ui-core/Battery.h
uses it for every percentage. L1: Settings > System > Batt curve list
(Left/Right 10 mV, Reset); L2: Settings > Power > Battery curve chart
with draggable points and a line at the cell's voltage.
- L2: the Nodes map is the full navigation map plus the nodes, Back
returns to Nodes; the favourite button is lit while it is one; pinning
to the Home dial uses a thumbtack (fonts regenerated); minimap dots
follow the accent; arranging tiles shows where they land while dragging;
emoji row in Diagnostics > Font.
- Sim hooks: sim_open nodesmap / accent@N / homeedit / battcurve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Icon pages become quick panels: two or three rows (Settings, Messages,
Bluetooth, Advert, Tools, Power) inset from the edges, the last one
opening the full screen. Brightness and auto-advert are edited in place
(Enter, Left/Right, Enter/Back saves once).
- Tools panel shows the two tools used last with their live state.
- Map page: preview on the left, GPS / trail / live / target on the right.
- Favourites as soft cards; Status tile with a battery spark line.
- Status bar without boxes or blinking; BT only when connected, GPS
search glyph without a fix.
- Redrawn mini icons (advert waves, cog, wrench, messages); big hovering
icons removed. Tools list is text only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home's Radio, GPS and Sensors pages become one Status page: four tiles split
by dotted rules (radio, GPS, battery, nodes), each a headline over a detail.
Enter opens the Status screen, tabs Radio / GPS / Sky / Power / Mesh /
System, which also takes over Tools > Diagnostics and Tools > Satellites
(the sky plot now lives in SkyView.h). GPS on/off moves to Enter on its tab.
New InfoKit.h holds the shared pieces, sized from the font: label/value row
(a long value takes its own line), centred section title, meter, chip, big
number with unit, switch, history line (one sample a minute) and a block
flow that scrolls a tab by whole blocks.
The Status page keeps the old Radio bit and shows when any of the three old
pages did; the save format is unchanged. The L1 degree sign was a cp437
byte the UTF-8 renderer drew as a box; now U+00B0. The perf walk covers
every Home page and Status tab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DisplayDriver gains slideBegin()/slideCompose(): the driver keeps the frame
on screen and, in the next one, moves it out while the new rows follow.
SH1106 does it on its 1 KB buffer (+1 KB RAM for the copy), the sim on its
canvas; drivers that can't read their frame back keep cutting over (e-ink).
Home slides the content below the page-icon row in 220 ms with an ease-out
(the whole screen to and from the header-less clock), and the current-page
pill glides between icons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Splash: the L2 wordmark and lettering, shared through ui-core/Lettering.h,
rise in with loading dots under the version line (static frame on e-ink).
- Home: the header shows the time once the clock is set; icon pages carry
only their title. One 32 px icon family (3 px strokes) bobs 2 px over a
dithered blob shadow that shrinks as it rises; the unread badge rides on
the Messages bubble; Bluetooth off is the icon at half density.
- Soft corners on every selection, the page pill, tabs and popups;
dotted scrollbar track with a rounded thumb; the tab bar lays out whole
tabs and shows edge arrows only in free space.
- DM bubbles drop the sender row and wrap to a second line, with the
delivery mark and age closing it.
- Keyboards name what is typed (channel, contact, Password, Scope...)
and show characters left near the limit; PIN entry has its own preview.
- Loading dots for waits (login, scan, GPS) and signal bars in place of
raw RSSI in Nearby.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DisplayDriver gets drawSoftRect / fillSoftRect / drawPanel (the unread badge's
corner knockout; a panel also clears a 1 px moat so lines below stop short of
its border). Used by alerts, popups, the accent picker, the lock hint, chat
bubbles and the send button.
Fixes: no "Msg:" toast for a message landing in the open thread; Nodes scan
text fits the screen; the PIN pad skips the blanks beside 0; Auto-Advert is a
plain value row; the Admin picker says what it needs when empty; the GPS page
shows "-" instead of 999 before a first fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opens each screen directly, types keys, and prints per step over USB: frames
drawn/sent, render and flush time (CPU cycle counter), heap used/free/largest
block, every task's stack headroom, and a screen dump. Measurement only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The contributor's feature -- a PIN asked before the screen unlocks, on
boot, and when the magnet cover opens, stored as a salted SHA-256 -- on
top of this branch, with:
- ui-core/ScreenLock.h: set / check / isSet and the tries limit, shared by
L1 and L2. isSet looks at every byte (the PR tested only the first, so one
PIN in 256 silently disabled the lock). 5 misses pause entry for 30 s.
- NodePrefs: the fields at the tail after quiet hours, sentinel 0xC0DE0031,
sizeof 2880; an older file's bytes there are cleared. A sentinel bump no
longer resets display_brightness_pct from a file that already has it.
- DataStore: no AGPL header (the project is MIT), no whitespace churn.
- Keyboard: the number pad is a widget flag (pin_mode) with a prompt in the
preview ("New PIN", "Again", "PIN"), not keyboard_type = 2 written into the
prefs and restored after.
- L1 Settings: "Lock PIN" right after the schema's "Lock screen" row, typed
twice, at least 4 characters, saved at once (a power-off right after
setting it kept no PIN). Home is told about a boot-time lock once it
exists (also fixes booting with the cover closed).
- L2: the screen PIN moves from plain text in NVS to the same hash in
NodePrefs; the old one is migrated on first boot and removed. The pad
unlocks as soon as the hash matches from 4 digits on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- L1's MessagesScreen sends through UiCore::sendDirectText / sendChannelText
(its own copy of send + file + ACK tracking gone).
- ui-core/Telemetry.h: the dashboard fields (numbers, names, formats) and
sensor readings as text, each frontend passing its Style; L2 Home, L1's
clock page, lock screen, sensors page and GPS altitude use it.
- L2: the side button held in the first 8 s is the CLI rescue, as on L1.
- L1's repeater radio goes through rptctl.
- L1 Settings are built from SettingsSchema: short labels for small
screens, SCHEMA_* placeholders expand to the schema's sections, one
generic row (switch ON/OFF, bars for brightness / volume). 20 hand-written
rows and their tables gone; L1 implements applyDisplayPrefs /
applySoundPrefs. settings::text / step / indexOf shared with L2.
- Schema: auto-off gains 5 s; the advert sound scope reads "Direct".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dead code gone: anim::fadeHide, radioctl::presetCount, the L2 board's
setSdPower / setGrovePower / expanderOK (awRead, gnssReset now private).
- buttonBar() / barButton() replace 7 copies of the popup button row.
- confirmBody(): one red-button confirm for reboot / power off, reset
counters, admin commands, preset and scope delete.
- tapConfirmed(): the 11 "tap again" buttons share one mechanism, 3 s
everywhere, the label restored when left alone.
- noteLabel() for ~30 wrapped notes.
- localTm() in NodePrefs.h and one MONTHS table: lvgl, MsgExpand, the L1
clock and lock screen.
- The sim keeps UI settings in an in-memory nvs::, so one set of load /
save helpers serves both.
- Asleep, the loop wakes every 50 ms (the button / touch poll), not 20.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > Sound > Quiet hours (L2: its own group; L1: Quiet hrs / from /
until): from quiet_from to quiet_to (local hours, wrapping past midnight)
the buzzer is muted and a message doesn't wake the screen; the clock alarm
still rings. A manual mute or unmute inside the window stands until the
window ends. Off with the clock unset.
NodePrefs gains quiet_hours / quiet_from / quiet_to (appended to the file,
sentinel 0xC0DE0030; sizeof 2832 on nRF52 and ESP32), off by default,
22:00-07:00. hourInWindow() / localHour() are shared with the bot's quiet
hours. soundctl::tick() replaces autoTick() (Auto and quiet hours).
Also fixes the L2 side-button mute: it went by the mode, not by whether
the buzzer was muted, so with Auto + an app connected (and now quiet hours)
the first press muted instead of unmuting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two views, swapped with left / right: a polar sky plot (filled = used in the
fix, hollow = tracked, a pixel = no signal) beside fix, used / in view, HDOP,
PDOP and time to first fix; and a signal bar per satellite (used first, a
dotted 30 dB-Hz line) under per-constellation counts. The GPS stays awake
while it's open. Data from GpsSky (-D GPS_SKYVIEW, now on the L1 solo build).
The simulator's made-up sky moves from the L2's GpsScreen into
helpers/sensors/GpsSkySim.h, so both sims show it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>