Commit Graph

239 Commits

Author SHA1 Message Date
MarekZegare4
bd048d9e25 fix(ui): keep pre-release suffix in splash-screen firmware version
All three UI variants stripped the displayed version at the FIRST
dash, assuming it was always the commit-hash separator build.sh
appends. A tag containing its own dash (e.g. v1.21-rc1) has that
dash earlier, so the splash screen silently dropped "-rc1" and
showed just "v1.21" — no way to tell an RC from the final release
on-device.

Strip at the LAST dash instead, since build.sh always appends the
commit hash as the final segment regardless of how many dashes the
tag itself has.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:01:24 +02:00
MarekZegare4
0c46cc5212 fix(ui): show distance to active target on Home map status line
The MAP page status line only ever measured distance to the nearest
live-tracked ([LOC]-sharing) contact, ignoring the active Locator/Nav
target entirely. A waypoint target isn't a live-tracked contact at all, so
after "Set as target" on a waypoint its flag drew fine on the mini-map but
the status line below it never showed how far away it was.

statusDistanceKm() (renamed from nearestTrackedKm()) now checks
activeTargetPos() first — covers both waypoint and person targets — and
only falls back to the nearest live-tracked contact when no target is set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:02:32 +02:00
MarekZegare4
d2c9f2e19b fix(ui): connect mini-map trail points with a line
The Home "Map" page preview only plotted each trail vertex as a single
pixel, leaving gaps wherever the streaming simplifier collapsed a straight
stretch to its two endpoints — so a route looked like scattered dots instead
of a path. The stale "gfx isn't available this early in the TU" comment no
longer holds: GfxUtils.h is already reachable here, so draw the same
Bresenham line the full Trail map uses.

Extracted the shared "walk points, draw a line, break on TRAIL_FLAG_SEG_START"
loop into gfx::drawTrail() (GfxUtils.h) so the full map and the mini-map
preview don't carry two copies of it; they differ only in their projection
and what (if anything) they draw at a segment break.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:02:22 +02:00
MarekZegare4
49f790bf76 fix(locator): hide homing beeper in leave-only mode
The proximity beeper only fires inside the radius and ticks faster toward
the centre — a homing aid that only makes sense when "arrive" is part of the
alert mode. In pure "leave" mode it's contradictory (silent outside, slows
as you near the exit), so hide the Beeper row there and gate the beeper
engine on the mode, so a stale beeper=ON pref carried over from arrive mode
can't keep chirping after switching to leave.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:50:16 +02:00
MarekZegare4
c726b2bb35 fix(locator): add a way to clear the active target
setTarget() always set locator_has_target=1; there was no path back to
"none" once a target was picked. Add a synthetic "(none)" entry that's
always first in the target picker/cycle set, backed by new
UITask::clearTarget().
2026-06-24 22:52:19 +02:00
MarekZegare4
e5887f1d01 feat(companion): highlight the active target on the map
Setting a Locator/Nav target had no spatial feedback — you could arm a
destination but never see where it is. Draw it as a flag marker (ICON_MAP_TARGET)
on both the home Map preview and the full Trail map, folded into each view's
bounding box so a destination you set never sits off-frame, and drawn last so it
stays legible atop a coincident waypoint or contact.

