Density from less chrome instead of smaller rows (ROW_H stays 44):
- titled screens drop the status bar; the title bar (APPBAR_H) holds back,
the title, the screen's actions and the time/battery on a pill; the status
bar returns for Home, the map, typing, the quick panel and the lock
- one radius (RADIUS) for everything tapped: buttons, tabs, choices, chips
- tabs are all segmented at TAB_H in the body (GPS, Clock converted)
- Nodes: chips row + sort + status line become one toolbar row with
Show / Order choices; Messages' All/Favourites is the same choice
- nav map: the target bar only while there is a target; the pin opens one
"Places" popup; back from a map options page restores the map and Map
tools as left, without a jump
- node detail actions at the top of the scroll; Home tiles fill the page
- rowsFit() leaves growing labels to the flex (a screen title cut to one
letter after Messages' filter changed)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Shared chanctl::order() builds the channel list for ui-new and the L2:
favourites first, then by ch_sort. New ch_sort pref at the end of NodePrefs,
Settings > Contacts row and contacts.channel.order CLI key.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Core: PathEditor (UiCore::route) loads, edits, traces and saves the route to
a contact; MyMesh::sendTrace over an explicit route with per-hop SNRs,
setContactPath / setContactPinned. A route can be kept
(CONTACT_FLAG_ROUTE_PINNED, ContactInfo::flags bit 4): a path heard from the
node doesn't replace it and the last DM resend doesn't drop it.
ui-lvgl: Route screen (node detail), repeater picker by distance, picking
hops by tapping the nodes map (numbered badges, line, Undo / Done). The
map's markers are one drawn layer with hit-testing and name decluttering
instead of three objects per node.
ui-new: Path / trace view in the Nodes action menu; Add reuses the list
filtered to repeaters.
First cut: built for L2 LVGL, L1 OLED and L1 e-ink, tried on L2 only.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
PREFS_VERSION 2: settings saved by 2.0-rc1..rc3 are not read, the device
starts from the defaults (v1.x /new_prefs is still imported as before).
The fields appended at the end during the rc cycle move into their
sections (BLE off, battery curve, text/icon size, message alert, advert
position precision, lock screen look). ch_scope_follow and its rc1
conversion and the unused use_lemon_font are removed. Stale comments in
NodePrefs.h and Trail.h fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- L2 vector packs: a damaged count can't wrap the index size, and an
entry pointing past the file is refused.
- L2 map share-target list: sized for 40 long channel names and 16
favourites (it could overflow), off the stack.
- L2 SD history: a file with an unreadable header is set aside as .bad
instead of written over; Delete message history takes those too.
- Contact lookups by key or hash start after the reserved anon slots (an
empty one matched a 00 hop hash with a blank name); MessagesScreen uses
contactctl::hopName().
- Nearby keeps its selection by public key: deleting a contact shifts
the table.
- ui-new Live Share target cycle holds every channel and favourite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Joining #topic drops surrounding spaces and keeps the case, as
meshcore-open's derivePskFromHashtag() does.
- /prefs written by a newer build (bigger NodePrefs, same version): the
extra bytes are read through the CRC and skipped instead of resetting
every setting.
- A channel post from someone named "Me" is filed as "\"Me\": ...", so
the views don't take it for our own.
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>
- Removing a scope moves the channels that used it to the default, not to
unscoped.
- The room bot ignores posts older than 5 minutes: the backlog a room
replays after login no longer gets answers.
- Adverts take the default scope from the scope list, as messages do; the
legacy fields always mirror it, and CMD_SET_DEFAULT_FLOOD_SCOPE with a
full list answers TABLE_FULL.
- The last DM resend drops the direct path and floods.
- /trail and /waypoints are written through a temp file.
- An admin login timeout keeps the saved password.
- History records read from the SD card are sanitised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MicroNMEA's valid flag survives a power cycle, so the duty cycle saw the
old fix right after waking the receiver and stopped it again a tick
later: with GPS power saving on the position never updated. begin() now
clears the parser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the phone not connected, every frame (each packet heard, each advert)
went to the USB port. With nobody holding it open the core either drops the
bytes or lets its buffer fill, and streamWriteUntil() then spun out its whole
timeout. Skip the write when the native USB Serial has no host.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Neither is published or used any more: the LVGL firmware on Arduino-ESP32
3.x is the only L2 build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contacts and rooms, the scope list, discovery and ping, APC and
screenshots move out of MyMesh.cpp to headers included at its bottom,
as MyMeshBot.h and MyMeshCli.h are, so upstream merges meet less of our
code. The feature matrix gains the 4.26" in its layout tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pure move: each section 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>
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>
WioTrackerL1Eink426_solo_dual extends eink_solo like the 4.2" build and
sets only what differs: the panel, rotation, the 9x15 at 2x with Text and
Icon size, bulk writes at 8 MHz, 250 contacts and a 32-message queue.
Replaces the full copy of the flags in solo/wio-tracker-l1-gdeq0426/.
Docs: advert precision, text and icon size, the 4.26" in the feature matrix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page break counted a space before every key, printing put none before
the first, so the skipped pages broke one key early and the next page
began with the previous page's last key.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 4.2" build's 9x15 font (EINK_LARGE_FONT=2) at 2x -- 18x30 cells,
~44 characters x 16 lines -- instead of the 6x9 at 4x, which goes blocky.
Compared side by side on the panel, as suggested in review.
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>
Settings > Privacy > Precision: Exact / ~100 m / ~500 m / ~1 km, beside
"Position in adverts", which stays the on/off. With the position shared,
it is snapped to a fixed grid (0.0009 / 0.0045 / 0.009 degrees) rather
than jittered, so repeated adverts from one spot send the same value and
can't be averaged back to the exact fix.
A schema row (NodePrefs::advert_loc_prec), so both UIs show it, factory
reset clears it and the console has it as advert.loc.precision.
Every self-advert -- manual, flood, auto, the phone app's and contact
export -- now goes through MyMesh::createOwnAdvert(), so the precision
applies everywhere.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setRamArea() and fastWrite() follow GxEPD2 1.6.2's private
_setPartialRamArea() and _writeImage() for the GDEQ0426T82; say so, so
whoever bumps the library knows to re-check them.
- 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>
- Settings schema gains Privacy (position in adverts, who can ask for
status / location / sensors), Adding contacts (all or by type, max
hops, replace oldest) and Advanced radio (path hash size, double ACK,
listen before talk, interference threshold, RX boost, RX delay,
airtime factor); both UIs build their rows from it
- Auto-advert carries position only when Position in adverts is on
- ui-new accordion holds 16 sections and 128 rows
- docs: settings, tools, feature matrix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Watch13 env still compiled with -Ofast and had run out of flash
after the map, calendar and QR commits. -Os brings it to 70.7%.
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
GxEPD2 sends the panel RAM a byte at a time; on the nRF52's SPIM the
per-call overhead dominates, and an 800x480 partial refresh writes the
whole 48 KB several times. GxEPD2_426_GDEQ0426T82_Fast overrides the three
image writes to send each row in one transfer() call, falling back to the
stock path for the cases it doesn't cover (first write, mirror_y, PROGMEM).
Byte-for-byte the same data as the stock driver.
Opt-in per env: -D EINK_FAST_WRITES=1, and EINK_SPI_HZ (default 4 MHz,
8 MHz here, SPIM2's ceiling).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxAZVAbhhYr18Dd4guTQM1
New solo env WioTrackerL1_GDEQ0426T82_companion_solo_dual: the Wio Tracker
L1 (Lite) with a Good Display GDEQ0426T82 (SSD1677) on the board's 24-pin
e-ink FPC. Build config only. Extends WioTrackerL1Eink with its own full
build_flags list so the stock panel's model and rotation never reach the
compiler.
RAM: the 800x480 framebuffer is 48 KB, so OFFLINE_QUEUE_SIZE is 32 and
MAX_CONTACTS 250 -- with the stock limits UiCore's heap allocations fail
at boot and it loops.
Tested on hardware: Wio Tracker L1 Lite + GDEQ0426T82.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxAZVAbhhYr18Dd4guTQM1
ui-new: Status › System, Enter shows it full screen. L2: Settings › About.
One precomputed 25 x 25 matrix (ui-core/SiteQr.h) for both, so no QR
library; the URL in upper case keeps it at version 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
The cursor marks the character it stands before instead of pushing the
text apart: underlined while typing, an inverted block while moving it.
The grid stays on screen with the special row turned into the key hints;
Up/Down move a word, Hold-Enter deletes before the cursor.
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>