refactor(companion): tidy geo-alert / live-share; dedicated auto-pause gate

Review cleanup of the new location features:
- GeoAlertScreen + LiveShareScreen use the shared drawList() helper instead
  of hand-rolling the scroll/clamp/scrollbar maths, matching every other
  list screen.
- Extract UITask::geoAlertDistance() — one place for the target-distance
  maths, shared by the crossing evaluator and the proximity beeper.
- Geo Alert re-seeds its crossing engine after every edit, so changing the
  target/radius while armed can't fire a stale arrive/leave before exit.
- Trail auto-pause gets its own coarse movement gate
  (NodePrefs::TRAIL_AUTOPAUSE_MOVE_M = 15 m) instead of reusing the trail
  min-delta, so GPS jitter while parked doesn't keep the idle timer alive.
- Fix stale include comments.

Verified: WioTrackerL1_companion_solo_dual builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-22 12:38:59 +02:00
parent 408dfd494b
commit 659ecec5a4
5 changed files with 43 additions and 56 deletions

View File

@@ -300,6 +300,11 @@ struct NodePrefs { // persisted to file
static const char* L[TRAIL_AUTOPAUSE_COUNT] = { "Off", "1m", "2m", "5m" };
return L[idx < TRAIL_AUTOPAUSE_COUNT ? idx : 0];
}
// Movement under this many metres counts as "stationary" for auto-pause.
// Deliberately coarser than the trail min-delta gate (and independent of it)
// so GPS jitter while parked doesn't keep resetting the idle timer. Engine
// tuning only — not persisted.
static const uint16_t TRAIL_AUTOPAUSE_MOVE_M = 15;
// Tail sentinel written at the end of /new_prefs. Bump the low byte when
// adding/removing/reordering fields in DataStore::savePrefs/loadPrefsInt so

View File

@@ -9,7 +9,7 @@
#include "../NodePrefs.h"
#include "../Waypoint.h"
#include "icons.h" // scrollIndicatorReserve / drawScrollIndicator
#include "icons.h" // drawList (shared scrolling-list helper)
class GeoAlertScreen : public UIScreen {
UITask* _task;
@@ -71,31 +71,16 @@ public:
display.setColor(DisplayDriver::LIGHT);
display.drawCenteredHeader("GEO ALERT");
const int y0 = display.listStart();
const int step = display.lineStep();
int vis = (display.height() - y0) / step;
if (vis < 1) vis = 1;
if (_sel < _scroll) _scroll = _sel;
if (_sel >= _scroll + vis) _scroll = _sel - vis + 1;
if (_scroll < 0) _scroll = 0;
const int reserve = scrollIndicatorReserve(display, ROW_COUNT, vis);
const int valx = display.width() / 2 + 6;
for (int i = _scroll; i < ROW_COUNT && i < _scroll + vis; i++) {
int y = y0 + (i - _scroll) * step;
const int valx = display.width() / 2 + 6;
drawList(display, ROW_COUNT, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
Row r = rows(i);
bool sel = (i == _sel);
display.drawSelectionRow(0, y - 1, display.width() - reserve, step - 1, sel);
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel);
display.setCursor(4, y);
display.print(r.label);
char val[24];
valueLabel(r.kind, val, sizeof(val));
if (val[0]) display.drawTextEllipsized(valx, y, display.width() - valx - reserve, val);
}
drawScrollIndicator(display, y0, vis * step, ROW_COUNT, vis, _scroll);
});
return 500;
}
@@ -128,6 +113,9 @@ public:
_dirty = true;
break;
}
// Re-seed the crossing engine after any change so editing the target/radius
// while armed can't fire a stale arrive/leave before the screen is closed.
_task->resetGeoAlert();
}
// Cycle the target through the saved waypoints, snapshotting the chosen one's
@@ -157,7 +145,7 @@ public:
bool handleInput(char c) override {
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) {
if (_dirty) { the_mesh.savePrefs(); _task->resetGeoAlert(); _dirty = false; }
if (_dirty) { the_mesh.savePrefs(); _dirty = false; } // engine re-seeded per edit
_task->gotoToolsScreen();
return true;
}

View File

@@ -9,7 +9,7 @@
// Included by UITask.cpp after AutoAdvertScreen.h.
#include "../NodePrefs.h"
#include "icons.h" // scrollIndicatorReserve / drawScrollIndicator
#include "icons.h" // drawList (shared scrolling-list helper)
class LiveShareScreen : public UIScreen {
UITask* _task;
@@ -89,31 +89,16 @@ public:
display.setColor(DisplayDriver::LIGHT);
display.drawCenteredHeader("LIVE SHARE");
const int y0 = display.listStart();
const int step = display.lineStep();
int vis = (display.height() - y0) / step;
if (vis < 1) vis = 1;
if (_sel < _scroll) _scroll = _sel;
if (_sel >= _scroll + vis) _scroll = _sel - vis + 1;
if (_scroll < 0) _scroll = 0;
const int reserve = scrollIndicatorReserve(display, ROW_COUNT, vis);
const int valx = display.width() / 2 + 6;
for (int i = _scroll; i < ROW_COUNT && i < _scroll + vis; i++) {
int y = y0 + (i - _scroll) * step;
const int valx = display.width() / 2 + 6;
drawList(display, ROW_COUNT, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
Row r = rows(i);
bool sel = (i == _sel);
display.drawSelectionRow(0, y - 1, display.width() - reserve, step - 1, sel);
display.drawSelectionRow(0, y - 1, display.width() - reserve, display.lineStep() - 1, sel);
display.setCursor(4, y);
display.print(r.label);
char val[24];
valueLabel(r.kind, val, sizeof(val));
if (val[0]) display.drawTextEllipsized(valx, y, display.width() - valx - reserve, val);
}
drawScrollIndicator(display, y0, vis * step, ROW_COUNT, vis, _scroll);
});
return 500;
}