activeTargetPos() resolves the target once (waypoint coords, or a person via
resolvePersonPos), gated only on a target being set — independent of whether the
Locator alert is enabled — so it doubles as the engine's resolver
(locatorDistance now delegates to it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 17:56:45 +02:00
MarekZegare4
83d139a62d refactor(companion): unify the active target across Locator/Nav entry points
Three notions of "set a target" had drifted apart: LocatorScreen's picker
hand-wrote the prefs fields and deferred the save, while the Nearby/Waypoints
popups wrote the same fields again and saved immediately, and the live>last-
advertised position precedence was duplicated between the engine and the picker.

- setTarget() is now the single definition of the active target (fields +
  re-arm). setTargetNow() wraps it for popups (immediate save + confirm); the
  Locator screen keeps its deferred save so LEFT/RIGHT cycling doesn't thrash
  flash. applyTarget() delegates instead of re-writing the fields.
- resolvePersonPos() is the single live-share / last-advertised precedence,
  used by both locatorDistance() and the picker.
- Consistent action verb "Set as target" in Nearby and Waypoints menus.

No behaviour change for the user beyond the relabel; pure consolidation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 17:34:53 +02:00
MarekZegare4
f481e3669f feat(ui): add a status-bar indicator for active Live Share
Live Share was the only "leave it on and forget" broadcast mode with no
at-a-glance cue, unlike auto-advert/trail/repeater which all blink in the top
bar. Reuses the diamond already used for a live-tracked contact on the map,
so the glyph already means "sharing position" elsewhere in the UI. Also
fixes the Tools-list "Live Share" row to use ICON_GPS instead of ICON_ADVERT
(which collided with the Auto-Advert row) — currently inert since per-row
icons are disabled there, but correct for whenever they're re-enabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 17:08:03 +02:00
MarekZegare4
b7e7e945d2 feat(companion): widen Locator targeting beyond favourites/live-share
Locator's person target now resolves any known contact's position, not just
an active live [LOC] share: locatorDistance() falls back to the contact's
last-advertised gps_lat/gps_lon when there's no current live share, so a
rarely-updating but stationary node (a repeater, or someone who shared a fix
once) still works as a target. LocatorScreen's target picker is widened to
match (favourites always offered, plus every other contact with a currently
resolvable position), and shows a compact age tag for non-live entries so
staleness is visible.

Also adds a quick "Set Locator target" action directly from Nearby Nodes' and
Waypoints' own popup menus (UITask::setLocatorTarget()), so picking a target
doesn't require a detour through Tools > Locator.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 16:18:39 +02:00
MarekZegare4
57f5346427 refactor: rename Geo Alert feature to Locator
Pure rename, no behavior change: GeoAlertScreen -> LocatorScreen (file +
class), NodePrefs geo_alert_* fields -> locator_*, UITask engine functions
(evaluateGeoAlert/fireGeoAlert/geoAlertDistance/geoProximityBeeper) and state
-> evaluateLocator/fireLocator/locatorDistance/locatorProximityBeeper, Tools
menu entry and screen header text -> "Locator", plus matching docs/README
updates. NodePrefs field renames don't affect on-disk layout (DataStore
serializes by position, not name).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:37:30 +02:00
MarekZegare4
3c94aca602 feat(ui): show nearest-tracked-contact distance as an arrow icon on the map status line
Replaces the bottom-left "->dist" text inside the map preview with a proper
arrow mini-icon, moved onto the status line alongside the tracked-node count
(e.g. "Track:3 ->120m"). The map's bottom-left corner now always shows the
scale tick instead of conditionally falling back to it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 15:37:57 +02:00
MarekZegare4
02cfb9da86 fix(ui): hide GPS status icon when GPS setting is disabled
The status-bar GPS icon appeared whenever the board had GPS hardware,
even if the user had turned GPS off in settings, implying a search
that wasn't actually happening.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 11:24:06 +02:00
MarekZegare4
5bb3111bb0 feat(ui): GPS fix icon on status bar, minimap distance label, hide Tools icons
- Status bar: add a boxed/plain GPS reticle icon reflecting LocationProvider
  fix state, shown only on boards that have a location provider. Drop the
  now-redundant "Fix:y/n" text from the map page's info line.
- Home minimap: replace the bottom-left scale bar with a "->dist" label to
  the nearest live-tracked point when one is available, falling back to a
  smaller single-tick scale indicator otherwise. Less likely to be covered
  by a plotted point than the old full-width bar, and more useful at a
  glance. Drop the now-duplicate nearest-distance text from the map page's
  info line.
- Tools screen: comment out per-row/section icons (kept for easy restore) —
  they don't fit visually in that list for now.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:37:46 +02:00
MarekZegare4
79ebad58ae feat(companion): parse [LOC] in room messages + wire roomMessage event
Two receive→UI wiring gaps of the same class as the room-history fix:

- onSignedMessageRecv (room posts) didn't parse [LOC] live-location shares,
  while DM (onMessageRecv) and channel (onChannelMessageRecv) both do — so a
  guest sharing position in a room never appeared on the map/nearby. Resolve
  the signed sender prefix to a name and track by name, mirroring the channel
  path (unverified: only a 4-byte prefix is available here).

- UIEventType::roomMessage was defined but never emitted; room notifications
  rode on contactMessage and always played the global DM melody. Emit
  roomMessage for room posts and handle it explicitly (default DM melody, no
  per-sender lookup since the author varies); drop the now-duplicate dead case.

Build verified: WioTrackerL1 solo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:28:37 +02:00
MarekZegare4
8adc313cc5 feat(home): page-indicator icons instead of dots
Replace the home carousel's page-indicator dots with a small per-page glyph
set (uniform 5x5, smaller than the menu/status icons), the current page
underlined rather than inverted (an inverted box read too much like the top
status bar). Icons follow buildVisibleOrder, so custom page order and
hide/show toggles from Settings carry over automatically; the row pitch
shrinks to fit when many pages are enabled, and the band geometry scales with
the font (identical layout to the old dots at 1x, clears header/content on 2x
e-ink).

