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>
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>
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>
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>
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().
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
- 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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>