feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
#pragma once
|
2026-06-02 23:51:08 +02:00
|
|
|
#include "../GeoUtils.h"
|
2026-06-03 08:32:32 +02:00
|
|
|
#include "NavView.h"
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// ── Nearby Nodes ──────────────────────────────────────────────────────────────
|
|
|
|
|
// One list / detail / action-menu interaction path over two sources:
|
|
|
|
|
// SRC_STORED — contacts known to the mesh (distance / bearing / last-heard)
|
|
|
|
|
// SRC_SCAN — live NODE_DISCOVER_REQ results (RSSI / SNR / status)
|
|
|
|
|
// Filter (type) and sort are independent axes and combine freely. The action
|
|
|
|
|
// menu (Hold Enter) is identical everywhere; only the per-row column and the
|
|
|
|
|
// detail fields differ between sources.
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
class NearbyScreen : public UIScreen {
|
|
|
|
|
UITask* _task;
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// ── filter (type axis) ──────────────────────────────────────────────────────
|
|
|
|
|
enum Filter : uint8_t { F_ALL, F_FAV, F_COMP, F_RPT, F_ROOM, F_SNSR, F_COUNT };
|
|
|
|
|
static const char* FILTER_LABELS[F_COUNT];
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// ── sort axis ───────────────────────────────────────────────────────────────
|
|
|
|
|
enum Sort : uint8_t { SORT_DIST, SORT_TIME };
|
|
|
|
|
|
|
|
|
|
// ── source ──────────────────────────────────────────────────────────────────
|
|
|
|
|
enum Source : uint8_t { SRC_STORED, SRC_SCAN };
|
|
|
|
|
|
|
|
|
|
// ── action-menu actions (matched by id, not by row index) ────────────────────
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
enum Action : uint8_t { ACT_NAV, ACT_PING, ACT_WAYPOINT, ACT_LOCATOR, ACT_SORT, ACT_SCAN };
|
2026-06-15 09:22:26 +02:00
|
|
|
|
|
|
|
|
// ── unified list entry ───────────────────────────────────────────────────────
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
struct Entry {
|
2026-05-14 16:29:04 +02:00
|
|
|
char name[32];
|
2026-06-15 09:22:26 +02:00
|
|
|
uint8_t type;
|
|
|
|
|
uint8_t pub_key[PUB_KEY_SIZE];
|
|
|
|
|
bool has_key;
|
|
|
|
|
// stored-source fields
|
2026-05-14 16:29:04 +02:00
|
|
|
int32_t lat_e6, lon_e6;
|
|
|
|
|
float dist_km;
|
2026-05-21 00:13:36 +02:00
|
|
|
uint32_t lastmod;
|
2026-06-15 09:22:26 +02:00
|
|
|
int contact_idx;
|
|
|
|
|
// scan-source fields
|
|
|
|
|
int8_t rssi, snr_x4, remote_snr_x4;
|
|
|
|
|
bool is_known;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
// live-track ([LOC] share) overlay: this row's position/age came from a
|
|
|
|
|
// shared-location message. live_verified == true for a DM (pubkey) share,
|
|
|
|
|
// false for a channel (name, best-effort) share.
|
|
|
|
|
bool is_live;
|
|
|
|
|
bool live_verified;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const int MAX_NEARBY = 32;
|
|
|
|
|
Entry _entries[MAX_NEARBY];
|
|
|
|
|
int _count;
|
|
|
|
|
int _sel;
|
|
|
|
|
int _scroll;
|
|
|
|
|
bool _detail;
|
2026-06-03 08:32:32 +02:00
|
|
|
bool _nav = false; // full-screen navigate-to-node view (over detail)
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
navview::EtaTracker _nav_eta; // closing-speed/ETA for the navigate view
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
int32_t _own_lat, _own_lon;
|
|
|
|
|
bool _own_gps;
|
2026-06-15 09:22:26 +02:00
|
|
|
|
|
|
|
|
Source _source;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
uint8_t _filter;
|
2026-06-15 09:22:26 +02:00
|
|
|
uint8_t _sort;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-05-14 16:29:04 +02:00
|
|
|
unsigned long _detail_refresh_ms;
|
2026-06-09 17:15:07 +02:00
|
|
|
unsigned long _list_refresh_ms = 0;
|
2026-06-15 09:22:26 +02:00
|
|
|
static const unsigned long DETAIL_REFRESH_MS = 10000UL;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
static const unsigned long NAV_REFRESH_MS = 1000UL; // navigate view tracks a moving target
|
2026-06-09 17:15:07 +02:00
|
|
|
static const unsigned long TIME_LIST_REFRESH_MS = 3000UL;
|
2026-05-14 16:29:04 +02:00
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// ── live-scan state ──────────────────────────────────────────────────────────
|
|
|
|
|
bool _scanning;
|
|
|
|
|
unsigned long _scan_started_ms;
|
|
|
|
|
static const unsigned long SCAN_DURATION_MS = 8000UL;
|
|
|
|
|
|
|
|
|
|
// ── popups ────────────────────────────────────────────────────────────────────
|
|
|
|
|
PopupMenu _menu; // unified action menu (Hold Enter), list + detail
|
|
|
|
|
PopupMenu _ping_menu; // ping (special: read-only result rows)
|
2026-05-29 21:49:48 +02:00
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
Action _menu_actions[8]; // parallel to _menu rows — stable action ids
|
|
|
|
|
int _menu_action_count;
|
|
|
|
|
char _sort_label[16]; // dynamic label for the Sort row
|
|
|
|
|
|
|
|
|
|
// ── ping state ───────────────────────────────────────────────────────────────
|
|
|
|
|
char _ping_time_str[24];
|
|
|
|
|
char _ping_snr_out_str[24];
|
|
|
|
|
char _ping_snr_back_str[24];
|
2026-05-29 21:49:48 +02:00
|
|
|
bool _pinging;
|
|
|
|
|
unsigned long _ping_started_ms;
|
|
|
|
|
static const unsigned long PING_TIMEOUT_MS = 3000UL;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-05-21 07:57:21 +02:00
|
|
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
2026-05-21 09:14:16 +02:00
|
|
|
static void pubKeyToBase64(const uint8_t* key, char* out, int out_len) {
|
|
|
|
|
static const char T[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
|
int j = 0;
|
|
|
|
|
for (int i = 0; i < PUB_KEY_SIZE && j + 5 < out_len; i += 3) {
|
|
|
|
|
uint32_t b = ((uint32_t)key[i] << 16)
|
|
|
|
|
| (i+1 < PUB_KEY_SIZE ? (uint32_t)key[i+1] << 8 : 0)
|
|
|
|
|
| (i+2 < PUB_KEY_SIZE ? (uint32_t)key[i+2] : 0);
|
|
|
|
|
out[j++] = T[(b >> 18) & 63];
|
|
|
|
|
out[j++] = T[(b >> 12) & 63];
|
|
|
|
|
if (i+1 < PUB_KEY_SIZE) out[j++] = T[(b >> 6) & 63];
|
|
|
|
|
if (i+2 < PUB_KEY_SIZE) out[j++] = T[b & 63];
|
|
|
|
|
}
|
|
|
|
|
out[j] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 13:21:59 +02:00
|
|
|
bool useImperial() const { return _task && _task->useImperial(); }
|
|
|
|
|
|
2026-06-29 17:43:17 +02:00
|
|
|
// Full "X ago" form for the detail view, built on the shared short-age tag
|
|
|
|
|
// so there's one bucket ladder (see geo::fmtAgeShort).
|
2026-05-14 16:29:04 +02:00
|
|
|
static void fmtAge(char* buf, int n, uint32_t lastmod) {
|
2026-06-29 17:43:17 +02:00
|
|
|
char s[8];
|
|
|
|
|
geo::fmtAgeShort(s, sizeof(s), rtc_clock.getCurrentTime(), lastmod);
|
|
|
|
|
if (!s[0]) { snprintf(buf, n, "unknown"); return; }
|
|
|
|
|
snprintf(buf, n, "%s ago", s);
|
2026-05-14 16:29:04 +02:00
|
|
|
}
|
|
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
static const char* typeName(uint8_t t) {
|
|
|
|
|
switch (t) {
|
2026-05-14 15:49:23 +02:00
|
|
|
case ADV_TYPE_CHAT: return "Companion";
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
case ADV_TYPE_REPEATER: return "Repeater";
|
|
|
|
|
case ADV_TYPE_ROOM: return "Room";
|
|
|
|
|
case ADV_TYPE_SENSOR: return "Sensor";
|
|
|
|
|
default: return "Unknown";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
static const char* typeShort(uint8_t t) {
|
|
|
|
|
switch (t) {
|
|
|
|
|
case ADV_TYPE_REPEATER: return "Rpt";
|
|
|
|
|
case ADV_TYPE_SENSOR: return "Snsr";
|
|
|
|
|
case ADV_TYPE_ROOM: return "Room";
|
|
|
|
|
case ADV_TYPE_CHAT: return "Comp";
|
|
|
|
|
default: return "?";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The selected entry, or nullptr when the list is empty.
|
|
|
|
|
const Entry* selected() const {
|
|
|
|
|
return (_count > 0 && _sel < _count) ? &_entries[_sel] : nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool typeMatchesFilter(uint8_t type, uint8_t flags, bool have_flags) const {
|
|
|
|
|
switch (_filter) {
|
|
|
|
|
case F_FAV: return have_flags && (flags & 0x01);
|
|
|
|
|
case F_COMP: return type == ADV_TYPE_CHAT;
|
|
|
|
|
case F_RPT: return type == ADV_TYPE_REPEATER;
|
|
|
|
|
case F_ROOM: return type == ADV_TYPE_ROOM;
|
|
|
|
|
case F_SNSR: return type == ADV_TYPE_SENSOR;
|
|
|
|
|
case F_ALL:
|
|
|
|
|
default: return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── data refresh ──────────────────────────────────────────────────────────────
|
|
|
|
|
void refreshStored() {
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
_count = 0;
|
|
|
|
|
_own_lat = _own_lon = 0;
|
2026-06-04 00:12:15 +02:00
|
|
|
_own_gps = _task->currentLocation(_own_lat, _own_lon);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
int nc = the_mesh.getNumContacts();
|
|
|
|
|
for (int i = 0; i < nc && _count < MAX_NEARBY; i++) {
|
|
|
|
|
ContactInfo ci;
|
|
|
|
|
if (!the_mesh.getContactByIdx(i, ci)) continue;
|
2026-06-15 09:22:26 +02:00
|
|
|
if (!typeMatchesFilter(ci.type, ci.flags, true)) continue;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
Entry& e = _entries[_count++];
|
|
|
|
|
strncpy(e.name, ci.name, sizeof(e.name) - 1);
|
|
|
|
|
e.name[sizeof(e.name) - 1] = '\0';
|
2026-06-15 09:22:26 +02:00
|
|
|
memcpy(e.pub_key, ci.id.pub_key, PUB_KEY_SIZE);
|
|
|
|
|
e.has_key = true;
|
2026-05-14 15:49:23 +02:00
|
|
|
e.lat_e6 = ci.gps_lat;
|
|
|
|
|
e.lon_e6 = ci.gps_lon;
|
|
|
|
|
bool remote_gps = (ci.gps_lat != 0 || ci.gps_lon != 0);
|
|
|
|
|
e.dist_km = (_own_gps && remote_gps)
|
2026-06-04 00:15:36 +02:00
|
|
|
? geo::haversineKm(_own_lat, _own_lon, ci.gps_lat, ci.gps_lon)
|
2026-05-14 15:49:23 +02:00
|
|
|
: -1.0f;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
e.type = ci.type;
|
|
|
|
|
e.contact_idx = i;
|
2026-05-14 16:29:04 +02:00
|
|
|
e.lastmod = ci.lastmod;
|
2026-06-15 09:22:26 +02:00
|
|
|
e.is_known = true;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
e.is_live = false;
|
|
|
|
|
e.live_verified = false;
|
2026-05-21 00:13:36 +02:00
|
|
|
}
|
|
|
|
|
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
mergeLiveTrack();
|
2026-06-15 09:22:26 +02:00
|
|
|
sortStored();
|
|
|
|
|
clampSelection();
|
|
|
|
|
}
|
|
|
|
|
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
// Rebuild the stored list (re-merge live shares + re-sort) while keeping the
|
|
|
|
|
// currently highlighted node selected across the rebuild — by contact index
|
|
|
|
|
// for a stored node, or by name for a non-contact live sender. Returns false
|
|
|
|
|
// if that node is no longer in the list (caller decides whether to drop the
|
|
|
|
|
// detail/nav view). Shared by the list, detail and navigate refresh paths.
|
|
|
|
|
bool refreshKeepingSelection() {
|
|
|
|
|
int saved_idx = (_sel < _count) ? _entries[_sel].contact_idx : -1;
|
|
|
|
|
bool saved_live = (_sel < _count) && _entries[_sel].is_live && saved_idx < 0;
|
|
|
|
|
char saved_name[sizeof(_entries[0].name)]; saved_name[0] = '\0';
|
|
|
|
|
if (_sel < _count) {
|
|
|
|
|
strncpy(saved_name, _entries[_sel].name, sizeof(saved_name) - 1);
|
|
|
|
|
saved_name[sizeof(saved_name) - 1] = '\0';
|
|
|
|
|
}
|
|
|
|
|
refreshStored();
|
|
|
|
|
if (saved_idx >= 0) {
|
|
|
|
|
for (int i = 0; i < _count; i++)
|
|
|
|
|
if (_entries[i].contact_idx == saved_idx) { _sel = i; return true; }
|
|
|
|
|
} else if (saved_live && saved_name[0]) {
|
|
|
|
|
for (int i = 0; i < _count; i++)
|
|
|
|
|
if (_entries[i].is_live && strncmp(_entries[i].name, saved_name, sizeof(saved_name) - 1) == 0)
|
|
|
|
|
{ _sel = i; return true; }
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Overlay live [LOC] shares onto the stored list: refresh a matching contact
|
|
|
|
|
// with the fresher shared position, and append senders who aren't contacts so
|
|
|
|
|
// a group sharing on a channel still shows up. DM shares match by pubkey
|
|
|
|
|
// prefix (verified); channel shares match by name (best-effort).
|
|
|
|
|
void mergeLiveTrack() {
|
|
|
|
|
if (!_task) return;
|
|
|
|
|
LiveTrackStore& lt = _task->liveTrack();
|
|
|
|
|
uint32_t now = rtc_clock.getCurrentTime();
|
|
|
|
|
for (int i = 0; i < LiveTrackStore::CAPACITY; i++) {
|
|
|
|
|
if (!lt.isActive(i, now)) continue;
|
|
|
|
|
const LiveTrackStore::Entry& s = lt.slotAt(i);
|
|
|
|
|
|
|
|
|
|
int m = -1;
|
|
|
|
|
for (int j = 0; j < _count; j++) {
|
|
|
|
|
if (s.verified && _entries[j].has_key
|
|
|
|
|
&& memcmp(_entries[j].pub_key, s.key, LiveTrackStore::KEY_LEN) == 0) { m = j; break; }
|
|
|
|
|
if (!s.verified && strncmp(_entries[j].name, s.name, sizeof(_entries[j].name) - 1) == 0) { m = j; break; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m >= 0) {
|
|
|
|
|
Entry& e = _entries[m];
|
|
|
|
|
// A [LOC] share is an explicit "here I am now", so it defines the pin and
|
|
|
|
|
// the distance (used for proximity sorting). Recency for the time sort is
|
|
|
|
|
// the most recent of the advert and the share.
|
|
|
|
|
e.lat_e6 = s.lat_1e6;
|
|
|
|
|
e.lon_e6 = s.lon_1e6;
|
|
|
|
|
e.dist_km = _own_gps ? geo::haversineKm(_own_lat, _own_lon, s.lat_1e6, s.lon_1e6) : -1.0f;
|
|
|
|
|
if (s.ts > e.lastmod) e.lastmod = s.ts;
|
|
|
|
|
e.is_live = true;
|
|
|
|
|
e.live_verified = s.verified;
|
|
|
|
|
} else if (_count < MAX_NEARBY && typeMatchesFilter(ADV_TYPE_CHAT, 0, false)) {
|
|
|
|
|
// A sender we don't have as a contact: treat it as a companion (the only
|
|
|
|
|
// node type that shares position), so the type filter still applies —
|
|
|
|
|
// e.g. it must not appear under the Repeater/Room/Sensor filters.
|
|
|
|
|
Entry& e = _entries[_count++];
|
|
|
|
|
memset(&e, 0, sizeof(e));
|
|
|
|
|
strncpy(e.name, s.name, sizeof(e.name) - 1);
|
|
|
|
|
e.name[sizeof(e.name) - 1] = '\0';
|
|
|
|
|
// We only keep a key *prefix* for shares, not the full pubkey, so Ping
|
|
|
|
|
// and the base64 key view (which need 32 bytes) stay unavailable for a
|
|
|
|
|
// non-contact live entry. Navigate / Save-waypoint work off lat/lon.
|
|
|
|
|
e.has_key = false;
|
|
|
|
|
e.type = ADV_TYPE_CHAT;
|
|
|
|
|
e.lat_e6 = s.lat_1e6;
|
|
|
|
|
e.lon_e6 = s.lon_1e6;
|
|
|
|
|
e.dist_km = _own_gps ? geo::haversineKm(_own_lat, _own_lon, s.lat_1e6, s.lon_1e6) : -1.0f;
|
|
|
|
|
e.lastmod = s.ts;
|
|
|
|
|
e.contact_idx = -1;
|
|
|
|
|
e.is_known = false;
|
|
|
|
|
e.is_live = true;
|
|
|
|
|
e.live_verified = s.verified;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
void sortStored() {
|
|
|
|
|
uint32_t now_ts = rtc_clock.getCurrentTime();
|
2026-05-14 15:49:23 +02:00
|
|
|
for (int i = 0; i < _count - 1; i++) {
|
|
|
|
|
int best = i;
|
|
|
|
|
for (int j = i + 1; j < _count; j++) {
|
2026-06-15 09:22:26 +02:00
|
|
|
if (_sort == SORT_TIME) {
|
|
|
|
|
// lastmod=0 or lastmod>now (RTC not synced) → "unknown" → sort to bottom.
|
|
|
|
|
uint32_t tj = (_entries[j].lastmod > 0 && now_ts >= _entries[j].lastmod) ? _entries[j].lastmod : 0;
|
|
|
|
|
uint32_t tb = (_entries[best].lastmod > 0 && now_ts >= _entries[best].lastmod) ? _entries[best].lastmod : 0;
|
2026-06-09 15:09:57 +02:00
|
|
|
if (tj > 0 && (tb == 0 || tj > tb)) best = j; // descending — most recent first
|
|
|
|
|
} else {
|
|
|
|
|
float dj = _entries[j].dist_km, db = _entries[best].dist_km;
|
|
|
|
|
if (dj >= 0.0f && (db < 0.0f || dj < db)) best = j; // ascending — closest first
|
|
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
2026-05-14 15:49:23 +02:00
|
|
|
if (best != i) { Entry tmp = _entries[i]; _entries[i] = _entries[best]; _entries[best] = tmp; }
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
2026-06-15 09:22:26 +02:00
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
void refreshScan() {
|
|
|
|
|
static DiscoverResult dr[DISCOVER_RESULTS_MAX]; // scratch — refresh isn't reentrant
|
|
|
|
|
int n = the_mesh.getDiscoverResults(dr, DISCOVER_RESULTS_MAX);
|
|
|
|
|
_count = 0;
|
|
|
|
|
for (int i = 0; i < n && _count < MAX_NEARBY; i++) {
|
|
|
|
|
if (!typeMatchesFilter(dr[i].type, 0, false)) continue;
|
|
|
|
|
Entry& e = _entries[_count++];
|
|
|
|
|
strncpy(e.name, dr[i].name, sizeof(e.name) - 1);
|
|
|
|
|
e.name[sizeof(e.name) - 1] = '\0';
|
|
|
|
|
e.type = dr[i].type;
|
|
|
|
|
memcpy(e.pub_key, dr[i].pub_key, PUB_KEY_SIZE);
|
|
|
|
|
e.has_key = true;
|
|
|
|
|
e.rssi = dr[i].rssi;
|
|
|
|
|
e.snr_x4 = dr[i].snr_x4;
|
|
|
|
|
e.remote_snr_x4 = dr[i].remote_snr_x4;
|
|
|
|
|
e.is_known = dr[i].is_known;
|
|
|
|
|
e.lat_e6 = e.lon_e6 = 0;
|
|
|
|
|
e.dist_km = -1.0f;
|
|
|
|
|
e.lastmod = 0;
|
|
|
|
|
e.contact_idx = -1;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
e.is_live = false;
|
|
|
|
|
e.live_verified = false;
|
2026-06-15 09:22:26 +02:00
|
|
|
}
|
|
|
|
|
// strongest first
|
|
|
|
|
for (int i = 0; i < _count - 1; i++) {
|
|
|
|
|
int best = i;
|
|
|
|
|
for (int j = i + 1; j < _count; j++)
|
|
|
|
|
if (_entries[j].rssi > _entries[best].rssi) best = j;
|
|
|
|
|
if (best != i) { Entry tmp = _entries[i]; _entries[i] = _entries[best]; _entries[best] = tmp; }
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
2026-06-15 09:22:26 +02:00
|
|
|
clampSelection();
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
void refresh() { if (_source == SRC_SCAN) refreshScan(); else refreshStored(); }
|
|
|
|
|
|
|
|
|
|
void clampSelection() {
|
|
|
|
|
if (_count == 0) { _sel = _scroll = 0; }
|
|
|
|
|
else if (_sel >= _count) { _sel = _count - 1; if (_scroll > _sel) _scroll = _sel; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── live scan ────────────────────────────────────────────────────────────────
|
|
|
|
|
void enterScan() {
|
|
|
|
|
_source = SRC_SCAN;
|
|
|
|
|
_detail = false;
|
|
|
|
|
_nav = false;
|
|
|
|
|
_scanning = true;
|
|
|
|
|
_scan_started_ms = millis();
|
|
|
|
|
_sel = _scroll = 0;
|
2026-05-21 07:57:21 +02:00
|
|
|
the_mesh.sendNodeDiscoverReq();
|
2026-06-15 09:22:26 +02:00
|
|
|
refreshScan();
|
2026-05-21 07:57:21 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
void leaveScan() {
|
|
|
|
|
_source = SRC_STORED;
|
|
|
|
|
_detail = false;
|
|
|
|
|
_nav = false;
|
|
|
|
|
_sel = _scroll = 0;
|
|
|
|
|
refreshStored();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save the currently-selected entry as a waypoint (its name as label).
|
|
|
|
|
void saveSelectedWaypoint() {
|
|
|
|
|
const Entry* e = selected();
|
|
|
|
|
if (!e) return;
|
|
|
|
|
if (e->lat_e6 == 0 && e->lon_e6 == 0) { _task->showAlert("No node GPS", 1000); return; }
|
|
|
|
|
_task->addWaypoint(e->lat_e6, e->lon_e6, e->name); // WaypointStore truncates the label
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── ping ──────────────────────────────────────────────────────────────────────
|
2026-05-29 21:49:48 +02:00
|
|
|
void resetPingLines() {
|
|
|
|
|
_ping_time_str[0] = '\0';
|
|
|
|
|
_ping_snr_out_str[0] = '\0';
|
|
|
|
|
_ping_snr_back_str[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 14:00:16 +02:00
|
|
|
int pingRowCount() const {
|
|
|
|
|
return 1 + (_ping_time_str[0] ? 1 : 0)
|
|
|
|
|
+ (_ping_snr_out_str[0] ? 1 : 0)
|
|
|
|
|
+ (_ping_snr_back_str[0] ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void rebuildPingMenu() {
|
2026-06-14 13:37:03 +02:00
|
|
|
int keep = _ping_menu.selectedIndex(); // preserve selection across a rebuild
|
2026-05-29 21:49:48 +02:00
|
|
|
_ping_menu.begin("Ping", 4);
|
2026-05-30 23:35:07 +02:00
|
|
|
_ping_menu.addItem("Send");
|
2026-06-15 09:22:26 +02:00
|
|
|
if (_ping_time_str[0]) _ping_menu.addItem(_ping_time_str);
|
|
|
|
|
if (_ping_snr_out_str[0]) _ping_menu.addItem(_ping_snr_out_str);
|
2026-06-03 14:00:16 +02:00
|
|
|
if (_ping_snr_back_str[0]) _ping_menu.addItem(_ping_snr_back_str);
|
2026-06-14 13:37:03 +02:00
|
|
|
_ping_menu.setSelected(keep);
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void closePingMenu(bool clear_task = true) {
|
|
|
|
|
_ping_menu.active = false;
|
|
|
|
|
_pinging = false;
|
|
|
|
|
if (clear_task && _task) _task->clearPing();
|
|
|
|
|
resetPingLines();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void startPingForKey(const uint8_t* pub_key) {
|
|
|
|
|
resetPingLines();
|
2026-05-30 23:35:07 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: ...");
|
2026-05-29 21:49:48 +02:00
|
|
|
if (_task && _task->startPing(pub_key)) {
|
|
|
|
|
_pinging = true;
|
|
|
|
|
_ping_started_ms = millis();
|
|
|
|
|
} else {
|
2026-06-03 14:11:03 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: send fail");
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
2026-06-03 14:11:03 +02:00
|
|
|
if (_ping_menu.active) rebuildPingMenu(); // surface "RTT: ..." immediately
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void updatePingMenuState() {
|
|
|
|
|
if (!_ping_menu.active || !_task || (!_pinging && !_task->isPingActive())) return;
|
|
|
|
|
|
|
|
|
|
int16_t snr_out = 0, snr_back = 0;
|
|
|
|
|
uint32_t rtt = 0;
|
|
|
|
|
_task->getPingResult(snr_out, snr_back, rtt);
|
|
|
|
|
|
|
|
|
|
if (_pinging && (snr_out != 0 || snr_back != 0 || rtt != 0)) {
|
|
|
|
|
if (rtt > 0 && rtt < 10000) {
|
2026-05-30 23:35:07 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: %lums", rtt);
|
2026-05-29 21:49:48 +02:00
|
|
|
} else {
|
2026-05-30 23:35:07 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: timeout");
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
2026-06-15 09:22:26 +02:00
|
|
|
if (snr_out != 0)
|
2026-05-29 21:49:48 +02:00
|
|
|
snprintf(_ping_snr_out_str, sizeof(_ping_snr_out_str), "SNR out: %.1f", snr_out / 4.0f);
|
2026-06-15 09:22:26 +02:00
|
|
|
if (snr_back != 0)
|
2026-05-29 21:49:48 +02:00
|
|
|
snprintf(_ping_snr_back_str, sizeof(_ping_snr_back_str), "SNR back: %.1f", snr_back / 4.0f);
|
|
|
|
|
_pinging = false;
|
|
|
|
|
} else if (_pinging && millis() - _ping_started_ms >= PING_TIMEOUT_MS) {
|
2026-05-30 23:35:07 +02:00
|
|
|
snprintf(_ping_time_str, sizeof(_ping_time_str), "RTT: timeout");
|
2026-05-29 21:49:48 +02:00
|
|
|
_ping_snr_out_str[0] = '\0';
|
|
|
|
|
_ping_snr_back_str[0] = '\0';
|
|
|
|
|
_pinging = false;
|
|
|
|
|
if (_task) _task->clearPing();
|
|
|
|
|
}
|
2026-06-14 13:37:03 +02:00
|
|
|
if (pingRowCount() != _ping_menu.count()) rebuildPingMenu();
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// Ping popup input. Result rows are read-only, so UP/DOWN are swallowed to keep
|
|
|
|
|
// the highlight on "Send".
|
|
|
|
|
void handlePingMenuInput(char c) {
|
|
|
|
|
if (c == KEY_UP || c == KEY_DOWN) return;
|
2026-05-29 21:49:48 +02:00
|
|
|
auto res = _ping_menu.handleInput(c);
|
|
|
|
|
if (res == PopupMenu::SELECTED) {
|
2026-06-15 09:22:26 +02:00
|
|
|
const Entry* e = selected();
|
|
|
|
|
if (!_pinging && e && e->has_key) startPingForKey(e->pub_key);
|
|
|
|
|
_ping_menu.active = true; // stay open so Ping can be repeated
|
2026-05-29 21:49:48 +02:00
|
|
|
} else if (res == PopupMenu::CANCELLED) {
|
|
|
|
|
closePingMenu();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// ── action menu (Hold Enter) — same everywhere ──────────────────────────────
|
|
|
|
|
void buildSortLabel() {
|
|
|
|
|
snprintf(_sort_label, sizeof(_sort_label),
|
|
|
|
|
"Sort: %s", _sort == SORT_TIME ? "Recent" : "Dist");
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
void openActionMenu() {
|
|
|
|
|
const Entry* e = selected();
|
|
|
|
|
bool stored = (_source == SRC_STORED);
|
|
|
|
|
bool has_gps = e && stored && (e->lat_e6 != 0 || e->lon_e6 != 0);
|
|
|
|
|
bool has_key = e && e->has_key;
|
|
|
|
|
|
|
|
|
|
buildSortLabel();
|
|
|
|
|
_menu_action_count = 0;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
_menu.begin("Options", 7);
|
2026-06-15 09:22:26 +02:00
|
|
|
auto add = [&](const char* label, Action a) {
|
|
|
|
|
_menu.addItem(label);
|
|
|
|
|
_menu_actions[_menu_action_count++] = a;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (has_gps) add("Navigate", ACT_NAV);
|
|
|
|
|
if (has_key) add("Ping", ACT_PING);
|
|
|
|
|
if (has_gps) add("Save waypoint", ACT_WAYPOINT);
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
// Needs both a position and a stable identity — a person target is keyed
|
|
|
|
|
// by pubkey prefix, so a name-only live-scan/channel row can't offer this.
|
|
|
|
|
if (has_gps && has_key) add("Set as target", ACT_LOCATOR);
|
2026-06-15 09:22:26 +02:00
|
|
|
if (stored) add(_sort_label, ACT_SORT); // sort is meaningless for live-scan rows
|
|
|
|
|
add(stored ? "Discover scan" : "Rescan", ACT_SCAN);
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
void runAction(Action a) {
|
|
|
|
|
switch (a) {
|
|
|
|
|
case ACT_NAV: {
|
|
|
|
|
const Entry* e = selected();
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
if (e && (e->lat_e6 != 0 || e->lon_e6 != 0)) { _nav = true; _nav_eta.reset(); }
|
2026-06-15 09:22:26 +02:00
|
|
|
else _task->showAlert("No node GPS", 1000);
|
|
|
|
|
break;
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
2026-06-15 09:22:26 +02:00
|
|
|
case ACT_PING: {
|
|
|
|
|
const Entry* e = selected();
|
|
|
|
|
rebuildPingMenu();
|
|
|
|
|
_ping_menu.active = true;
|
|
|
|
|
if (e && e->has_key) startPingForKey(e->pub_key);
|
|
|
|
|
break;
|
2026-05-31 00:11:43 +02:00
|
|
|
}
|
2026-06-15 09:22:26 +02:00
|
|
|
case ACT_WAYPOINT: saveSelectedWaypoint(); break;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
case ACT_LOCATOR: {
|
|
|
|
|
const Entry* e = selected();
|
|
|
|
|
if (e && e->has_key && (e->lat_e6 != 0 || e->lon_e6 != 0))
|
|
|
|
|
_task->setTargetNow(1, e->pub_key, e->lat_e6, e->lon_e6, e->name);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-06-15 09:22:26 +02:00
|
|
|
case ACT_SORT: break; // adjusted in-place via LEFT/RIGHT, not ENTER
|
|
|
|
|
case ACT_SCAN: enterScan(); break;
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// ── detail rendering ──────────────────────────────────────────────────────────
|
|
|
|
|
void renderStoredDetail(DisplayDriver& display) {
|
2026-05-29 21:49:48 +02:00
|
|
|
const Entry& e = _entries[_sel];
|
2026-06-17 21:35:51 +02:00
|
|
|
const int hdr = display.listStart(); // content top (gap below the header separator)
|
2026-06-14 13:21:59 +02:00
|
|
|
display.drawInvertedHeader(e.name);
|
2026-05-29 21:49:48 +02:00
|
|
|
|
|
|
|
|
int step = display.lineStep();
|
|
|
|
|
if (step * 5 > display.height() - hdr) step = (display.height() - hdr) / 5;
|
|
|
|
|
char buf[32];
|
|
|
|
|
snprintf(buf, sizeof(buf), "Lat: %.5f", e.lat_e6 / 1e6);
|
|
|
|
|
display.setCursor(2, hdr); display.print(buf);
|
|
|
|
|
snprintf(buf, sizeof(buf), "Lon: %.5f", e.lon_e6 / 1e6);
|
|
|
|
|
display.setCursor(2, hdr + step); display.print(buf);
|
|
|
|
|
|
|
|
|
|
if (e.dist_km >= 0.0f) {
|
|
|
|
|
char dist[12];
|
2026-06-04 00:44:56 +02:00
|
|
|
geo::fmtDist(dist, sizeof(dist), e.dist_km, useImperial());
|
2026-06-04 00:15:36 +02:00
|
|
|
int az = geo::bearingDeg(_own_lat, _own_lon, e.lat_e6, e.lon_e6);
|
|
|
|
|
snprintf(buf, sizeof(buf), "Dist: %s %s", dist, geo::bearingCardinal(az));
|
2026-05-29 21:49:48 +02:00
|
|
|
} else {
|
|
|
|
|
snprintf(buf, sizeof(buf), "Dist: no GPS");
|
|
|
|
|
}
|
|
|
|
|
display.setCursor(2, hdr + step * 2); display.print(buf);
|
|
|
|
|
snprintf(buf, sizeof(buf), "Type: %s", typeName(e.type));
|
|
|
|
|
display.setCursor(2, hdr + step * 3); display.print(buf);
|
|
|
|
|
char age[16];
|
|
|
|
|
fmtAge(age, sizeof(age), e.lastmod);
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
// For a live [LOC] row, label the timestamp as a position share and note
|
|
|
|
|
// whether the sender's identity is verified (DM) or name-only (channel).
|
|
|
|
|
if (e.is_live) snprintf(buf, sizeof(buf), "Sharing pos: %s %s", age, e.live_verified ? "(DM)" : "(chan)");
|
|
|
|
|
else snprintf(buf, sizeof(buf), "Seen: %s", age);
|
2026-05-29 21:49:48 +02:00
|
|
|
display.drawTextEllipsized(2, hdr + step * 4, display.width() - 4, buf);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
void renderScanDetail(DisplayDriver& display) {
|
|
|
|
|
const Entry& e = _entries[_sel];
|
2026-06-17 21:35:51 +02:00
|
|
|
const int hdr = display.listStart(); // content top (gap below the header separator)
|
2026-05-21 07:57:21 +02:00
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
char label[32];
|
|
|
|
|
if (e.name[0]) { strncpy(label, e.name, 31); label[31] = '\0'; }
|
|
|
|
|
else { snprintf(label, sizeof(label), "[%s]", typeName(e.type)); }
|
|
|
|
|
display.drawInvertedHeader(label);
|
2026-05-21 07:57:21 +02:00
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
char b64[48];
|
|
|
|
|
pubKeyToBase64(e.pub_key, b64, sizeof(b64));
|
|
|
|
|
int max_chars = (display.width() - 4) / display.getCharWidth();
|
|
|
|
|
char b64_line[48];
|
|
|
|
|
if (max_chars < 4) {
|
|
|
|
|
b64_line[0] = '\0';
|
|
|
|
|
} else if ((int)strlen(b64) > max_chars) {
|
|
|
|
|
strncpy(b64_line, b64, max_chars - 3);
|
|
|
|
|
b64_line[max_chars - 3] = '\0';
|
|
|
|
|
strcat(b64_line, "...");
|
2026-05-21 07:57:21 +02:00
|
|
|
} else {
|
2026-06-15 09:22:26 +02:00
|
|
|
strncpy(b64_line, b64, sizeof(b64_line) - 1);
|
|
|
|
|
b64_line[sizeof(b64_line) - 1] = '\0';
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
2026-06-15 09:22:26 +02:00
|
|
|
if (b64_line[0]) { display.setCursor(2, hdr); display.print(b64_line); }
|
2026-05-29 21:49:48 +02:00
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
int step = display.lineStep();
|
|
|
|
|
if (step * 5 > display.height() - hdr) step = (display.height() - hdr) / 5;
|
|
|
|
|
char buf[32];
|
|
|
|
|
snprintf(buf, sizeof(buf), "RSSI: %d dBm", (int)e.rssi);
|
|
|
|
|
display.setCursor(2, hdr + step); display.print(buf);
|
|
|
|
|
snprintf(buf, sizeof(buf), "SNR: %.1f dB", e.snr_x4 / 4.0f);
|
|
|
|
|
display.setCursor(2, hdr + step * 2); display.print(buf);
|
|
|
|
|
snprintf(buf, sizeof(buf), "Rem: %.1f dB", e.remote_snr_x4 / 4.0f);
|
|
|
|
|
display.setCursor(2, hdr + step * 3); display.print(buf);
|
|
|
|
|
display.setCursor(2, hdr + step * 4);
|
|
|
|
|
display.print(e.is_known ? "Status: known" : "Status: new");
|
2026-05-21 07:57:21 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// Draw whichever popup is active over the current view. Returns true if one was.
|
|
|
|
|
bool renderActivePopup(DisplayDriver& display) {
|
|
|
|
|
updatePingMenuState();
|
|
|
|
|
if (_ping_menu.active) { _ping_menu.render(display); return true; }
|
|
|
|
|
if (_menu.active) { _menu.render(display); return true; }
|
|
|
|
|
return false;
|
2026-05-21 07:57:21 +02:00
|
|
|
}
|
|
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
public:
|
|
|
|
|
NearbyScreen(UITask* task)
|
2026-05-21 22:08:28 +02:00
|
|
|
: _task(task), _count(0), _sel(0), _scroll(0), _detail(false),
|
2026-06-15 09:22:26 +02:00
|
|
|
_own_lat(0), _own_lon(0), _own_gps(false),
|
|
|
|
|
_source(SRC_STORED), _filter(F_ALL), _sort(SORT_DIST),
|
|
|
|
|
_detail_refresh_ms(0), _scanning(false), _scan_started_ms(0),
|
|
|
|
|
_menu_action_count(0), _pinging(false), _ping_started_ms(0) {
|
|
|
|
|
resetPingLines();
|
|
|
|
|
_sort_label[0] = '\0';
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-06-29 18:37:04 +02:00
|
|
|
void onShow() override {
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
_sel = _scroll = 0;
|
|
|
|
|
_detail = false;
|
2026-06-03 08:32:32 +02:00
|
|
|
_nav = false;
|
2026-06-15 09:22:26 +02:00
|
|
|
_source = SRC_STORED;
|
|
|
|
|
// _filter / _sort persist across enter() — set once in the constructor
|
|
|
|
|
_scanning = false;
|
|
|
|
|
_menu.active = false;
|
2026-05-29 21:49:48 +02:00
|
|
|
_ping_menu.active = false;
|
|
|
|
|
_pinging = false;
|
2026-06-15 09:22:26 +02:00
|
|
|
resetPingLines();
|
2026-05-29 21:49:48 +02:00
|
|
|
_task->clearPing();
|
2026-06-15 09:22:26 +02:00
|
|
|
refreshStored();
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
// Periodic refresh of the selected entry while in detail or navigate view,
|
|
|
|
|
// preserving the selection across the list rebuild. Navigate refreshes
|
|
|
|
|
// faster so a moving live target (a contact sharing [LOC]) tracks smoothly.
|
|
|
|
|
// Re-selection keys on contact index for stored nodes, and on name for a
|
|
|
|
|
// non-contact live sender — without the latter, navigating to someone who
|
|
|
|
|
// only shares on a channel would drop out on the first refresh.
|
|
|
|
|
unsigned long refresh_due = _nav ? NAV_REFRESH_MS : DETAIL_REFRESH_MS;
|
|
|
|
|
if ((_detail || _nav) && _source == SRC_STORED &&
|
|
|
|
|
millis() - _detail_refresh_ms >= refresh_due) {
|
|
|
|
|
if (!refreshKeepingSelection()) { _detail = false; _nav = false; } // node/share gone
|
2026-05-14 16:29:04 +02:00
|
|
|
_detail_refresh_ms = millis();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 08:32:32 +02:00
|
|
|
// ── navigate-to-node view ────────────────────────────────────────────────
|
|
|
|
|
if (_nav && _sel < _count) {
|
|
|
|
|
const Entry& e = _entries[_sel];
|
|
|
|
|
int cog; bool cogv = _task->currentCourse(cog);
|
|
|
|
|
navview::draw(display, _own_gps, _own_lat, _own_lon,
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
e.lat_e6, e.lon_e6, e.name, cogv, cog, useImperial(), &_nav_eta);
|
2026-06-03 08:32:32 +02:00
|
|
|
return 1000;
|
|
|
|
|
}
|
|
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
// ── detail view ──────────────────────────────────────────────────────────
|
|
|
|
|
if (_detail && _sel < _count) {
|
2026-06-15 09:22:26 +02:00
|
|
|
if (_source == SRC_SCAN) renderScanDetail(display);
|
|
|
|
|
else renderStoredDetail(display);
|
|
|
|
|
renderActivePopup(display);
|
2026-05-29 21:49:48 +02:00
|
|
|
return _ping_menu.active ? 50 : 2000;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── list view ────────────────────────────────────────────────────────────
|
2026-06-15 09:22:26 +02:00
|
|
|
if (_source == SRC_SCAN) {
|
|
|
|
|
refreshScan();
|
|
|
|
|
if (_scanning && millis() - _scan_started_ms >= SCAN_DURATION_MS) _scanning = false;
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
} else if (millis() - _list_refresh_ms >= TIME_LIST_REFRESH_MS) {
|
|
|
|
|
// Re-merge + re-sort periodically so newly-heard contacts and fresh live
|
|
|
|
|
// [LOC] shares bubble to the right spot under either sort (distances move
|
|
|
|
|
// as you and they do, not just recency). Keep the highlighted node put.
|
|
|
|
|
refreshKeepingSelection();
|
2026-06-09 17:15:07 +02:00
|
|
|
_list_refresh_ms = millis();
|
|
|
|
|
}
|
|
|
|
|
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int item_h = display.lineStep();
|
|
|
|
|
int dist_col = display.width() - display.getCharWidth() * 7;
|
|
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-06-15 09:22:26 +02:00
|
|
|
char title[28];
|
|
|
|
|
const char* flt = (_filter != F_ALL) ? FILTER_LABELS[_filter] : nullptr;
|
|
|
|
|
if (_source == SRC_SCAN) {
|
|
|
|
|
const char* base = _scanning ? "SCANNING" : "SCAN";
|
|
|
|
|
if (flt) {
|
|
|
|
|
if (!_scanning && _count == 0) snprintf(title, sizeof(title), "SCAN %s: none", flt);
|
|
|
|
|
else snprintf(title, sizeof(title), "%s %s (%d)", base, flt, _count);
|
|
|
|
|
} else if (_scanning) snprintf(title, sizeof(title), "SCANNING (%d)", _count);
|
|
|
|
|
else if (_count == 0) snprintf(title, sizeof(title), "SCAN: none");
|
|
|
|
|
else snprintf(title, sizeof(title), "SCAN (%d)", _count);
|
|
|
|
|
} else {
|
|
|
|
|
snprintf(title, sizeof(title), "NEARBY %s %s",
|
|
|
|
|
FILTER_LABELS[_filter], _sort == SORT_TIME ? "recent" : "dist");
|
|
|
|
|
}
|
refactor(ui): shared drawList + header + key-decode helpers
- drawList(): scrollable list skeleton (visible window, keep-sel-in-view,
scroll-column reserve, per-row callback, indicator). Migrate Tools, Bot,
Nearby, Waypoints list and the three QuickMsg lists onto it; centralises the
scroll-clamp/reserve/indicator that each had open-coded (and inconsistently).
- drawCenteredHeader(): centred title + separator, replacing the duplicated
two-liner across ~10 screens (incl. QuickMsg, which used width()/2 spacing and
was missed before); unifies the separator at headerH()-sepH().
- keyIsPrev()/keyIsNext() in UIScreen.h for the LEFT||PREV / RIGHT||NEXT idiom;
applied to 6 screens, behaviour-preserving. Cancel/context-menu stay
per-screen (they mean different things on different screens).
- rotLabel() dedups the 0/90/180/270 array in Settings.
- Unify selection-row height to lineStep()-1 (Tools/Bot matched the rest).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:23:00 +02:00
|
|
|
display.drawCenteredHeader(title);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
if (_count == 0) {
|
2026-06-15 09:22:26 +02:00
|
|
|
char empty[24];
|
|
|
|
|
if (_source == SRC_SCAN) {
|
|
|
|
|
const char* msg;
|
|
|
|
|
if (_scanning) msg = "Waiting for replies...";
|
|
|
|
|
else if (flt) { snprintf(empty, sizeof(empty), "No %s nodes", flt); msg = empty; }
|
|
|
|
|
else msg = "No nodes found";
|
|
|
|
|
display.drawTextCentered(display.width() / 2, display.height() / 2, msg);
|
|
|
|
|
} else {
|
|
|
|
|
const char* hint;
|
|
|
|
|
if (flt) { snprintf(empty, sizeof(empty), "No %s contacts", flt); hint = "[<>] change filter"; }
|
|
|
|
|
else { snprintf(empty, sizeof(empty), "No contacts found"); hint = "[Enter]=Discover"; }
|
|
|
|
|
display.drawTextCentered(display.width() / 2, display.height() / 2 - display.lineStep() / 2, empty);
|
|
|
|
|
display.drawTextCentered(display.width() / 2, display.height() / 2 + display.lineStep() / 2, hint);
|
|
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
} else {
|
2026-06-16 08:54:11 +02:00
|
|
|
drawList(display, _count, _sel, _scroll, [&](int idx, int y, bool sel, int reserve) {
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
const Entry& e = _entries[idx];
|
|
|
|
|
|
feat(ui): proportional scrollbar with mini-icon caps; reclaim row space
Replace the text ^/v scroll arrows with a font-scaling indicator drawn in
icons.h: a proportional thumb between fixed up/down triangle mini-icon caps.
drawScrollIndicator() lives in a ~5px right-edge column and scales via
miniIconScale (1x OLED, 2x landscape e-ink).
- Mini-icons: ICON_SCROLL_UP/DOWN; miniIconDrawTop (exact placement) and
miniIconDrawHalo (shape-hugging 1px DARK halo, clip_top to spare the header
separator) so caps stay visible on the LIGHT selection bar without a box.
- Thumb is 3*s wide so it centres on the triangle column; caps are static end
markers (always drawn) so neither vanishes at the extremes.
- scrollIndicatorReserve(): right-edge gutter (0 when the list fits). Content,
the selection bar and the message-history bubbles are all narrowed by it so
nothing renders under the scrollbar. Portrait e-ink wrap width subtracts the
reserve in both the box-height pass and render so wrapped text can't spill.
- Drop the redundant ">" selection marker (the highlight bar already shows
selection) and shift rows to x=2, reclaiming left-edge space.
- Wire the indicator into every scrollable list (Bot, Settings, Nearby, Tools,
Trail, QuickMsg lists + DM/channel history) and add one to WaypointsView,
which scrolled with no indicator before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:41:46 +02:00
|
|
|
display.drawSelectionRow(0, y - 1, display.width() - reserve, item_h - 1, sel);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
char filt[32];
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
int tx = 2;
|
|
|
|
|
if (e.is_live) {
|
|
|
|
|
// Diamond marker = this node is broadcasting its position ([LOC]),
|
|
|
|
|
// the same marker the map uses for a live-tracked contact. DM-verified
|
|
|
|
|
// vs channel-only is spelled out in the detail view.
|
|
|
|
|
int iw = ICON_MAP_CONTACT.w * miniIconScale(display);
|
|
|
|
|
miniIconDrawCentered(display, 2 + iw / 2, y + display.getLineHeight() / 2 - 1, ICON_MAP_CONTACT);
|
|
|
|
|
tx = 2 + iw + 2;
|
|
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
display.translateUTF8ToBlocks(filt, e.name, sizeof(filt));
|
2026-06-15 09:22:26 +02:00
|
|
|
if (_source == SRC_SCAN && !e.name[0]) { // unknown node → "[Type]"
|
|
|
|
|
snprintf(filt, sizeof(filt), "[%s]", typeName(e.type));
|
|
|
|
|
}
|
feat(companion): live location sharing, Locator geofencing, trail auto-pause
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>
2026-06-25 19:04:21 +02:00
|
|
|
display.drawTextEllipsized(tx, y, dist_col - tx - 2, filt);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
display.setColor(sel ? DisplayDriver::DARK : DisplayDriver::LIGHT);
|
2026-06-09 15:09:57 +02:00
|
|
|
char right[10];
|
2026-06-15 09:22:26 +02:00
|
|
|
if (_source == SRC_SCAN) {
|
|
|
|
|
snprintf(right, sizeof(right), "%d", (int)e.rssi);
|
|
|
|
|
} else if (_sort == SORT_TIME) {
|
2026-06-29 17:43:17 +02:00
|
|
|
geo::fmtAgeShort(right, sizeof(right), rtc_clock.getCurrentTime(), e.lastmod);
|
|
|
|
|
if (!right[0]) snprintf(right, sizeof(right), "?"); // unknown / RTC not synced
|
2026-06-09 15:09:57 +02:00
|
|
|
} else {
|
|
|
|
|
if (e.dist_km >= 0.0f) geo::fmtDist(right, sizeof(right), e.dist_km, useImperial());
|
|
|
|
|
else strncpy(right, "?GPS", sizeof(right));
|
|
|
|
|
}
|
2026-06-29 18:01:29 +02:00
|
|
|
display.drawTextRightAlign(display.width() - reserve - 2, y, right);
|
refactor(ui): shared drawList + header + key-decode helpers
- drawList(): scrollable list skeleton (visible window, keep-sel-in-view,
scroll-column reserve, per-row callback, indicator). Migrate Tools, Bot,
Nearby, Waypoints list and the three QuickMsg lists onto it; centralises the
scroll-clamp/reserve/indicator that each had open-coded (and inconsistently).
- drawCenteredHeader(): centred title + separator, replacing the duplicated
two-liner across ~10 screens (incl. QuickMsg, which used width()/2 spacing and
was missed before); unifies the separator at headerH()-sepH().
- keyIsPrev()/keyIsNext() in UIScreen.h for the LEFT||PREV / RIGHT||NEXT idiom;
applied to 6 screens, behaviour-preserving. Cancel/context-menu stay
per-screen (they mean different things on different screens).
- rotLabel() dedups the 0/90/180/270 array in Settings.
- Unify selection-row height to lineStep()-1 (Tools/Bot matched the rest).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:23:00 +02:00
|
|
|
});
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
if (renderActivePopup(display)) return 50;
|
|
|
|
|
if (_source == SRC_SCAN) return _scanning ? 200 : 2000;
|
2026-05-21 07:57:21 +02:00
|
|
|
return _count == 0 ? 3000 : 2000;
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool handleInput(char c) override {
|
2026-06-03 08:32:32 +02:00
|
|
|
// ── navigate-to-node view — any nav key returns to detail ─────────────────
|
|
|
|
|
if (_nav) {
|
refactor(ui): shared drawList + header + key-decode helpers
- drawList(): scrollable list skeleton (visible window, keep-sel-in-view,
scroll-column reserve, per-row callback, indicator). Migrate Tools, Bot,
Nearby, Waypoints list and the three QuickMsg lists onto it; centralises the
scroll-clamp/reserve/indicator that each had open-coded (and inconsistently).
- drawCenteredHeader(): centred title + separator, replacing the duplicated
two-liner across ~10 screens (incl. QuickMsg, which used width()/2 spacing and
was missed before); unifies the separator at headerH()-sepH().
- keyIsPrev()/keyIsNext() in UIScreen.h for the LEFT||PREV / RIGHT||NEXT idiom;
applied to 6 screens, behaviour-preserving. Cancel/context-menu stay
per-screen (they mean different things on different screens).
- rotLabel() dedups the 0/90/180/270 array in Settings.
- Unify selection-row height to lineStep()-1 (Tools/Bot matched the rest).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:23:00 +02:00
|
|
|
if (c == KEY_CANCEL || keyIsPrev(c) || keyIsNext(c)) _nav = false;
|
2026-06-03 08:32:32 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// ── popups (same handling in list and detail) ─────────────────────────────
|
|
|
|
|
if (_ping_menu.active) { handlePingMenuInput(c); return true; }
|
|
|
|
|
if (_menu.active) {
|
|
|
|
|
// LEFT/RIGHT on the Sort row toggles the value in-place and rebuilds the
|
|
|
|
|
// label; the popup stays open so the user can keep tapping. Other rows
|
|
|
|
|
// swallow L/R. ENTER on Sort just closes (value changes via L/R only).
|
refactor(ui): shared drawList + header + key-decode helpers
- drawList(): scrollable list skeleton (visible window, keep-sel-in-view,
scroll-column reserve, per-row callback, indicator). Migrate Tools, Bot,
Nearby, Waypoints list and the three QuickMsg lists onto it; centralises the
scroll-clamp/reserve/indicator that each had open-coded (and inconsistently).
- drawCenteredHeader(): centred title + separator, replacing the duplicated
two-liner across ~10 screens (incl. QuickMsg, which used width()/2 spacing and
was missed before); unifies the separator at headerH()-sepH().
- keyIsPrev()/keyIsNext() in UIScreen.h for the LEFT||PREV / RIGHT||NEXT idiom;
applied to 6 screens, behaviour-preserving. Cancel/context-menu stay
per-screen (they mean different things on different screens).
- rotLabel() dedups the 0/90/180/270 array in Settings.
- Unify selection-row height to lineStep()-1 (Tools/Bot matched the rest).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 21:23:00 +02:00
|
|
|
if (keyIsPrev(c) || keyIsNext(c)) {
|
2026-06-15 09:22:26 +02:00
|
|
|
int i = _menu.selectedIndex();
|
|
|
|
|
if (i >= 0 && i < _menu_action_count && _menu_actions[i] == ACT_SORT) {
|
|
|
|
|
_sort = (_sort == SORT_DIST) ? SORT_TIME : SORT_DIST;
|
|
|
|
|
buildSortLabel();
|
|
|
|
|
refresh();
|
2026-06-03 14:00:16 +02:00
|
|
|
}
|
2026-06-03 08:32:32 +02:00
|
|
|
return true;
|
2026-05-29 21:49:48 +02:00
|
|
|
}
|
2026-06-15 09:22:26 +02:00
|
|
|
auto res = _menu.handleInput(c);
|
|
|
|
|
if (res == PopupMenu::SELECTED) {
|
|
|
|
|
int i = _menu.selectedIndex();
|
|
|
|
|
if (i >= 0 && i < _menu_action_count && _menu_actions[i] != ACT_SORT)
|
|
|
|
|
runAction(_menu_actions[i]);
|
2026-06-03 08:32:32 +02:00
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// ── detail view ───────────────────────────────────────────────────────────
|
|
|
|
|
if (_detail) {
|
|
|
|
|
if (c == KEY_CANCEL) { _detail = false; closePingMenu(); return true; }
|
|
|
|
|
if (c == KEY_CONTEXT_MENU) { openActionMenu(); return true; }
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
// ── list view ───────────────────────────────────────────────────────────
|
|
|
|
|
if (c == KEY_CANCEL) {
|
|
|
|
|
if (_source == SRC_SCAN) leaveScan();
|
|
|
|
|
else _task->gotoToolsScreen();
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2026-06-15 09:22:26 +02:00
|
|
|
if (c == KEY_CONTEXT_MENU) { openActionMenu(); return true; }
|
2026-06-16 08:54:11 +02:00
|
|
|
// drawList() reclamps _scroll from _sel every render.
|
2026-06-18 09:16:57 +02:00
|
|
|
if (c == KEY_UP && _count > 0) { _sel = (_sel > 0) ? _sel - 1 : _count - 1; return true; }
|
|
|
|
|
if (c == KEY_DOWN && _count > 0) { _sel = (_sel < _count - 1) ? _sel + 1 : 0; return true; }
|
2026-06-15 09:22:26 +02:00
|
|
|
if (c == KEY_ENTER) {
|
|
|
|
|
if (_count == 0) { if (_source == SRC_STORED) enterScan(); return true; }
|
2026-05-14 16:17:52 +02:00
|
|
|
_detail = true;
|
2026-05-14 16:29:04 +02:00
|
|
|
_detail_refresh_ms = millis();
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2026-06-29 18:01:29 +02:00
|
|
|
if (keyIsPrev(c)) { _filter = (_filter + F_COUNT - 1) % F_COUNT; refresh(); return true; }
|
|
|
|
|
if (keyIsNext(c)) { _filter = (_filter + 1) % F_COUNT; refresh(); return true; }
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-15 09:22:26 +02:00
|
|
|
const char* NearbyScreen::FILTER_LABELS[F_COUNT] = { "All", "Fav", "Comp", "Rpt", "Room", "Snsr" };
|