Adds 13 ICON_PG_* glyphs (clock, star, recent, radio, bluetooth, advert,
gps, sensors, settings, map, tools, msg, power).

Builds verified: WioTrackerL1 + e-ink solo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:56:38 +02:00
MarekZegare4
b8760f7e41 feat(ui): collapsible Tools, shared AccordionList helper
The flat 10-item Tools list grew crowded, so tools are now grouped into
collapsible sections (Location / Comms / System) that fold in place — the
same model as Settings.

Extracted that fold-in-place behaviour into a reusable AccordionList helper
(owns the collapse mask, flattened visible-row list, selection and scroll;
host supplies section sizes + two row painters) and migrated SettingsScreen
onto it, dropping its hand-rolled buildVis/visIndexOf/sectionIndex and five
state fields. AccordionList joins drawList/DigitEditor/NavView as a shared
UI element used by two screens.

Added four mini-icons (bot, note, chart, gear) so every Tools row and
section header carries a glyph.

Builds verified: WioTrackerL1 + e-ink solo (Flash 61.0%).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 18:13:33 +02:00
MarekZegare4
f49b7dd8bd fix(live-share): name the sender in DM position shares
A channel [LOC] broadcast already carries the sender (sendGroupMessage
prepends "<name>: "), but the DM path sent a bare "[LOC]<lat>,<lon>" with
no name in it. Embed the node name as a trailing token after the
coordinate so a DM share is self-describing in any chat client;
parseLocShare ignores the trailing text, so receivers are unaffected.

Verified: WioTrackerL1_companion_solo_dual builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:30:44 +02:00
MarekZegare4
3ffdfd64b6 feat(map): north + scale on home mini-map; avoid overlapping labels
Home Map page mini-preview gains a north mini-icon and a fillRect scale
bar with a round-distance label (metric/imperial), and its status line now
shows the distance to the nearest tracked contact alongside fix/track.

Trail map labels no longer smear in dense clusters: each placed label
reserves a rect and a new one tries four offsets around its marker,
skipping (marker still shown) when none fit without overlap.

Verified: WioTrackerL1_companion_solo_dual builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:00:04 +02:00
MarekZegare4
16c634bd6e feat(companion): geo-alert beeper overrides buzzer mute
The proximity beeper is its own opt-in toggle, so enabling it is an
explicit "I want to hear this" — drop the isBuzzerQuiet() guard so it
ticks even when the global buzzer is muted (manual Off or BT auto-mute).
The arrive/leave crossing alert has no dedicated toggle and keeps
respecting the mute. Docs updated.

Verified: WioTrackerL1_companion_solo_dual builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 13:15:07 +02:00
MarekZegare4
dcc4379f63 feat(companion): follow live contacts — nav, geo-alert, ETA, quick-share
Builds on live location sharing:
- Nearby "Navigate" now follows a moving live target: the navigate view
  re-reads the shared position every second and re-locates the entry by
  name for non-contact senders, so it no longer freezes or drops out
  (previously only refreshed for stored contacts entered via detail).
- NavView gains a closing-speed / ETA line (EtaTracker), shown in the
  Nearby navigate view while approaching.
- Geo Alert target can be a live contact, not just a waypoint: the engine
  re-reads the contact's latest [LOC] position each evaluation (keyed by
  pubkey prefix) so the geofence follows a moving person; alert wording
  switches to Near/Away. The picker cycles waypoints + verified live
  contacts (shown with an '@' prefix). Schema bumped 0xC0DE0014 -> 0x15.
- Nearby marks a position-broadcasting node with the map's ♦ diamond
  (was a cryptic '*'/'~' prefix) and spells out "Sharing pos:" in detail.
- Home Map page Hold-Enter is a one-shot position share: pushes to the
  Live Share target while auto-sharing is on, else opens the picker.

Verified: WioTrackerL1_companion_solo_dual builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 13:09:51 +02:00
MarekZegare4
659ecec5a4 refactor(companion): tidy geo-alert / live-share; dedicated auto-pause gate
Review cleanup of the new location features:
- GeoAlertScreen + LiveShareScreen use the shared drawList() helper instead
  of hand-rolling the scroll/clamp/scrollbar maths, matching every other
  list screen.
- Extract UITask::geoAlertDistance() — one place for the target-distance
  maths, shared by the crossing evaluator and the proximity beeper.
