feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
|
|
#include <Arduino.h>
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
#include <stdint.h>
|
2026-05-26 08:05:05 +02:00
|
|
|
|
#include <time.h>
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
2026-05-25 09:10:40 +02:00
|
|
|
|
// RAM-only GPS trail ring buffer.
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
// Storage cost: 256 × 12 B = 3 KB. The trail survives auto-off (only the
|
|
|
|
|
|
// display blanks) but is lost on reboot — user explicitly snapshots to a
|
|
|
|
|
|
// LittleFS slot before powering down to keep it.
|
|
|
|
|
|
|
2026-05-25 09:10:40 +02:00
|
|
|
|
struct TrailPoint {
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
int32_t lat_1e6;
|
|
|
|
|
|
int32_t lon_1e6;
|
|
|
|
|
|
uint32_t ts; // epoch seconds (RTC)
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
uint8_t flags; // bit 0 = SEG_START (don't draw a line from the previous point)
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
static const uint8_t TRAIL_FLAG_SEG_START = 0x01;
|
|
|
|
|
|
|
2026-05-25 09:10:40 +02:00
|
|
|
|
class TrailStore {
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
public:
|
2026-05-25 10:50:51 +02:00
|
|
|
|
static const int CAPACITY = 512;
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
2026-05-25 10:48:03 +02:00
|
|
|
|
// Fixed sampling cadence — matches the sensor manager's default GPS update
|
|
|
|
|
|
// rate (1 s). Density is controlled by the min-delta gate (settings) rather
|
|
|
|
|
|
// than by throttling the GPS poll. The NodePrefs::trail_interval_idx field
|
|
|
|
|
|
// is retained as reserved for backwards compatibility but no longer used.
|
|
|
|
|
|
static const uint16_t SAMPLING_SECS = 1;
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
|
|
|
|
|
// Min-delta (metres) gates samples too close to the previous one.
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
// Default 5 m: keeps walking jitter out, dense enough for a visible trail.
|
|
|
|
|
|
static const uint8_t MIN_DELTA_COUNT = 4;
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
static uint16_t minDeltaMeters(uint8_t idx) {
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
static const uint16_t OPTS[MIN_DELTA_COUNT] = { 5, 10, 25, 100 };
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
return OPTS[idx < MIN_DELTA_COUNT ? idx : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
static const char* minDeltaLabel(uint8_t idx) {
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
static const char* L[MIN_DELTA_COUNT] = { "5 m", "10 m", "25 m", "100 m" };
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
return L[idx < MIN_DELTA_COUNT ? idx : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 13:16:02 +02:00
|
|
|
|
// Speed / pace display units. UNITS_KMH / UNITS_MPH show speed; UNITS_PACE_KM
|
|
|
|
|
|
// / UNITS_PACE_MI show time per distance ("pace"). Index 0 = km/h default.
|
|
|
|
|
|
enum Units : uint8_t {
|
|
|
|
|
|
UNITS_KMH = 0,
|
|
|
|
|
|
UNITS_MPH = 1,
|
|
|
|
|
|
UNITS_PACE_KM = 2,
|
|
|
|
|
|
UNITS_PACE_MI = 3,
|
|
|
|
|
|
};
|
|
|
|
|
|
static const uint8_t UNITS_COUNT = 4;
|
|
|
|
|
|
static const char* unitLabel(uint8_t idx) {
|
|
|
|
|
|
static const char* L[UNITS_COUNT] = { "km/h", "mph", "min/km", "min/mi" };
|
|
|
|
|
|
return L[idx < UNITS_COUNT ? idx : 0];
|
|
|
|
|
|
}
|
|
|
|
|
|
static bool unitIsPace(uint8_t idx) { return idx == UNITS_PACE_KM || idx == UNITS_PACE_MI; }
|
|
|
|
|
|
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
bool isActive() const { return _active; }
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
void setActive(bool a) {
|
2026-05-25 12:51:37 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
_active = a;
|
|
|
|
|
|
}
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
|
|
|
|
|
int count() const { return _count; }
|
|
|
|
|
|
bool empty() const { return _count == 0; }
|
|
|
|
|
|
|
|
|
|
|
|
// i = 0 → oldest entry, i = count()-1 → newest.
|
2026-05-25 09:10:40 +02:00
|
|
|
|
const TrailPoint& at(int i) const { return _buf[(_head + i) % CAPACITY]; }
|
|
|
|
|
|
const TrailPoint& first() const { return at(0); }
|
|
|
|
|
|
const TrailPoint& last() const { return at(_count - 1); }
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
2026-05-25 12:51:37 +02:00
|
|
|
|
void clear() {
|
|
|
|
|
|
_head = 0; _count = 0;
|
|
|
|
|
|
_pending_seg_break = false;
|
|
|
|
|
|
_accumulated_ms = 0;
|
|
|
|
|
|
_session_start_ms = 0;
|
|
|
|
|
|
}
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
|
|
|
|
|
|
// Returns true if the point was stored (passed the min-delta gate).
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
// First point of the ring and the first point after a stop/start cycle
|
|
|
|
|
|
// get flagged TRAIL_FLAG_SEG_START so the map renderer breaks the line.
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
bool addPoint(int32_t lat_1e6, int32_t lon_1e6, uint32_t ts, uint16_t min_delta_m) {
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
if (_count > 0 && !_pending_seg_break) {
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
float d = haversineMeters(last().lat_1e6, last().lon_1e6, lat_1e6, lon_1e6);
|
|
|
|
|
|
if (d < (float)min_delta_m) return false;
|
|
|
|
|
|
}
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
uint8_t flags = (_count == 0 || _pending_seg_break) ? TRAIL_FLAG_SEG_START : 0;
|
|
|
|
|
|
_pending_seg_break = false;
|
|
|
|
|
|
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
int pos;
|
|
|
|
|
|
if (_count < CAPACITY) {
|
|
|
|
|
|
pos = (_head + _count) % CAPACITY;
|
|
|
|
|
|
_count++;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
pos = _head;
|
|
|
|
|
|
_head = (_head + 1) % CAPACITY;
|
|
|
|
|
|
}
|
|
|
|
|
|
_buf[pos].lat_1e6 = lat_1e6;
|
|
|
|
|
|
_buf[pos].lon_1e6 = lon_1e6;
|
|
|
|
|
|
_buf[pos].ts = ts;
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
_buf[pos].flags = flags;
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
// Sum of pairwise Haversine deltas across the whole ring, skipping segment
|
|
|
|
|
|
// boundaries (a SEG_START point isn't reached from its predecessor).
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
uint32_t totalDistanceMeters() const {
|
|
|
|
|
|
float d = 0;
|
|
|
|
|
|
for (int i = 1; i < _count; i++) {
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
if (at(i).flags & TRAIL_FLAG_SEG_START) continue;
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
d += haversineMeters(at(i - 1).lat_1e6, at(i - 1).lon_1e6,
|
|
|
|
|
|
at(i).lat_1e6, at(i).lon_1e6);
|
|
|
|
|
|
}
|
|
|
|
|
|
return (uint32_t)d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 12:51:37 +02:00
|
|
|
|
// 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;
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 12:51:37 +02:00
|
|
|
|
// Average speed in km/h = total distance / cumulative active time.
|
|
|
|
|
|
uint16_t avgSpeedKmh() const {
|
|
|
|
|
|
uint32_t es = elapsedSeconds();
|
fix(ui): trail sampling, avg speed, RTC elapsed, segment-aware map
Test feedback revealed four issues:
1. Sampling too sparse — defaults were 60 s interval + 25 m min-delta,
so a walking pace dropped ~80% of samples. New defaults: 30 s + 5 m.
Interval options expanded to { 30, 10, 20, 60, 300, 900 } s and
min-delta to { 5, 10, 25, 100 } m so the user can dial it further
from Settings (phase 5).
2. "Speed" was current-from-last-pair, misleading next to a Time field
that grows monotonically. Switch to avgSpeedKmh = total / elapsed,
labeled "Avg speed".
3. Time advanced only when a new sample landed (it used
last().ts - first().ts). Now elapsedSeconds takes an optional
now_ts and uses it whenever the trail is active, so the Time field
in Summary ticks every render cycle. Sub-1h shown as m:ss for
visible seconds.
4. Stop → start drew a straight line across the dead time. TrailPoint
gains a flags byte; addPoint flags the first point and the first
point after a re-arm as SEG_START. The map renderer skips the line
from the predecessor for SEG_START points (still draws the start
pixel). totalDistanceMeters also skips segment boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:10:03 +02:00
|
|
|
|
if (es == 0) return 0;
|
|
|
|
|
|
return (uint16_t)((float)totalDistanceMeters() / (float)es * 3.6f);
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Compute bounding box across all points. Returns false if empty.
|
|
|
|
|
|
bool boundingBox(int32_t& min_lat, int32_t& min_lon,
|
|
|
|
|
|
int32_t& max_lat, int32_t& max_lon) const {
|
|
|
|
|
|
if (_count == 0) return false;
|
|
|
|
|
|
min_lat = max_lat = first().lat_1e6;
|
|
|
|
|
|
min_lon = max_lon = first().lon_1e6;
|
|
|
|
|
|
for (int i = 1; i < _count; i++) {
|
|
|
|
|
|
const auto& p = at(i);
|
|
|
|
|
|
if (p.lat_1e6 < min_lat) min_lat = p.lat_1e6;
|
|
|
|
|
|
if (p.lat_1e6 > max_lat) max_lat = p.lat_1e6;
|
|
|
|
|
|
if (p.lon_1e6 < min_lon) min_lon = p.lon_1e6;
|
|
|
|
|
|
if (p.lon_1e6 > max_lon) max_lon = p.lon_1e6;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 07:49:17 +02:00
|
|
|
|
// Persistent snapshot — single slot at the given filesystem path.
|
|
|
|
|
|
// Layout: 4-byte magic "TRAL", uint8 version, uint8 reserved, uint16 count,
|
|
|
|
|
|
// uint32 accumulated_ms, then `count` raw TrailPoint records. count is
|
|
|
|
|
|
// clamped to CAPACITY on load.
|
|
|
|
|
|
static const uint32_t SAVE_MAGIC = 0x4C415254; // "TRAL"
|
|
|
|
|
|
static const uint8_t SAVE_VERSION = 1;
|
|
|
|
|
|
|
|
|
|
|
|
// Caller supplies an opened, writable File (the FS-open call is
|
|
|
|
|
|
// platform-specific). Returns true if the header and every point wrote
|
|
|
|
|
|
// cleanly. The file is left open for the caller to close.
|
|
|
|
|
|
template <typename F>
|
|
|
|
|
|
bool writeTo(F& file) {
|
|
|
|
|
|
uint32_t magic = SAVE_MAGIC;
|
|
|
|
|
|
if (file.write((uint8_t*)&magic, sizeof(magic)) != sizeof(magic)) return false;
|
|
|
|
|
|
uint8_t ver = SAVE_VERSION;
|
|
|
|
|
|
uint8_t res = 0;
|
|
|
|
|
|
uint16_t cnt = (uint16_t)_count;
|
|
|
|
|
|
file.write(&ver, 1);
|
|
|
|
|
|
file.write(&res, 1);
|
|
|
|
|
|
file.write((uint8_t*)&cnt, sizeof(cnt));
|
|
|
|
|
|
uint32_t accum = currentAccumulatedMs();
|
|
|
|
|
|
file.write((uint8_t*)&accum, sizeof(accum));
|
|
|
|
|
|
for (int i = 0; i < _count; i++) {
|
|
|
|
|
|
file.write((uint8_t*)&at(i), sizeof(TrailPoint));
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename F>
|
|
|
|
|
|
bool readFrom(F& file) {
|
|
|
|
|
|
uint32_t magic = 0;
|
|
|
|
|
|
if (file.read((uint8_t*)&magic, sizeof(magic)) != (int)sizeof(magic)) return false;
|
|
|
|
|
|
if (magic != SAVE_MAGIC) return false;
|
|
|
|
|
|
uint8_t ver = 0, res = 0;
|
|
|
|
|
|
uint16_t cnt = 0;
|
|
|
|
|
|
uint32_t accum = 0;
|
|
|
|
|
|
file.read(&ver, 1);
|
|
|
|
|
|
file.read(&res, 1);
|
|
|
|
|
|
file.read((uint8_t*)&cnt, sizeof(cnt));
|
|
|
|
|
|
file.read((uint8_t*)&accum, sizeof(accum));
|
|
|
|
|
|
if (ver != SAVE_VERSION || cnt > CAPACITY) return false;
|
|
|
|
|
|
if (_active) {
|
|
|
|
|
|
_active = false;
|
|
|
|
|
|
_session_start_ms = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
_head = 0;
|
|
|
|
|
|
_count = 0;
|
|
|
|
|
|
for (int i = 0; i < cnt; i++) {
|
|
|
|
|
|
TrailPoint p;
|
|
|
|
|
|
int n = file.read((uint8_t*)&p, sizeof(TrailPoint));
|
|
|
|
|
|
if (n != (int)sizeof(TrailPoint)) break;
|
|
|
|
|
|
_buf[_count++] = p;
|
|
|
|
|
|
}
|
|
|
|
|
|
_accumulated_ms = accum;
|
|
|
|
|
|
_pending_seg_break = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 08:02:16 +02:00
|
|
|
|
// GPX writers — shared helpers so we can dump from RAM and from flash
|
|
|
|
|
|
// through the same formatting code.
|
|
|
|
|
|
|
|
|
|
|
|
template <typename S>
|
|
|
|
|
|
static size_t gpxHeader(S& out, const char* name) {
|
|
|
|
|
|
size_t n = 0;
|
|
|
|
|
|
n += out.print(F("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"));
|
|
|
|
|
|
n += out.print(F("<gpx version=\"1.1\" creator=\"MeshCore\" "
|
|
|
|
|
|
"xmlns=\"http://www.topografix.com/GPX/1/1\">\n"));
|
|
|
|
|
|
n += out.print(F("<trk><name>"));
|
|
|
|
|
|
n += out.print(name);
|
|
|
|
|
|
n += out.print(F("</name>\n"));
|
|
|
|
|
|
return n;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename S>
|
|
|
|
|
|
static size_t gpxFooter(S& out, bool in_segment) {
|
|
|
|
|
|
size_t n = 0;
|
|
|
|
|
|
if (in_segment) n += out.print(F("</trkseg>\n"));
|
|
|
|
|
|
n += out.print(F("</trk></gpx>\n"));
|
|
|
|
|
|
return n;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Emit a single <trkpt>; opens a <trkseg> on a segment boundary. Updates
|
|
|
|
|
|
// `in_segment` to track open/close pairing.
|
|
|
|
|
|
template <typename S>
|
|
|
|
|
|
static size_t gpxPoint(S& out, const TrailPoint& p, bool first, bool& in_segment) {
|
|
|
|
|
|
size_t n = 0;
|
|
|
|
|
|
bool seg_start = first || (p.flags & TRAIL_FLAG_SEG_START);
|
|
|
|
|
|
if (seg_start) {
|
|
|
|
|
|
if (in_segment) n += out.print(F("</trkseg>\n"));
|
|
|
|
|
|
n += out.print(F("<trkseg>\n"));
|
|
|
|
|
|
in_segment = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
char buf[120];
|
|
|
|
|
|
time_t t = (time_t)p.ts;
|
2026-05-26 08:05:05 +02:00
|
|
|
|
struct tm* gt = ::gmtime(&t);
|
2026-05-26 08:02:16 +02:00
|
|
|
|
int len = snprintf(buf, sizeof(buf),
|
|
|
|
|
|
"<trkpt lat=\"%.6f\" lon=\"%.6f\"><time>%04d-%02d-%02dT%02d:%02d:%02dZ</time></trkpt>\n",
|
|
|
|
|
|
p.lat_1e6 / 1.0e6, p.lon_1e6 / 1.0e6,
|
|
|
|
|
|
gt->tm_year + 1900, gt->tm_mon + 1, gt->tm_mday,
|
|
|
|
|
|
gt->tm_hour, gt->tm_min, gt->tm_sec);
|
|
|
|
|
|
n += out.write((const uint8_t*)buf, len);
|
|
|
|
|
|
return n;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Dump the live RAM ring as GPX. Returns bytes written.
|
2026-05-26 07:49:17 +02:00
|
|
|
|
template <typename S>
|
|
|
|
|
|
size_t exportGpx(S& out, const char* trk_name = "MeshCore Trail") {
|
2026-05-26 08:02:16 +02:00
|
|
|
|
size_t total = gpxHeader(out, trk_name);
|
2026-05-26 07:49:17 +02:00
|
|
|
|
bool in_segment = false;
|
|
|
|
|
|
for (int i = 0; i < _count; i++) {
|
2026-05-26 08:02:16 +02:00
|
|
|
|
total += gpxPoint(out, at(i), i == 0, in_segment);
|
|
|
|
|
|
}
|
|
|
|
|
|
total += gpxFooter(out, in_segment);
|
|
|
|
|
|
return total;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Stream a saved trail straight from the open file as GPX without
|
|
|
|
|
|
// touching the live RAM ring. Returns 0 on format mismatch.
|
|
|
|
|
|
template <typename F, typename S>
|
|
|
|
|
|
static size_t exportGpxFromFile(F& file, S& out, const char* trk_name = "MeshCore Trail") {
|
|
|
|
|
|
uint32_t magic = 0;
|
|
|
|
|
|
if (file.read((uint8_t*)&magic, sizeof(magic)) != (int)sizeof(magic)) return 0;
|
|
|
|
|
|
if (magic != SAVE_MAGIC) return 0;
|
|
|
|
|
|
uint8_t ver = 0, res = 0;
|
|
|
|
|
|
uint16_t cnt = 0;
|
|
|
|
|
|
uint32_t accum = 0;
|
|
|
|
|
|
file.read(&ver, 1);
|
|
|
|
|
|
file.read(&res, 1);
|
|
|
|
|
|
file.read((uint8_t*)&cnt, sizeof(cnt));
|
|
|
|
|
|
file.read((uint8_t*)&accum, sizeof(accum));
|
|
|
|
|
|
if (ver != SAVE_VERSION || cnt > CAPACITY) return 0;
|
|
|
|
|
|
|
|
|
|
|
|
size_t total = gpxHeader(out, trk_name);
|
|
|
|
|
|
bool in_segment = false;
|
|
|
|
|
|
for (uint16_t i = 0; i < cnt; i++) {
|
|
|
|
|
|
TrailPoint p;
|
|
|
|
|
|
int n = file.read((uint8_t*)&p, sizeof(TrailPoint));
|
|
|
|
|
|
if (n != (int)sizeof(TrailPoint)) break;
|
|
|
|
|
|
total += gpxPoint(out, p, i == 0, in_segment);
|
2026-05-26 07:49:17 +02:00
|
|
|
|
}
|
2026-05-26 08:02:16 +02:00
|
|
|
|
total += gpxFooter(out, in_segment);
|
2026-05-26 07:49:17 +02:00
|
|
|
|
return total;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t currentAccumulatedMs() const {
|
|
|
|
|
|
uint32_t ms = _accumulated_ms;
|
|
|
|
|
|
if (_active && _session_start_ms != 0) ms += millis() - _session_start_ms;
|
|
|
|
|
|
return ms;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
// Approximate great-circle distance in metres (Haversine).
|
|
|
|
|
|
static float haversineMeters(int32_t la1, int32_t lo1, int32_t la2, int32_t lo2) {
|
|
|
|
|
|
const float R = 6371000.0f;
|
|
|
|
|
|
const float D2R = (float)M_PI / 180.0f;
|
|
|
|
|
|
float lat1 = (la1 / 1.0e6f) * D2R;
|
|
|
|
|
|
float lat2 = (la2 / 1.0e6f) * D2R;
|
|
|
|
|
|
float dlat = ((la2 - la1) / 1.0e6f) * D2R;
|
|
|
|
|
|
float dlon = ((lo2 - lo1) / 1.0e6f) * D2R;
|
|
|
|
|
|
float sdl = sinf(dlat * 0.5f);
|
|
|
|
|
|
float sdo = sinf(dlon * 0.5f);
|
|
|
|
|
|
float a = sdl * sdl + cosf(lat1) * cosf(lat2) * sdo * sdo;
|
|
|
|
|
|
float c = 2.0f * atan2f(sqrtf(a), sqrtf(1.0f - a));
|
|
|
|
|
|
return R * c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
2026-05-25 09:10:40 +02:00
|
|
|
|
TrailPoint _buf[CAPACITY];
|
2026-05-25 12:51:37 +02:00
|
|
|
|
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
|
feat(ui): GPS breadcrumb phase 1 — storage, sampling, Summary view
Adds Tools › Breadcrumb. Enter on the screen toggles tracking; while
active, UITask::loop samples GPS at the cadence chosen in settings
(default 1 min) and drops the point into a 256-entry RAM ring guarded
by a min-distance gate (default 25 m). A "G" indicator joins "A" in
the status bar with the same blink convention.
New storage:
- examples/companion_radio/Breadcrumb.h — BreadcrumbStore class
- 256 × 12 B = 3 KB RAM (survives auto-off, lost on reboot — explicit
flash-slot save comes in phase 4)
- Haversine min-delta gate, total distance, elapsed seconds, current
km/h, bounding-box helper
New screen:
- examples/companion_radio/ui-new/BreadcrumbScreen.h — Summary view
with Status, Points, Dist (m or km), Time (h:mm), Speed; Enter
toggles tracking, Esc returns to Tools
Schema bump 0xC0DE0002 → 0xC0DE0003 for two new NodePrefs bytes:
breadcrumb_interval_idx (1min default) and breadcrumb_min_delta_idx
(25m default). Older saves leave both at zero, which matches the new
defaults. DataStore::loadPrefsInt/savePrefs read/write the pair.
Logging on/off is a runtime flag inside BreadcrumbStore — not a
preference — so reboot returns to the off state cleanly; the user
will be able to persist a snapshot to a slot in phase 4.
Phase 2 (map view), 3 (point list), 4 (slot save/load, GPX export over
USB), and 5 (settings UI) follow incrementally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 08:22:12 +02:00
|
|
|
|
};
|