CMD_SET_CHANNEL clearing a slot (empty secret) left bot_channel_idx,
loc_share_channel_idx, and the per-channel melody bitmasks pointing at that
index. A new channel added later at the same slot would then silently
inherit the old one's bot target, Live Share target, or notification
melody. New onChannelRemoved() hook, mirroring onContactRemoved(), turns
the bot/Live Share channel target off (fail closed) and clears the melody
bits for that index.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
onContactOverwrite() (the contact-table-full LRU eviction path) deleted the
contact's blob and notified the companion app, but never called the new
onContactRemoved() cleanup -- so a Favourites Dial slot, Locator target, or
Live Share target could still go stale, just via the silent auto-evict path
instead of an explicit removal. This is likely the main real-world cause of
the "(gone)" tile the docs described, since auto-eviction happens far more
often than an explicit CMD_REMOVE_CONTACT.
Also sync the two docs that described the old (now wrong) behaviour:
Locator's "survives delete" claim, and Favourites Dial's "(gone) until
reassigned" claim.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deleting a waypoint left the Locator pointed at coordinates that no longer
existed (it's a coordinate snapshot, so nothing noticed). Removing a contact
was worse: nothing cleared its favourite slot, its Locator/Live Share target,
or its per-contact mute/melody entry, so all four kept referencing a pubkey
that no longer resolved to anything.
- WaypointsView's Delete now calls UITask::clearTargetIfWaypoint() first.
- New AbstractUITask::onContactRemoved() hook, called from MyMesh.cpp's
CMD_REMOVE_CONTACT handler, clears the favourite slot, the Locator target,
and dm_notif/dm_melody entries for that pubkey. Live Share's DM target
turns auto-share off instead of guessing a new recipient.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Log in to a room server from the device UI with no phone app: picking a
room prompts for its password (blank allowed for open rooms), and a
context-menu "Login..." allows re-login. Successful passwords are
persisted to a dedicated /room_pw file so a previously-used room logs
back in after reboot without retyping; a failed login forgets the
(now-stale) saved password so the next attempt prompts again.
The room-password file is written via a temp file + atomic rename
(new DataStore::commitFile helper), matching the crash-safety of
contacts/channels persistence.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Squash merge of feat/location-beacon-alerts-autopause (v1.21).
Features:
- Live Location Sharing — broadcast position as movement-gated [LOC]
messages to a channel or contact; live shares show as map pins with
distance/bearing in Nearby Nodes and a status-bar indicator. [LOC] is
parsed in DMs, channel messages and room messages; DM shares name the
sender.
- Locator (geofence) — arm a geofence around a saved waypoint or a person
(live [LOC] or last-known position), alert on arrive/leave or near/far,
with an optional homing beeper (gated to arrive/both modes). Arm from
Tools > Locator, Nearby Nodes, or Waypoints; target picker lists
favourites first, clearable via a "None" entry. Active target is drawn
as a flag on the map.
- One active target shared across Locator / Navigate / Map via a single
resolver (resolvePersonPos / activeTargetPos) that prefers a live
[LOC] share over the last-advertised GPS fix.
- Follow live contacts — Navigate to a live-sharing contact follows them
as they move and adds an ETA line; quick-share your own position from
the Map.
- Map & status-bar upgrades — home mini-map gets a north marker, scale
tick, and a connected trail line (was disconnected dots); status line
shows tracked-node count, an arrow + distance to the active Locator/Nav
target (falling back to the nearest live-tracked contact); GPS fix icon
in the top status bar, shown only on GPS boards with GPS enabled.
- Trail auto-pause — recording freezes on stops (banking elapsed time,
breaking the map line across the idle gap) and resumes on movement
without ending the session.
- Streaming trail simplification — GPS points are simplified in-stream
via a fixed-corridor (Reumann-Witkam) pass tuned for fidelity: straight
runs collapse to their endpoints, curves stay bounded to the Min-dist
tolerance, so the 512-point buffer covers a far longer route than a
flat point budget would suggest.
- Collapsible Tools (Location / Comms / System sections, fold-in-place
like Settings) and page-indicator icons on the home carousel.
- Waypoint coordinate editor — add a waypoint by scroll-editing lat/lon
digit by digit.
Fixes:
- Critical: low-heap hang and contact loss on RAM-tight builds. Halved
message-history scrollback rings (recovering ~14 KB free heap) and
made contacts/channels/prefs persistence atomic (temp file + rename),
so an interrupted save can no longer corrupt or wipe the store.
- Serial.write() bounded so a stalled USB host can't hang the device.
- Nearby Nodes: live [LOC] senders respect the type filter, sort by
shared position, and the list refreshes so live shares bubble up.
- Map: live contacts are labelled before waypoints.
- GPS status icon hidden when GPS is off in Settings.
- Splash screen no longer truncates a pre-release tag's own dash (e.g.
v1.21-rc1) when stripping the build's commit-hash suffix.
- Null-guarded the Locator target picker; clamped loc-share channel
index on load.
Under the hood:
- -Os size optimisation on the e-ink and GAT562 30S solo envs.
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>
UITask.onBLEDisconnected() sets _next_refresh=0 so the BLE icon
updates instantly instead of waiting for the next polling cycle.
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>
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>
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>
- Add DM_HIST phase in QuickMsgScreen: 32-entry ring buffer tracks incoming
and outgoing DMs per contact; selecting a contact now opens history view
before compose
- Per-contact DM unread badge in contact list (replaces hop count display)
- Bot replies (DM and channel) stored in history so they appear on-screen
- Fix contact index truncation: _sorted was uint8_t (max 255), causing
contacts at indices 256-349 to map to wrong entries; changed to uint16_t
- Add addDMMsg() virtual to AbstractUITask; wire through MyMesh.cpp
- Incoming chat DMs call addDMMsg(); outgoing after successful send too
- msgRead(0) from companion app clears per-contact unread table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- newMsg() now takes contact_type; room server messages (ADV_TYPE_ROOM)
tracked separately in _room_unread; DM badge = msgCount - roomUnread
- MODE_SELECT shows unread badges for all 3 rows: DM, Channels, Rooms
- Room unread cleared when user opens Room Servers list or msgRead(0)
- Channel context menu: ENTER performs action but stays open; only
CANCEL (Back button) closes it so user can see the updated state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add genericVibration class with 5-second cooldown and 1-second pulse
- Integrate vibration triggers for new messages and contact discoveries
- Add conditional compilation support with PIN_VIBRATION guard
- Implement abstract interface for vibration in UITask system