- Geo Alert re-seeds its crossing engine after every edit, so changing the
  target/radius while armed can't fire a stale arrive/leave before exit.
- Trail auto-pause gets its own coarse movement gate
  (NodePrefs::TRAIL_AUTOPAUSE_MOVE_M = 15 m) instead of reusing the trail
  min-delta, so GPS jitter while parked doesn't keep the idle timer alive.
- Fix stale include comments.

Verified: WioTrackerL1_companion_solo_dual builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:38:59 +02:00
MarekZegare4
408dfd494b feat(companion): geo-alert geofence + trail auto-pause
Geo Alert (Tools › Geo Alert): a single geofence around a snapshotted
waypoint. Beeps + alerts on crossing into (arrive) / out of (leave) the
radius per mode, with edge hysteresis and silent first-eval seeding. Adds
a proximity Beeper that ticks faster the closer to the target while inside
the radius — independent of the discrete arrive/leave alert.

Trail auto-pause (Trail › Settings): freezes the trail timer + sampling
after the device sits still for a configurable delay, resuming on the next
real movement. New paused state in TrailStore (distinct from Stop); Summary
shows "paused".

Persisted via new NodePrefs fields; schema bumped 0xC0DE0012 → 0xC0DE0014
with clamps so upgraders start with both features off.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:45:00 +02:00
MarekZegare4
1d2f2beaba fix(companion): Live Share UX + map labels
- Live Share tool: add scrollbar, drop non-clickable section headers and
  the one-shot "Share now" (keep Auto-share toggle; manual share stays on
  the Map). Ellipsize the "To" target so long names don't wrap.
- Move "Track loc" (receive) from Settings into Live Share.
- Map: 2-char contact labels (distinguishable); drop "press" hint from the
  Home Map page status line so it fits on OLED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:23:22 +02:00
MarekZegare4
d78e74d4cd feat(companion): live location sharing via [LOC] messages
Share and track positions over the mesh through chat messages, separate
from (and parallel to) the 0-hop auto-advert.

Receive / tracking:
- geo::parseLocShare + [LOC] tag (sibling to [WAY]); LiveTrackStore (16
  slots, DM=pubkey/verified, channel=name/best-effort, 20-min expiry).
- MyMesh parses [LOC] on DM + channel receive → UITask live-track table.
- NearbyScreen overlays shares onto contacts (fresher pin) and lists
  non-contact senders; markers (*=DM, ~=chan) + "Loc:" age/verified line.

Send:
- Map menu "Share my pos" (one-shot) → Messages recipient picker.
- New Tools > Live Share tool: Receive (Track loc) + Send (Auto, Target,
  Move/Min gap/Heartbeat, Share now). Target chosen via the Messages
  picker (channel/DM). Movement-gated auto-send engine in UITask loop.

Map / navigation:
- Trail map shows tracked contacts as ◆ markers (ICON_MAP_CONTACT).
- Home "Map" page: mini-map preview + status line; ENTER opens the tool,
  Cancel returns Home.

Prefs: track_shared_loc + loc_share_* (NodePrefs schema 0xC0DE0012).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:13:09 +02:00
Jakub
32c50d1cfe feat(repeater): politeness controls, dedicated radio profile, and diagnostics
Settings > Radio gains a repeater toggle, 16 community-suggested radio
presets plus manual Freq/SF/BW/CR tuning (digit-by-digit Freq editor),
4 persisted user preset slots, and the packet pool bumped 16->32 so
queued retransmits stop starving incoming-packet allocation while
relaying.

Four opt-in politeness knobs (skip-advert, max-hops, yield, min-SNR),
all flood-only and off by default, plus overhear suppression that
cancels a queued retransmit if a peer relays the same packet first.
Adaptive Power Control is suppressed while relaying (pins TX power to
the ceiling) and duty-cycle RX is forced off, since a repeater needs
to hear and relay at consistent power.

A dedicated Tools > Repeater screen consolidates the toggle, the
politeness knobs, and live forwarding stats, plus an optional "Custom"
radio profile — a dedicated frequency/SF/BW/CR for relaying, separate
from the companion's own network, band-matched to the companion
frequency by default and used everywhere a radio change can happen
(boot, on-device toggle, app-driven CMD_SET_RADIO_PARAMS, Settings
radio edits) so the device never silently falls back to the wrong
params mid-relay.