View File

@@ -2032,15 +2032,17 @@ void UITask::loop() {
int32_t lo = (int32_t)loc->getLongitude();
uint16_t md = TrailStore::minDeltaMeters(_node_prefs->trail_min_delta_idx,
_node_prefs->units_imperial);
// Auto-pause: freeze the trail once the device has sat within the
// min-delta gate for the configured delay; resume on the next real move.
// Auto-pause: freeze the trail once the device has stayed within
// TRAIL_AUTOPAUSE_MOVE_M of one spot for the configured delay; resume on
// the next real move. Its own coarse gate (not the trail min-delta) so
// GPS jitter while parked doesn't keep the idle timer alive.
uint16_t ap = NodePrefs::trailAutoPauseSecs(_node_prefs->trail_autopause_idx);
if (ap > 0) {
uint32_t now = millis();
float moved = _trail_pause_has_ref
? geo::haversineKm(_trail_pause_ref_lat, _trail_pause_ref_lon, la, lo) * 1000.0f
: 1e9f;
if (!_trail_pause_has_ref || moved >= (float)md) {
if (!_trail_pause_has_ref || moved >= (float)NodePrefs::TRAIL_AUTOPAUSE_MOVE_M) {
_trail_pause_ref_lat = la; _trail_pause_ref_lon = lo;
_trail_pause_has_ref = true;
_trail_last_move_ms = now;
@@ -2121,17 +2123,27 @@ void UITask::loop() {
// fires fireGeoAlert() according to the configured mode; a hysteresis band on
// the "leave" edge stops it chattering at the boundary, and the first reading
// after arming only seeds the inside/outside state (no spurious alert).
// Distance (m) from the current GPS fix to the geo-alert target, plus the
// configured radius (m). Returns false when no target is set or there's no fix
// — the single place the target-distance maths lives, shared by the crossing
// evaluator and the proximity beeper.
bool UITask::geoAlertDistance(float& dist_m, float& radius_m) const {
if (!_node_prefs || !_node_prefs->geo_alert_has_target) return false;
int32_t lat, lon;
if (!currentLocation(lat, lon)) return false;
dist_m = geo::haversineKm(lat, lon, _node_prefs->geo_alert_lat_1e6,
_node_prefs->geo_alert_lon_1e6) * 1000.0f;
radius_m = (float)NodePrefs::geoAlertRadiusMeters(_node_prefs->geo_alert_radius_idx);
return true;
}
void UITask::evaluateGeoAlert() {
if (!_node_prefs || !_node_prefs->geo_alert_enabled || !_node_prefs->geo_alert_has_target) {
_geo_alert_known = false;
return;
}
int32_t lat, lon;
if (!currentLocation(lat, lon)) return;
float dist = geo::haversineKm(lat, lon,
_node_prefs->geo_alert_lat_1e6,
_node_prefs->geo_alert_lon_1e6) * 1000.0f;
float r = (float)NodePrefs::geoAlertRadiusMeters(_node_prefs->geo_alert_radius_idx);
float dist, r;
if (!geoAlertDistance(dist, r)) return; // armed but no fix yet — keep state
bool inside;
if (!_geo_alert_known) inside = dist <= r; // seed state
else if (_geo_alert_inside) inside = dist <= r * 1.25f; // leave past band
@@ -2169,12 +2181,8 @@ void UITask::geoProximityBeeper() {
if ((int32_t)(millis() - _geo_beep_check_ms) < 0) return;
_geo_beep_check_ms = millis() + 250UL;
int32_t lat, lon;
if (!currentLocation(lat, lon)) return;
float dist = geo::haversineKm(lat, lon,
_node_prefs->geo_alert_lat_1e6,
_node_prefs->geo_alert_lon_1e6) * 1000.0f;
float r = (float)NodePrefs::geoAlertRadiusMeters(_node_prefs->geo_alert_radius_idx);
float dist, r;
if (!geoAlertDistance(dist, r)) return;
if (dist > r) { // outside the zone: stay quiet, beep on re-entry
_geo_beep_next_ms = millis();
return;

View File

@@ -116,6 +116,7 @@ class UITask : public AbstractUITask {
// is when the next tick is due.
uint32_t _geo_beep_check_ms = 0;
uint32_t _geo_beep_next_ms = 0;
bool geoAlertDistance(float& dist_m, float& radius_m) const;
void evaluateGeoAlert();
void fireGeoAlert(bool arrived);
void geoProximityBeeper();