Commit Graph
365 Commits
Author SHA1 Message Date
JakubandClaude Opus 5.5 608c5b7dad fix(ui-new): on/off written On / Off everywhere, as every other value
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:12:27 +02:00
JakubandClaude Opus 5.5 50d0d5577b fix(ui-new): a tool opened from Home's Tools page goes back to Home
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:09:22 +02:00
JakubandClaude Opus 5.5 3326881dc1 feat(eink): screens laid out for the 4.2" -- tiles, Nodes pane, charts, Sky, nav, clock tools
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:44:11 +02:00
JakubandClaude Opus 5.5 c3782cb8e5 feat(prefs): v2.0 settings file -- the struct as it is, behind a CRC
/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>
2026-10-03 00:12:18 +02:00
JakubandClaude Opus 5.5 99bd41650f feat(ui-new): e-ink graphics on the OLED, a Trail height view
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>
2026-10-02 23:26:29 +02:00
JakubandClaude Opus 5.5 e16fe2c621 feat(eink): 7 px icons in landscape, a rounded battery
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>
2026-10-02 20:39:26 +02:00
JakubandClaude Opus 5.5 6e6a0fb3ff feat(eink): use the room of tall screens, add e-ink graphics
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>
2026-10-02 19:24:42 +02:00
JakubandClaude Opus 5.5 b0e24bb076 feat(eink): landscape and portrait builds, 8x13 font in landscape
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>
2026-10-02 19:24:36 +02:00
JakubandClaude Opus 5.5 687a31e657 refactor(ui): remove dead code and fix stale comments after the review
- 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>
2026-10-02 13:49:26 +02:00
JakubandClaude Opus 5.5 096b8e2b1d feat(ui): drop echo toasts for actions the screen already shows
L1: Counters reset, Trail reset, Contact added/deleted, Logged in (admin),
Logged out, Channel deleted, Share target / Bot channel / Bot room set,
PIN set/cleared, Preset saved/deleted, and Unpinned on the favourites grid.
L2: Channel deleted, Counters reset, Region/Preset/Scope/Waypoint deleted,
Target cleared, Trail cleared, Message history deleted, Contact added/deleted.
Failures and Unpinned where the item leaves the screen stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:49:26 +02:00
JakubandClaude Opus 5.5 6cd081add1 fix(l1): wrap-safe UI timers, a failed send keeps the text, bars stop at the ends
- 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>
2026-10-02 13:49:16 +02:00
JakubandClaude Opus 5.5 6ae0e73663 feat(l1): clock page and lock screen in the splash lettering
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>
2026-10-02 11:58:56 +02:00
JakubandClaude Opus 5.5 61c75946f6 feat(l2,core): battery curve editor and an L2 feedback round
- 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>
2026-10-02 11:25:58 +02:00
JakubandClaude Opus 5.5 0f635c0171 feat(l1): Home quick panels, map page with live data, quieter status bar
- 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>
2026-10-02 10:23:58 +02:00
JakubandClaude Opus 5.5 4d8724dcc5 feat(l1): Status page and screen replace Radio/GPS/Sensors pages and Diagnostics
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>
2026-10-01 23:51:47 +02:00
JakubandClaude Opus 5.5 061e5ba36b feat(l1): Home pages slide sideways, the page pill glides along
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>
2026-10-01 23:08:40 +02:00
JakubandClaude Opus 5.5 608231ed6b feat(l1): L2 splash, hovering Home icons, soft selections, two-line DM bubbles
- 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>
2026-10-01 22:59:44 +02:00
JakubandClaude Opus 5.5 1580de53f5 feat(l1): one soft-cornered frame for popups, alerts and bubbles; small screen fixes
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>
2026-10-01 22:13:11 +02:00
JakubandClaude Opus 5.5 a6612ee2b0 perf(l1): -D UI_PERF_L1 walks the screens and reports draw time, heap and stacks
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>
2026-10-01 22:01:53 +02:00
JakubandClaude Opus 5.5 358f92222a Merge PR #37: lock screen PIN (3urobeat), adapted
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>
2026-09-29 00:06:01 +02:00
JakubandClaude Opus 5.5 b355922d32 feat(companion): core parity -- L1 sends via the core, shared telemetry, schema settings on L1
- 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>
2026-09-28 23:19:47 +02:00
JakubandClaude Opus 5.5 ece849db3b refactor(ui-lvgl): review -- shared button bar, confirms, notes, local time
- 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>
2026-09-28 22:35:26 +02:00
JakubandClaude Opus 5.5 1def29335c feat(companion): quiet hours -- muted on a schedule, the alarm still rings
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>
2026-09-28 21:59:40 +02:00
JakubandClaude Opus 5.5 95c4c093f0 feat(ui-new): Tools > Satellites -- sky plot and signal bars on the 128x64 OLED
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>
2026-09-28 16:33:41 +02:00
JakubandClaude Opus 5.5 f790631e03 refactor(ui-new,ui-lvgl): L1 sound mode and battery display through the Core, consistent admin toasts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 01:00:31 +02:00
JakubandClaude Opus 5.5 d588193d0d feat(ui-core,ui-lvgl): message placeholders and quick messages in the Core, L2 quick messages, advert, Bluetooth toggle, read all, node waypoint/pin
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 00:46:34 +02:00
JakubandClaude Opus 5.5 938dfefba6 feat(ui-core,ui-lvgl): sound in the Core, L2 speaker via ES8311, Sound settings, melody editor
- genericBuzzer I2S path (BUZZER_I2S): an audio task synthesises the notes
  with eased edges and keeps the DMA queue fed with silence between sounds;
  ES8311 setup; amp powered through the board only around sounds