Diagnostics gains the actually-forwarded packet count, real heap-free
via mallinfo(), a reset-counters popup, and hardened loop-detect
bounds. Also: a frequency-floor fix and a float-equality preset-match
fix in the repeater profile logic, deduped BW-table/valCol/profile-
seeding helpers shared between Settings and the Repeater screen, and
a build.sh fix tolerating control characters in `pio project config`'s
JSON dump.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 09:07:00 +02:00
Jakub
327e659d51 feat(ui): add on-device diagnostics screen (Tools > Diagnostics)
Shows packet counts by category (RX/TX), radio noise floor/RSSI/SNR,
packet-pool free count and outbound queue length, uptime, and heap/stack
headroom on a single scrollable screen.

Adds generic per-payload-type RX/TX counters to Dispatcher (mesh layer),
plus pool-free/queue-length getters, and a new DeviceDiag helper for
nRF52 heap (linker-symbol + sbrk) and stack (FreeRTOS high-water-mark)
stats — the only platform needed for the 3 in-scope boards (Wio Tracker
L1 OLED/Eink, GAT562 30S), all nRF52840.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:34:52 +02:00
Jakub
f591cddf4f refactor(ui): audit-pass fixes + dead-code/redundancy cleanup
Fixes from a full ui-new audit (OLED + e-ink both build green):
- remove dead DisplayDriver::drawScrollArrows (all lists use drawScrollIndicator)
- SettingsScreen selection bar -> lineStep()-1, matching the drawList screens
- HomeScreen: don't force the 1s blink refresh on the CLOCK page (status icons
  are hidden there anyway)
- FullscreenMsgView: clamp KEY_DOWN scroll to a cached _max_scroll instead of
  over-incrementing and leaning on the next render to clamp
- DataStore: clamp use_lemon_font (>1 -> 0) on load, like the other enum fields
- move the ~1.5KB wrap scratch (trans/lines) off the render stack into shared
  file-scope statics in FullscreenMsgView (single-threaded UI; the fullscreen
  view and history list never lay out in the same frame)

Cleanup:
- drop dead members RingtoneEditorScreen::DUR_VALS and HomeScreen::sensors_scroll
- delete redundant manual scroll-clamps in handleInput across the drawList
  screens (drawList already reclamps each render); remove the now-unused _visible
  from QuickMsgScreen/NearbyScreen/BotScreen and BotScreen::scrollToSel()

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 08:54:11 +02:00
Jakub
da161d49d2 fix(ui): message-history scrollbar reserve stuck behind alert overlay
The DM/channel history views size the scroll-column reserve from the previous
frame's visible count. A new message bumps the count so the reserve briefly
appears until the next frame settles it — normally invisible, but the alert
overlay froze the screen (_next_refresh = _alert_expiry) for the alert's whole
duration, leaving the content shifted as if a scrollbar were needed. Keep the
underlying screen refreshing at its own cadence (capped at the alert expiry);
the display CRC skips unchanged frames so e-ink isn't thrashed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:23:00 +02:00
Jakub
98af868e68 refactor(ui): dedup status-bar indicators
- Extract drawBoxedIcon()/drawSlotIcon() in icons.h; collapse the four
  near-identical mute/BT/advert/trail blocks in renderBatteryIndicator.
- Centre the glyph on the actual indicator box (box_h) instead of the text
  line, fixing the 1px vertical offset in Lemon mode.
- Extract blinkOn() for the shared advert/trail blink cadence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:17:34 +02:00
Jakub
353dee5878 feat(ui): status-bar mini-icons + settings fixes
- Replace single-letter M/B/A/G top-bar indicators with scalable mini-icons:
  ICON_MUTE (speaker+cross), ICON_BLUETOOTH (rune), ICON_ADVERT (broadcast
  mast+waves), ICON_TRAIL (map pin). Centred in the cw+2 indicator box;
  disconnected BT shows the plain glyph instead of lowercase b.
- fix(settings): right-side values used display.valCol() without the scrollbar
  reserve, so after expanding a section they rendered under the indicator.
  Route all value cursors through valCol(display) = display.valCol() - _reserve.
- fix(settings): font toggle used ^=1, which on a stale value of 2 (older Hybrid
  build) flips 2<->3 — both nonzero, locking applyFont() on Lemon with no way
  back. Normalise: use_lemon_font = use_lemon_font ? 0 : 1.
- docs(settings): document Messages > Resend setting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:45:28 +02:00
MarekZegare4
bcf8774962 feat(ui): message delivery status (DM ACK + channel relay)
End-to-end delivery indicators on the device UI, drawn next to outgoing
messages and auto-scaled to the font (legible on landscape e-ink).

DM (and room servers, which share the DM path):
- Pending / delivered / failed marker driven by the real end-to-end ACK.
  Pending shows a row of dots — one per send — so auto-resend progress is
  visible before it resolves to ✓ / ✗.
