From 178a34d1ec2b7c38fb72580779f5edf853a2c0c4 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Mon, 25 May 2026 12:51:37 +0200 Subject: [PATCH] fix(ui): trail elapsed/avg-speed driven by millis(), not RTC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous implementation derived "Time" and "Avg speed" from the RTC: first().ts and rtc_clock.getCurrentTime(). When the RTC isn't synced yet (no GPS time fix, no host sync), every point ts is 0, the current time is 0 too, and elapsed stays at 0 — Time never moves and Avg speed reads 0 even with the map filling in. Real-world breakage. Switch the elapsed/avg-speed timebase to millis(): - TrailStore gains _session_start_ms (millis() when the current start→stop window opened) and _accumulated_ms (banked time across previous windows). - setActive(true) records the start; setActive(false) banks the delta into the accumulator. - elapsedSeconds = (_accumulated_ms + active session delta) / 1000. - clear() zeroes both. Per-point ts stays RTC-derived (used for the HH:MM labels in List view where a wallclock is what the user actually wants). TrailScreen drops the now_ts argument it was passing in, and bumps the refresh interval to 1 s while active so the m:ss counter actually ticks every second. Co-Authored-By: Claude Sonnet 4.6 --- examples/companion_radio/Trail.h | 54 ++++++++++++------- examples/companion_radio/ui-new/TrailScreen.h | 7 ++- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/examples/companion_radio/Trail.h b/examples/companion_radio/Trail.h index 6809625a..1158f8f5 100644 --- a/examples/companion_radio/Trail.h +++ b/examples/companion_radio/Trail.h @@ -42,9 +42,18 @@ public: bool isActive() const { return _active; } void setActive(bool a) { - // Re-arming after a stop marks the next addPoint as a segment start, so - // the renderer doesn't draw a straight line through the dead time. - if (_active && !a) _pending_seg_break = true; + if (a && !_active) { + // off → on: start a new session timer. + _session_start_ms = millis(); + } else if (_active && !a) { + // on → off: bank the elapsed of this session and arm a segment break + // so the renderer doesn't draw a straight line through the dead time. + if (_session_start_ms != 0) { + _accumulated_ms += millis() - _session_start_ms; + _session_start_ms = 0; + } + _pending_seg_break = true; + } _active = a; } @@ -56,7 +65,12 @@ public: const TrailPoint& first() const { return at(0); } const TrailPoint& last() const { return at(_count - 1); } - void clear() { _head = 0; _count = 0; _pending_seg_break = false; } + void clear() { + _head = 0; _count = 0; + _pending_seg_break = false; + _accumulated_ms = 0; + _session_start_ms = 0; + } // Returns true if the point was stored (passed the min-delta gate). // First point of the ring and the first point after a stop/start cycle @@ -96,20 +110,18 @@ public: return (uint32_t)d; } - // Seconds between the first sample and either the most recent sample (when - // stopped) or the current RTC time passed in by the caller (when active). - // Using `now_ts` while active lets the UI advance the displayed time - // smoothly even when samples land on the floor of the min-delta gate. - uint32_t elapsedSeconds(uint32_t now_ts = 0) const { - if (_count == 0) return 0; - uint32_t start = first().ts; - uint32_t end = (_active && now_ts > start) ? now_ts : last().ts; - return (end > start) ? (end - start) : 0; + // Cumulative active tracking time across all start→stop sessions, in + // seconds. Counts ticks while the trail is on, freezes while it's off. + // millis()-based so it doesn't depend on RTC sync. + uint32_t elapsedSeconds() const { + uint32_t ms = _accumulated_ms; + if (_active && _session_start_ms != 0) ms += millis() - _session_start_ms; + return ms / 1000; } - // Average speed in km/h = total distance / elapsed time. - uint16_t avgSpeedKmh(uint32_t now_ts = 0) const { - uint32_t es = elapsedSeconds(now_ts); + // Average speed in km/h = total distance / cumulative active time. + uint16_t avgSpeedKmh() const { + uint32_t es = elapsedSeconds(); if (es == 0) return 0; return (uint16_t)((float)totalDistanceMeters() / (float)es * 3.6f); } @@ -147,8 +159,10 @@ public: private: TrailPoint _buf[CAPACITY]; - int _head = 0; - int _count = 0; - bool _active = false; - bool _pending_seg_break = false; // next addPoint flags itself SEG_START + int _head = 0; + int _count = 0; + bool _active = false; + bool _pending_seg_break = false; // next addPoint flags itself SEG_START + uint32_t _accumulated_ms = 0; // banked active time across previous sessions + uint32_t _session_start_ms = 0; // millis() of the current active session, 0 if none }; diff --git a/examples/companion_radio/ui-new/TrailScreen.h b/examples/companion_radio/ui-new/TrailScreen.h index ca3d2039..c7db645f 100644 --- a/examples/companion_radio/ui-new/TrailScreen.h +++ b/examples/companion_radio/ui-new/TrailScreen.h @@ -47,7 +47,7 @@ public: display.setCursor(2, hint_y); display.print(hint); - return _store->isActive() ? 2000 : 5000; + return _store->isActive() ? 1000 : 5000; } bool handleInput(char c) override { @@ -107,7 +107,7 @@ private: break; } case 3: { - uint32_t es = _store->elapsedSeconds((uint32_t)rtc_clock.getCurrentTime()); + uint32_t es = _store->elapsedSeconds(); // Below 1 h show m:ss so the seconds counter updates visibly each refresh. if (es < 3600) snprintf(buf, n, "Time: %lu:%02lu", (unsigned long)(es / 60), (unsigned long)(es % 60)); @@ -116,8 +116,7 @@ private: break; } case 4: - snprintf(buf, n, "Avg speed: %u km/h", - (unsigned)_store->avgSpeedKmh((uint32_t)rtc_clock.getCurrentTime())); + snprintf(buf, n, "Avg speed: %u km/h", (unsigned)_store->avgSpeedKmh()); break; default: buf[0] = '\0';