feat(companion): geo-alert geofence + trail auto-pause

Geo Alert (Tools › Geo Alert): a single geofence around a snapshotted
waypoint. Beeps + alerts on crossing into (arrive) / out of (leave) the
radius per mode, with edge hysteresis and silent first-eval seeding. Adds
a proximity Beeper that ticks faster the closer to the target while inside
the radius — independent of the discrete arrive/leave alert.

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-22 10:45:00 +02:00
parent 1d2f2beaba
commit 408dfd494b
8 changed files with 436 additions and 13 deletions

View File

@@ -375,6 +375,25 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
if (_prefs.loc_share_move_idx >= NodePrefs::LOC_SHARE_MOVE_COUNT) _prefs.loc_share_move_idx = 1;
if (_prefs.loc_share_interval_idx >= NodePrefs::LOC_SHARE_INTERVAL_COUNT) _prefs.loc_share_interval_idx = 1;
if (_prefs.loc_share_heartbeat_idx >= NodePrefs::LOC_SHARE_HEARTBEAT_COUNT) _prefs.loc_share_heartbeat_idx = 0;
// → 0xC0DE0013: geo-alert + trail auto-pause. Pre-0x13 files leave stray bytes
// here; clamp each back to its default so upgraders start with both off.
rd(&_prefs.geo_alert_enabled, sizeof(_prefs.geo_alert_enabled));
rd(&_prefs.geo_alert_has_target, sizeof(_prefs.geo_alert_has_target));
rd(&_prefs.geo_alert_radius_idx, sizeof(_prefs.geo_alert_radius_idx));
rd(&_prefs.geo_alert_mode, sizeof(_prefs.geo_alert_mode));
rd(&_prefs.geo_alert_lat_1e6, sizeof(_prefs.geo_alert_lat_1e6));
rd(&_prefs.geo_alert_lon_1e6, sizeof(_prefs.geo_alert_lon_1e6));
rd(_prefs.geo_alert_label, sizeof(_prefs.geo_alert_label));
rd(&_prefs.trail_autopause_idx, sizeof(_prefs.trail_autopause_idx));
if (_prefs.geo_alert_enabled > 1) _prefs.geo_alert_enabled = 0;
if (_prefs.geo_alert_has_target > 1) _prefs.geo_alert_has_target = 0;
if (_prefs.geo_alert_radius_idx >= NodePrefs::GEO_ALERT_RADIUS_COUNT) _prefs.geo_alert_radius_idx = 1;
if (_prefs.geo_alert_mode >= NodePrefs::GEO_ALERT_MODE_COUNT) _prefs.geo_alert_mode = 0;
if (_prefs.trail_autopause_idx >= NodePrefs::TRAIL_AUTOPAUSE_COUNT) _prefs.trail_autopause_idx = 0;
_prefs.geo_alert_label[sizeof(_prefs.geo_alert_label) - 1] = '\0';
// → 0xC0DE0014: geo-alert proximity beeper.
rd(&_prefs.geo_alert_beeper, sizeof(_prefs.geo_alert_beeper));
if (_prefs.geo_alert_beeper > 1) _prefs.geo_alert_beeper = 0;
// Pre-0x10 files leave stray sentinel bytes here, same as a never-configured
// device. Either way there's no valid saved profile, so default to a profile
// in the same band as the companion's own network (_prefs.freq, already read
@@ -562,6 +581,15 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
file.write((uint8_t *)&_prefs.loc_share_move_idx, sizeof(_prefs.loc_share_move_idx));
file.write((uint8_t *)&_prefs.loc_share_interval_idx, sizeof(_prefs.loc_share_interval_idx));
file.write((uint8_t *)&_prefs.loc_share_heartbeat_idx, sizeof(_prefs.loc_share_heartbeat_idx));
file.write((uint8_t *)&_prefs.geo_alert_enabled, sizeof(_prefs.geo_alert_enabled));
file.write((uint8_t *)&_prefs.geo_alert_has_target, sizeof(_prefs.geo_alert_has_target));
file.write((uint8_t *)&_prefs.geo_alert_radius_idx, sizeof(_prefs.geo_alert_radius_idx));
file.write((uint8_t *)&_prefs.geo_alert_mode, sizeof(_prefs.geo_alert_mode));
file.write((uint8_t *)&_prefs.geo_alert_lat_1e6, sizeof(_prefs.geo_alert_lat_1e6));
file.write((uint8_t *)&_prefs.geo_alert_lon_1e6, sizeof(_prefs.geo_alert_lon_1e6));
file.write((uint8_t *)_prefs.geo_alert_label, sizeof(_prefs.geo_alert_label));
file.write((uint8_t *)&_prefs.trail_autopause_idx, sizeof(_prefs.trail_autopause_idx));
file.write((uint8_t *)&_prefs.geo_alert_beeper, sizeof(_prefs.geo_alert_beeper));
// Tail sentinel — must be last. See NodePrefs::SCHEMA_SENTINEL.
uint32_t sentinel = NodePrefs::SCHEMA_SENTINEL;