- Auto-resend: new pref dm_resend_count (0-5, default 2), Settings ›
  Messages › Resend. A pending DM whose ACK times out is re-sent (reusing
  the original timestamp) until resends run out, then ✗. Driven from
  UITask::loop so it completes in the background, independent of screen.
- Incoming dedup: a retry reuses the sender timestamp + text but carries a
  fresh packet hash, so addDMMsg drops copies matching prefix+ts+text.

Channels (flood, no recipient ACK):
- ✓ only once a repeater echo confirms the send was relayed into the mesh;
  no echo is normal, not a failure (no pending/fail shown). A small ring
  tracks a burst of sends so each matches its echo. Receive-path hashing is
  gated so the hot flood path is untouched when idle.

Shared:
- Markers shown in both the history list and the fullscreen message view.
- Reusable scalable mini-icon facility in icons.h (bitmap + auto-scale);
  adding a new status icon is a bitmap plus one draw call.

No changes to the upstream mesh library (src/).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 23:33:16 +02:00
MarekZegare4
151892e1ce refactor(ui): extract shared helpers, dedup screens, GPS shutdown via provider
Pull repeated UI idioms into DisplayDriver and remove duplicated logic:

- DisplayDriver: add drawScrollArrows() and drawInvertedHeader(); replaces 11
  copy-pasted scroll-arrow blocks across 5 screens and 3 inverted title-bar
  blocks (NearbyScreen x2, NavView). Standardises the detail-view separator.
- useImperial(): single source on UITask; NearbyScreen/TrailScreen delegate
  instead of each re-reading NodePrefs.units_imperial.
- NearbyScreen: extract saveSelectedWaypoint() (two identical blocks -> one);
  drop the redundant local label buffer (WaypointStore truncates).
- UITask::shutdown(): power GPS off through LocationProvider::stop() instead of
  poking PIN_GPS_EN directly — centralises enable/reset-pin + active-level logic.

Net -58 lines. Verified: GAT562 (SSD1306) and WioTrackerL1Eink (GxEPD) solo
builds compile clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 13:21:59 +02:00
MarekZegare4
fcdacaa3bc fix: GPS shutdown active-level, buzzer octave-8 consistency, waypoint header read checks
- UITask::shutdown(): power GPS off using !PIN_GPS_EN_ACTIVE instead of hardcoded
  LOW, matching MicroNMEALocationProvider::stop() (still LOW on current active-high
  devices, but correct for a future active-low GPS).
- buzzer _noteFreq(): clamp octave to 8 instead of 7 so the parser's accepted range
  (4-8) is fully consumed; previously an octave-8 digit was clamped away and could
  leak into the stream as the next note's duration.
- Waypoint::readFrom(): check read() return for version/reserved/count header bytes
  so a truncated file is rejected instead of using garbage count.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 12:49:15 +02:00
MarekZegare4
6c5a40a8ce fix(power): power off GPS before SYSTEMOFF to prevent battery drain on shutdown
NRF52 GPIO pins retain their last state in SYSTEMOFF. If GPS was active,
PIN_GPS_EN stays HIGH and the module keeps drawing ~20mA. gps_enabled is
already persisted to flash so applyGpsPrefs() restores the correct state
on next boot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 12:29:33 +02:00
MarekZegare4
5ec61477e6 feat(ui): simplify solo splash label, remove outdated WIOTRACKER_L1.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 09:46:45 +02:00
Jakub
5b58049139 feat(power): battery saving — hardware duty-cycle RX + adaptive TX power
Two independent, default-off toggles under Settings › Radio.

Pwr save: hardware RX duty-cycle (SX126x SetRxDutyCycle via
startReceiveDutyCycleAuto). The chip cycles RX↔sleep and wakes on a preamble —
no MCU state machine; recvRaw reads the packet exactly as in continuous RX.
Falls back to continuous RX on non-SX126x. (Replaces an earlier software-CAD
state machine that fought the hardware: polling a warm-sleeping chip gave a
phantom-busy channel that stalled TX ~4 s and dropped ACKs in the scan gaps.)

Auto pwr: Adaptive Power Control. tx_power_dbm becomes a ceiling; actual TX
power tracks the reverse-link SNR margin (measured above the per-SF demod floor,
EWMA-smoothed, proportional step with a deadband). Feedback comes from direct /
room-server ACKs and, for channels (no ACK), from hearing a repeater rebroadcast
our own flood; a lost confirmation ramps power back up so channel sends can't get
stranded below what the repeaters can hear.

