diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 60d55eb5..d393a0f7 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -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 diff --git a/examples/companion_radio/ui-new/GeoAlertScreen.h b/examples/companion_radio/ui-new/GeoAlertScreen.h index 0f043ebc..56ed208f 100644 --- a/examples/companion_radio/ui-new/GeoAlertScreen.h +++ b/examples/companion_radio/ui-new/GeoAlertScreen.h @@ -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; } diff --git a/examples/companion_radio/ui-new/LiveShareScreen.h b/examples/companion_radio/ui-new/LiveShareScreen.h index bcfc3548..05897b01 100644 --- a/examples/companion_radio/ui-new/LiveShareScreen.h +++ b/examples/companion_radio/ui-new/LiveShareScreen.h @@ -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; } diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 64ac7d95..9e9d0b6a 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -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; diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index ccd2823f..97307a35 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -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();