- ui-core/SoundControl.h (mode, volume, built-in sounds, melody model),
  SoundNotifier moved to the Core, chanctl::melody, schema Sound page
- L2: notifications, alarm / timer ring, locator sounds, Settings > Sound,
  melody editor with the playing note lit, per-chat sound, mute icon
- noteIndex() on every buzzer backend; scrolling popups past screen height

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 00:27:52 +02:00
JakubandClaude Opus 5.5 2624ab0548 feat(ui-core,ui-lvgl): repeater control in the Core, L2 repeater mode
Min SNR now defaults to off on a fresh device (was 0 dB without a prefs file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 23:33:11 +02:00
JakubandClaude Opus 5.5 6f5fc07cd0 feat(ui-core,ui-lvgl): user presets and battery level in the Core, L2 my presets, scopes, battery display, clock seconds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 23:21:38 +02:00
JakubandClaude Opus 5.5 e2399b5e0d feat(ui-core,ui-lvgl): remote admin session in the Core, L2 admin screen
AdminSession (login with saved password, CLI round trips, timeouts, field
table, typed values) moves into ui-core; UiCore offers login answers and
CLI replies to it first. ui-new's Tools > Admin becomes a view over it.
L2: node detail > Admin with tabs and per-field popups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:59:00 +02:00
JakubandClaude Opus 5.5 79fd5a50fb feat(ui-core,ui-lvgl): channel management in the Core, L2 channel screens
ChannelControl (add Public/Hashtag/Private, rename, delete, notif/fav/scope)
and Favourites slots move into ui-core; ui-new uses them. Contact / channel
pref cleanup moves from ui-new's UITask into UiCore so it runs on every
frontend (L2 had none). L2: channel options popup (hold a row or the gear
in a channel) and the Add / Edit channel form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:42:44 +02:00
JakubandClaude Opus 5.5 1cc6a61747 feat(ui-core): waypoints, navigation helpers, settings schema, radio control
Shared by ui-new (L1) and ui-lvgl (L2) so both firmwares have the same core
features:
- WaypointModel (/waypoints, UTF-8-safe labels, [WAY] share text),
  EtaTracker, GpsAverager, TrackBack; TrailEngine save/load/reset actions;
  UiCore::setGpsEnabled / applyGpsInterval.
- Locator target kind 2: a channel live share followed by sender name
  (LiveTrackStore::activeByName).
- SettingsSchema: pages (navigation, display & power, messages & contacts),
  value tables over 1/2/4-byte NodePrefs fields, side effects via UiCore.
- RadioControl: apply params / TX power / APC and the preset list; ui-new's
  Settings > Radio now goes through it.
- NodePrefs::display_brightness_pct (sentinel 0xC0DE002F, size unchanged)
  for frontends with a brightness slider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:20:42 +02:00
JakubandClaude Opus 5.5 3e5ccd28c7 feat(ui-lvgl): Nearby screen, node detail, scan popup; NearbyModel in the Core
ui-core/NearbyModel.h builds the Nearby list (contacts + live [LOC] shares
+ heard adverts, or discover-scan results) with type filter and sort,
moved verbatim out of ui-new's NearbyScreen, which now inherits it.

ui-lvgl: Home is three tiles (Messages, Nearby, Settings). Nearby lists
known nodes with filter chips and a Dist/Recent toggle; tapping a row opens
node detail (distance/bearing, position, last heard, ID) with Message,
Ping, favourite, Add and two-tap Delete. Scan is a popup over the list
with its own model: nodes in range now, not the known set. Fonts gain the
FontAwesome star for favourites. The LVGL sim page adds an optional
repeater so Scan has something to find.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 00:53:23 +02:00
JakubandClaude Opus 5.5 cd926b7e45 feat(ui-lvgl): LVGL 9 skeleton frontend for Wio Tracker L2
New examples/companion_radio/ui-lvgl/ over the shared UI Core (env
Wio_Tracker_L2_companion_solo_lvgl, LVGL 9.2.2, pool in PSRAM):
status bar, home (clock, unread), conversation list (channels + recent
DMs with unread badges), contact picker, conversation view with bubbles,
delivery / relay markers and on-screen keyboard, toasts for Core events,
display auto-off with wake on touch / button. Board glue (LovyanGFX
flush + GT911 touch) isolated in LvglPort.h; design tokens in Theme.h.

UiCore gains the first frontend actions, sendDirectText() and
sendChannelText(), and drives DM auto-resend + unread reconcile from its
own loop (removed from ui-new's loop). MessageHistory exposes newest-first
DM ring iteration and a public contactByPrefix().

Verified on L2 hardware: navigation, send/receive, sleep/wake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:49:42 +02:00
JakubandClaude Opus 5.5 74db0d69a5 refactor(ui-core): UiCore becomes MyMesh's Listener for ui-new
ui-new hands MyMesh the UI Core (UITaskBase::meshListener()). The Core
applies the display filter, labels room posts, files history, keeps DM /
channel / room unread and the queue count, and reports incoming messages
and adverts as MessageArrived / AdvertHeard events. UITask now derives
UITaskBase + UiCoreHost: it answers "is this conversation on screen" and
keeps the selection after an insert, forwards not-yet-extracted parts
(room login / admin, bot device actions, removal cleanup, shutdown), and
turns events into alert / wake / sound, drained at the start and end of
its loop(). Mesh callbacks no longer touch the display or buzzer.

AbstractUITask is split into UITaskBase (lifecycle, link state, helpers)
and the Listener glue ui-orig / ui-tiny keep using unchanged. Screenshot
handler reaches ui_task directly instead of casting the Listener.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:28:58 +02:00
JakubandClaude Opus 5.5 124265b8be refactor(ui-core): move GPS trail sampling into TrailEngine
TrailStore, background sampling, auto-pause and the pre-shutdown
auto-save move from UITask to ui-core/TrailEngine.h. TrailScreen still
drives the store directly. The 512-point store now lives on the heap
with the rest of UiCore (static RAM -8 KB on Wio L1, same total).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:17:03 +02:00
JakubandClaude Opus 5.5 9e5ce72c86 refactor(ui-core): move locator into LocatorEngine
Active target (set/clear/resolve person position), geofence crossing
state machine and proximity beeper move from UITask to
ui-core/LocatorEngine.h, emitting LocatorCrossed / LocatorBeep events.
UiEvent gains a flag and a 24-byte text. tickCore() now runs at the end
of UITask::loop(), where the moved engines used to run, so a fresh GPS
wake re-seeds the locator before it evaluates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:15:18 +02:00
JakubandClaude Opus 5.5 74baaddd0c refactor(ui-core): move live location sharing into LiveShareEngine
Auto-[LOC] session (duration, movement/heartbeat gate, scoped send) and
the peers' LiveTrackStore + expiry move from UITask to
ui-core/LiveShareEngine.h. "Live share ended" becomes a
UiEventType::LiveShareEnded event; UITask keeps thin wrappers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:09:26 +02:00
JakubandClaude Opus 5.5 79a3a47ff1 refactor(ui-core): move position + course over ground into CourseEngine
COG ring sampling (every ~1 s from UiCore::loop), outlier rejection and
currentCourse/currentLocation move from UITask to
ui-core/CourseEngine.h. UITask keeps one-line wrappers; screens unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:07:32 +02:00
JakubandClaude Opus 5.5 311a3484e2 refactor(ui-core): move ping state into PingEngine
Single in-flight ping + last result move from UITask to
ui-core/PingEngine.h; MyMesh's ping callback routes to the engine
instead of casting the Listener back to UITask. UITask keeps thin
wrappers (and the "not supported" alert), NearbyScreen is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:05:39 +02:00
JakubandClaude Opus 5.5 dd826a447e refactor(ui-core): move clock tools into ClockEngine; add Core event queue
Alarm / countdown / ring logic moves from UITask to ui-core/ClockEngine.h
unchanged. New ui-core/UiEvents.h: fixed-size Core -> frontend event
queue; UITask::tickCore() runs UiCore::loop() and reacts to ClockAlert /
ClockRingEnded (wake, alert overlay, melody). ClockToolsScreen API unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:03:57 +02:00
JakubandClaude Opus 5.5 3b7aa28ff9 refactor(ui-core): add UI Core skeleton; move MessageHistory + DM unread into it
New examples/companion_radio/ui-core/ (header-only for now, reached from
ui-new/UITask.cpp by relative include, so no platformio.ini changes):
UiCore facade, MessageHistory (moved verbatim), DmUnreadTable (extracted
from UITask). UITask heap-allocates the Core in begin(); MessagesScreen
binds to core().history. Behaviour unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:00:46 +02:00
JakubandClaude Opus 5.5 2a8d4598d4 refactor: port upstream MyMesh::Listener; MyMesh no longer calls the UI directly
Ports upstream's "Abstract UI overhaul" (PR #3431 + follow-ups) with
identical names/signatures. MyMesh talks to the UI only through
MyMesh::Listener (setListener()); a second block holds Solo extensions
(own-send mirroring, relay echoes, room login/admin replies, [LOC] shares,
contact/channel removal, bot device actions, requestShutdown), all no-op
by default.

AbstractUITask becomes the Listener and carries the glue MyMesh used to
run for the UI (display filter, room-post labels, notifications,
connection polling). BLE PIN randomisation moves to main.cpp as upstream.
Also fixes ui-orig's stale advertReceived event on buzzer boards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 22:54:40 +02:00
JakubandClaude Opus 5.5 1620df6c10 fix: move FEAT_RX_POWERSAVE to Features.h; no extra [LOC] on "Stop after" change
- FEAT_RX_POWERSAVE lived in MyMesh.h, but SettingsScreen.h only pulls
  Features.h -- it saw the flag only via include order, and an undefined
  macro in `#if` silently reads as 0, which would split the build the day
  it's flipped to 1. Now defined in Features.h (the home of FEAT_* flags),
  which MyMesh.h includes; comment/doc pointers updated.
- Changing Live Share's "Stop after" restarted the whole session, forcing
  an immediate re-announce [LOC] although the position hadn't changed. It
  now restarts only the duration clock (restartLocShareClock()).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:50:37 +02:00
JakubandClaude Opus 5.5 299fcf2c13 fix(ui): correct DM unread overflow check and aggregate/scope edge cases
- DM unread: the ring-eviction check ran in newMsg(), which MyMesh calls
  before addDMMsg(), so the new message itself read as evicted -- the
  first DM from a contact showed no badge and every later one was one
  short with a false "+". Moved into addDMMsg() after the insert, and
  run for every slot, so a contact whose unread entry was evicted by
  someone else's message is flagged right away too.
- Channel overflow aggregate: ignore channels whose unread count is 0
  (eviction can zero it with the flag left set), so the dashboard no
  longer shows "0+ msgs" or a "+" borrowed from another conversation.
- Live Share scope: a stale index past the list's end now follows the
  target, as the screen shows, instead of silently sending unscoped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:46:18 +02:00
JakubandClaude Sonnet 5 81cee3846f feat(ui): flag unread counts understated by a full history ring
DM and channel history each live in a fixed-size ring (32 DM / 48
channel entries). A conversation that outpaces reading can fill its
ring, evicting the oldest entries — including unread ones that were
never opened — so the unread badge silently caps at ring capacity
instead of reflecting how many messages actually arrived.

MessageHistory now tracks a sticky per-channel overflow flag, set at
the exact point an unread channel entry gets evicted (mirroring the
existing unread-decrement logic) and cleared once the count reads back
down to 0. DMUnreadEntry gains the same bit for DM contacts, detected
by comparing the raw per-contact counter against actual ring occupancy
on each new message (the DM ring, unlike the channel ring, doesn't
proactively decrement on eviction).

DisplayDriver::drawUnreadBadge/unreadBadgeWidth/fmtBadgeCount take an
optional overflow flag (default false, existing call sites unchanged)
that appends "+" to the badge. Wired into every place an unread count
is shown: DM/favourites and channel lists, the DM/Channels/Rooms
mode-select screen, favourite tiles, and the aggregate "Msgs" field on
the clock and lock dashboards plus the Messages home-carousel page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 13:45:07 +02:00
JakubandClaude Sonnet 5 26e6bc877a fix(radio): disable Pwr save (RX duty-cycle) — unsafe across mixed firmware
Field report: a user on the stock SF8 "EU/UK (Narrow)" preset saw reception
drop from ~1-5 msg/min to ~1/3h with Pwr save on, unaffected by a better
antenna. Root cause, confirmed against the SX1262 datasheet and RadioLib's
own maintainers (jgromes/RadioLib#1597, closed as inherent chip behaviour):
the SX126x's duty-cycle preamble detection needs the sender's actual
preamble to closely match what we've configured our receiver to expect.
A mismatch — e.g. a repeater still on pre-v1.16 firmware sending 16 symbols
against our 32 — doesn't cost a little sensitivity, it silently drops every
packet from that sender regardless of signal strength. There's no local
parameter fix: shortening minSymbols to tolerate shorter preambles directly
shortens the wake-window's correlator dwell time, trading the preamble
mismatch failure mode for a marginal-signal one instead. Checked whether
IoTThinks' MeshCore fork solved this differently — it doesn't hit the
problem at all, because its "power saving" only light-sleeps the MCU on the
radio's own GPIO interrupt and never touches RX duty-cycle.

examples/companion_radio/MyMesh.h now defines FEAT_RX_POWERSAVE 0, gating
out the Settings row, the Diagnostics RXPS watchdog row, and every call site
that would apply rx_powersave to the radio or CAD auto-enable — including
forcing setPowerSaving(false) unconditionally so a stale rx_powersave=1 byte
from before this change can't do anything either. The real duty-cycle
implementation in RadioLibWrapper/CustomSX1262Wrapper is left in place,
unneutered, for if a network-wide compatibility mechanism (e.g. the
still-unused ADV_FEAT1/FEAT2 advert bits) is ever built to make it safe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 20:04:42 +02:00
JakubandClaude Sonnet 5 4af6d7d013 feat(live-share): own scope for [LOC] posts and time-limited sessions (#23, #24)
- Live Share gets a "Scope" row (Target / * / any scope from the shared
  list). It applies to [LOC] sends only, beats the app's send_scope and a
  channel's own pick, and never touches normal messages. Target keeps the
  previous behaviour. removeScope() fixes the stored index up.
- Auto share now always ends: "Stop after" 1/2/4/8/12 h (default 1 h, no
  unlimited option). On expiry it switches itself off and shows "Live share
  ended". The session clock is RAM-only, so a reboot starts a new session.
- NodePrefs: loc_share_scope + loc_share_duration_idx appended, sentinel
  0xC0DE002C -> 0xC0DE002E, sizeof unchanged (2824).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 14:22:27 +02:00
3urobeat 02726edcdc feat: Mask PIN input on lockscreen 2026-09-16 18:44:24 +02:00
JakubandClaude Opus 5 1d5dbe2f9a feat(companion): contact expiry + prune, and scope-list follow-up fixes
Settings > Contacts gains "Expire" (Off/7d/30d/90d) and a "Prune now" action
that counts first and asks before removing anything. A contact with no
advert/update within the threshold is eligible; favourites are always kept.
Thresholds and their labels live in one NodePrefs table so the age shown and
the age enforced can't drift. SCHEMA_SENTINEL -> 0xC0DE002C (sizeof unchanged
at 2824, confirmed on native and a real WioTrackerL1 build).

Also fixes four bugs in the scope list from 89c02eea:

- removeScope() saved /scopes1 but never savePrefs(), so the ch_scope_idx[]
  and repeat_extra_scope_mask fix-ups it makes were lost on reboot, leaving
  shifted entries against unshifted indices.
- CMD_SET_DEFAULT_FLOOD_SCOPE wrote the legacy fields directly instead of
  going through setPrimaryScope(), so the app's default-scope setting had
  nothing reading it once sends resolved through the list.
- The reverse direction was stale too: an on-device "Set as default" never
  refreshed default_scope_name/key, so CMD_GET_DEFAULT_FLOOD_SCOPE reported
  a scope the device had stopped using. New syncLegacyDefaultScope().
- Upgrading from the old single Scope field set the default (so DMs kept it)
  but left every channel unscoped, since "*" means unscoped, not "inherit".
  loadScopeList() now reports when it migrated and begin() seeds the channels
  that already exist, leaving empty slots alone.

"Set default" -> "Set as default", and setting it now says what it governs
("Default: DMs + relay") rather than leaving the [default] marker to imply
more than it does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 21:55:46 +02:00