Prefs schema 0xC0DE0009 (rx_powersave, tx_apc). Radio page / name bar show the
live TX power; noise floor reads n/a while duty-cycling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 08:15:42 +02:00
Jakub
2cc68fb987 fix(trail,batt): GPX NUL in stream + battery curve top at 4170 mV
- Trail.h gpxWaypoints/gpxPoint: snprintf truncation clamp was `> sizeof`
  (writing the NUL terminator into the XML stream); corrected to
  `>= sizeof - 1` at all 3 sites (<wpt>, <time>, <trkpt>).
- UITask.cpp battMvToPercent: raise 100% ceiling from 4200 → 4170 mV —
  the board never charges to 4200, so the indicator was stuck at ~97%.
- Trail.h header comment: corrected RAM cost from "256×12 B=3 KB" to
  actual CAPACITY(512)×16 B=8 KB.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 17:00:00 +02:00
Jakub
e6f8fb99c7 feat(NearbyScreen): waypoint/navigate from list, TIME filter, addWaypoint helper
NearbyScreen:
- Hold Enter on a list row now shows Navigate and Save waypoint directly,
  without entering the detail view first
- TIME added as the last filter slot (LEFT/RIGHT cycles Fav→ALL→…→Snsr→TIME);
  time-sort shows age in right column and sorts by last-heard descending
- Save waypoint also available from detail view Options menu

UITask:
- Extract addWaypoint() helper (two overloads: with/without explicit timestamp)
  consolidating the full()+add()+saveWaypoints()+alert pattern; TrailScreen
  and QuickMsgScreen updated to use it

Fixes:
- SoundNotifier.h: correct NodePrefs.h include path (../NodePrefs.h)
- kbAddSensorPlaceholders: dereference _kb pointer in SettingsScreen,
  QuickMsgScreen and BotScreen (signature changed to KeyboardWidget&)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 15:09:57 +02:00
Jakub
51ce15bbdc refactor: extract SoundNotifier helper from UITask::notify()
Move all buzzer/melody dispatch (DM, channel, advert) into a new
SoundNotifier class in SoundNotifier.h, isolating fork-specific sound
code so upstream changes to notify() no longer cause merge conflicts.
The shared playSlot() helper also removes the custom_played
duplication that existed across the three cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 21:51:03 +02:00
Jakub
ca9b3ff726 cleanup: remove dead UIEventType::advertReceived
The enum value and its case label were never emitted after PR #16 —
both dispatch sites in MyMesh.cpp now use advertReceivedFlood or
advertReceivedZeroHop exclusively.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:47:37 +02:00
Jakub
c339dc7d97 refactor(ui): share single KeyboardWidget instance across screens
Moves KeyboardWidget from a per-screen value member to a single instance
owned by UITask, passed to SettingsScreen, QuickMsgScreen, and BotScreen
as a pointer. Only one screen is ever active at a time, so the shared
state is safe. Saves ~1.5 KB of always-resident heap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:42:18 +02:00
vanous
8e1352b24a Settings - Sound: Add Advert scope 2026-06-07 10:53:17 +02:00
vanous
7703b3ae36 Settings - Sound: Allow to set items to None 2026-06-07 09:13:02 +02:00
Jakub
f5b3213f80 Merge upstream/main (v1.16.0) into wio-unified
Merge 254 upstream commits. Highlights relevant to the Wio fork: native
NRF52 companion power-saving (sleep when !hasPendingWork), preamble 16->32
for SF<9, generic sensor-registration model, CMD_SEND_RAW_PACKET, isEink()
display abstraction, KEEP_DISPLAY_ON_USB, contacts-sync/transient fixes.

Conflicts resolved (11 files):
- buzzer.h/.cpp: keep NRF52 include guard + our begin()->startup(); fold in
  upstream doc comment.
- platformio (wio + eink): keep our slim env layout, add upstream kiss_modem
  env (+ debug_tool=stlink on eink ble).
- GxEPDDisplay.h: take upstream isEink() override.
- EnvironmentSensorManager.h: take upstream's wider #if guard.
- DataStore.cpp: take upstream saveContacts(filter) signature, keep our
  ::openWrite (member openWrite would shadow the global 2-arg overload).
- main.cpp: keep our watchdog init + add board.onBootComplete(); adopt
  upstream's `if(!hasPendingWork()) sleep` loop.
- MyMesh.h: version -> v1.16-solo.0, build date 6 Jun 2026.
- MyMesh.cpp: keep both CMD codes (SCREENSHOT 66 + RAW_PACKET 65), both field
  inits, both handlers, our screenshot fns + upstream saveContacts/save_filter,
  upstream hasPendingWork() + our MyMeshBot include.
- UITask.cpp: keep our solo splash/clock page/snprintf/buzzer init; merge
  auto-off (+ opt-in KEEP_DISPLAY_ON_USB); merge low-batt shutdown (our EMA +
  prefs threshold + upstream's !isExternalPowered() guard and eink-aware delay).

Post-merge drift fixes (upstream API/architecture changes, not conflicts):
- applyTxPower(): radio_set_tx_power() removed upstream -> radio_driver.setTxPower().
- Sensor refactor dropped the per-sensor *_initialized flags our
  getAvailableLPPTypes() relied on; removed that override and the SENSORS page
  now derives available LPP types from the populated sensors_lpp buffer.

README kept ours via .gitattributes merge=ours. Builds verified on all 5 Wio
envs (dual, eink dual, radio_ble, dual_dev, eink_dual_dev). Not yet
hardware-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:21:41 +02:00
Jakub
cacc4e6206 fix(ui): keep hasConnection() BLE-specific; use isClientConnected() only for mute/wake
Self-review caught a regression I'd just introduced: feeding hasConnection()
from isClientConnected() (BLE *or* USB) broke the GPX-export collision warning
in TrailScreen, which relies on hasConnection() meaning "BLE app connected"
(BLE → USB dump is safe; otherwise the app may be on USB → warn).

Correct split:
- hasConnection() ← isBLEConnected() — BLE-specific consumers: BT status
  indicator, pairing PIN, GPX-export warning.
- isClientConnected() (BLE bonded OR open USB-CDC port) used directly only by
  the Auto buzzer mute and message-wake, which should react to either transport.

Builds clean on dual (OLED + e-ink) and pure-BLE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:42:14 +02:00
Jakub
3b795f7abc feat(ui): detect USB-CDC connection for Auto mute (BLE or USB)
USB client presence IS detectable on nRF52 after all: (bool)Serial ==
tud_cdc_n_connected() (DTR — a host has the CDC port open). Add
isClientConnected() = BLE-bonded OR USB-CDC-open; DualSerialInterface
overrides it, base defaults to isConnected() (single-transport unchanged).

Wire it so each consumer gets the right signal:
- hasConnection() ← isClientConnected(): Auto buzzer mute + message-wake now
  trigger on BLE or an open USB port (PR #14's intent, done correctly), but
  not on charging-only (no host → DTR low).
- BT status indicator + pairing PIN stay on isBLEConnected() (BLE-specific).

Caveat: a plain serial monitor also asserts DTR, so it counts as connected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:36:49 +02:00
Jakub
a6ae9e80c8 fix(ble): show pairing PIN on dual (BLE+USB) builds
The Bluetooth-page PIN prompt was gated on hasConnection(), which is fed by
the serial interface's isConnected(). DualSerialInterface::isConnected()
always returns true (USB always "connected"), so waiting_for_pair was always
false and the PIN was never drawn — on every dual build, i.e. the published
OLED + e-ink firmware. Pure-BLE builds were unaffected (there isConnected()
reflects bond state).

Gate the PIN on actual BLE-bonded state instead: add
AbstractUITask::isBLEConnected() (forwards to BaseSerialInterface::
isBLEConnected(), which DualSerialInterface overrides with the real BLE state
and pure-BLE inherits as its bond-aware isConnected()). The PIN now shows on
the Bluetooth page while BLE is on and not yet bonded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:03:38 +02:00
Jakub
2bbdb7fe6c Merge remote-tracking branch 'origin/wio-unified' into wio-unified
# Conflicts:
#	examples/companion_radio/DataStore.cpp
2026-06-04 17:13:49 +02:00
Jakub
75f9ccb34a feat(nav): share a waypoint as a message
Waypoints list → Hold Enter → Send hands the point to the Messages screen
as "[WAY]<lat>,<lon> <label>" (same text format geo::parseLatLon already
reads). The user picks a contact or channel, the text lands prefilled in
the keyboard to confirm/edit, then sends — closing the loop with the
Navigate / Save waypoint actions on the receiving end.

- QuickMsgScreen: share-compose mode (startShare/beginShareCompose). Picking
  a recipient jumps straight to the prefilled keyboard; cancel returns home;
  afterSend clears the mode.
- UITask::shareToMessage hands off from TrailScreen to the Messages screen.
- TrailScreen: "Send" added to the waypoint Rename/Delete popup; builds the
  [WAY] payload at the {loc} precision (5 dp).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:38:12 +02:00
vanous
0630e127d4 Add Advert sound and settings 2026-06-04 09:28:41 +02:00