mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-08-03 18:56:15 +00:00
feat(companion): track-back — retrace the recorded trail to its start
Add Tools › Trail → Hold Enter → Track back (shown when the trail has ≥2
points). It reuses NavView but, instead of a single fixed target, snaps onto
the route at the nearest recorded breadcrumb and walks the points in reverse,
auto-advancing the target as each is reached (within 20 m). The header shows
points remaining ("Back: 12 pt" → "Trail start"); reaching the start toasts
"Back at start" and exits. Cancel leaves at any time.
Lives in WaypointsView as a new sub-mode; advancement runs in poll()
(forwarded from TrailScreen) so it tracks GPS independently of the render
cadence. An EtaTracker drives the approach/ETA line and is reset on each
breadcrumb advance to avoid a bogus closing-speed spike.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c60a7a7f64
commit
4d795ade51
@@ -107,6 +107,7 @@ Cycle views with **LEFT / RIGHT**:
|
|||||||
| Start / Stop tracking | Begin or end a recording session. If **GPS is off**, choosing Start asks **"GPS is off — Enable GPS & start"** so a session can't silently run with nothing to record |
|
| Start / Stop tracking | Begin or end a recording session. If **GPS is off**, choosing Start asks **"GPS is off — Enable GPS & start"** so a session can't silently run with nothing to record |
|
||||||
| Mark here | Drop a waypoint at the current GPS fix (see below) |
|
| Mark here | Drop a waypoint at the current GPS fix (see below) |
|
||||||
| Waypoints… | Open the waypoint list / navigation / add-by-coords |
|
| Waypoints… | Open the waypoint list / navigation / add-by-coords |
|
||||||
|
| Track back | Retrace the recorded route back to its start (needs ≥2 points; see below) |
|
||||||
| Share my pos | Send your current position as a one-shot `[LOC]` message — pick a contact or channel (see **Live Share**) |
|
| Share my pos | Send your current position as a one-shot `[LOC]` message — pick a contact or channel (see **Live Share**) |
|
||||||
| Trail file… | Open the file submenu (below) |
|
| Trail file… | Open the file submenu (below) |
|
||||||
| Settings… | Open the settings submenu (below) |
|
| Settings… | Open the settings submenu (below) |
|
||||||
@@ -135,6 +136,10 @@ Cycle views with **LEFT / RIGHT**:
|
|||||||
|
|
||||||
**Auto-pause** — when set, a recording trail automatically **pauses** after the device has stayed within ~15 m of one spot for the chosen delay: the elapsed timer and point sampling both freeze, and the map line breaks across the idle gap. It **resumes on its own** as soon as you move again. This keeps a stop (a break, a meal, parking) out of your distance and average-speed stats without you having to remember to stop and restart tracking. A paused trail is still "on" (the **G** marker keeps blinking) — the Summary **Status** row shows `paused`. The stop is detected with its own coarse movement gate, independent of **Min dist**, so GPS jitter while you're parked doesn't keep it awake.
|
**Auto-pause** — when set, a recording trail automatically **pauses** after the device has stayed within ~15 m of one spot for the chosen delay: the elapsed timer and point sampling both freeze, and the map line breaks across the idle gap. It **resumes on its own** as soon as you move again. This keeps a stop (a break, a meal, parking) out of your distance and average-speed stats without you having to remember to stop and restart tracking. A paused trail is still "on" (the **G** marker keeps blinking) — the Summary **Status** row shows `paused`. The stop is detected with its own coarse movement gate, independent of **Min dist**, so GPS jitter while you're parked doesn't keep it awake.
|
||||||
|
|
||||||
|
### Track back
|
||||||
|
|
||||||
|
**Hold Enter → Track back** retraces the trail you just recorded, back to where you started — useful for returning the same way in poor visibility or unfamiliar ground. It reuses the navigation view (distance + two absolute bearings; see *Waypoints › Navigating*), but instead of a single fixed target it walks the recorded breadcrumbs in reverse: it snaps onto the route at the **nearest recorded point**, guides you to it, then automatically advances to the next earlier point as you reach each one (within ~20 m). The header shows how many points remain (`Back: 12 pt`), reading `Trail start` on the final leg; arriving there shows `Back at start` and exits. **Cancel** leaves track-back at any time. It needs a trail with at least two points and a GPS fix; it doesn't require tracking to still be running.
|
||||||
|
|
||||||
### Waypoints
|
### Waypoints
|
||||||
|
|
||||||
A waypoint is a saved spot — your car, camp, a water source — that you can navigate back to later. Waypoints are **independent of the trail**: they live in their own flash file (`/waypoints`), survive a reboot, and are **not** cleared by *Reset trail*. Up to 16 can be stored — the Waypoints list header shows how many are in use (e.g. `WAYPOINTS 3/16`).
|
A waypoint is a saved spot — your car, camp, a water source — that you can navigate back to later. Waypoints are **independent of the trail**: they live in their own flash file (`/waypoints`), survive a reboot, and are **not** cleared by *Reset trail*. Up to 16 can be stored — the Waypoints list header shows how many are in use (e.g. `WAYPOINTS 3/16`).
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class TrailScreen : public UIScreen {
|
|||||||
// the labels live in member buffers that get refreshed in openActionMenu()
|
// the labels live in member buffers that get refreshed in openActionMenu()
|
||||||
// and after every LEFT/RIGHT cycle.
|
// and after every LEFT/RIGHT cycle.
|
||||||
enum ActionId { ACT_MIN_DIST, ACT_UNITS, ACT_GRID, ACT_AUTOPAUSE, ACT_MARK_AVG, ACT_TOGGLE, ACT_SAVE, ACT_LOAD, ACT_RESET, ACT_EXPORT, ACT_EXPORT_SAVED, ACT_MARK, ACT_WAYPOINTS, ACT_FILE, ACT_SETTINGS,
|
enum ActionId { ACT_MIN_DIST, ACT_UNITS, ACT_GRID, ACT_AUTOPAUSE, ACT_MARK_AVG, ACT_TOGGLE, ACT_SAVE, ACT_LOAD, ACT_RESET, ACT_EXPORT, ACT_EXPORT_SAVED, ACT_MARK, ACT_WAYPOINTS, ACT_FILE, ACT_SETTINGS,
|
||||||
ACT_SHARE_NOW };
|
ACT_SHARE_NOW, ACT_TRACKBACK };
|
||||||
// The action popup is multi-level: a short main menu, plus "Trail file…" and
|
// The action popup is multi-level: a short main menu, plus "Trail file…" and
|
||||||
// "Settings…" submenus. _menu_level tracks which is open so input is routed
|
// "Settings…" submenus. _menu_level tracks which is open so input is routed
|
||||||
// correctly (settings rows cycle with LEFT/RIGHT; everything else is Enter).
|
// correctly (settings rows cycle with LEFT/RIGHT; everything else is Enter).
|
||||||
@@ -192,6 +192,7 @@ public:
|
|||||||
break;
|
break;
|
||||||
case ACT_MARK: _wp.markHere(); break;
|
case ACT_MARK: _wp.markHere(); break;
|
||||||
case ACT_WAYPOINTS: _wp.openList(); break;
|
case ACT_WAYPOINTS: _wp.openList(); break;
|
||||||
|
case ACT_TRACKBACK: _wp.startTrackBack(); break;
|
||||||
case ACT_SAVE: handleSave(); break;
|
case ACT_SAVE: handleSave(); break;
|
||||||
case ACT_LOAD: handleLoad(); break;
|
case ACT_LOAD: handleLoad(); break;
|
||||||
case ACT_RESET: handleReset(); break;
|
case ACT_RESET: handleReset(); break;
|
||||||
@@ -353,6 +354,8 @@ private:
|
|||||||
// "Waypoints" opens the nav list — always available; it hosts the list,
|
// "Waypoints" opens the nav list — always available; it hosts the list,
|
||||||
// backtrack (Trail-start row) and the "+ Add by coords" entry.
|
// backtrack (Trail-start row) and the "+ Add by coords" entry.
|
||||||
pushAction(ACT_WAYPOINTS, "Waypoints...");
|
pushAction(ACT_WAYPOINTS, "Waypoints...");
|
||||||
|
// Track back retraces the recorded route to its start; needs ≥2 points.
|
||||||
|
if (_store->count() >= 2) pushAction(ACT_TRACKBACK, "Track back");
|
||||||
pushAction(ACT_SHARE_NOW, "Share my pos");
|
pushAction(ACT_SHARE_NOW, "Share my pos");
|
||||||
if (fileMenuHasItems()) pushAction(ACT_FILE, "Trail file...");
|
if (fileMenuHasItems()) pushAction(ACT_FILE, "Trail file...");
|
||||||
pushAction(ACT_SETTINGS, "Settings...");
|
pushAction(ACT_SETTINGS, "Settings...");
|
||||||
|
|||||||
@@ -20,11 +20,18 @@ class WaypointsView {
|
|||||||
TrailStore* _store;
|
TrailStore* _store;
|
||||||
|
|
||||||
// Sub-modes layered over the trail views. OFF = the component is dormant.
|
// Sub-modes layered over the trail views. OFF = the component is dormant.
|
||||||
enum Mode { OFF, LIST, NAV, ADD, AVG };
|
enum Mode { OFF, LIST, NAV, ADD, AVG, TRACKBACK };
|
||||||
uint8_t _mode = OFF;
|
uint8_t _mode = OFF;
|
||||||
int _sel = 0;
|
int _sel = 0;
|
||||||
int _scroll = 0;
|
int _scroll = 0;
|
||||||
|
|
||||||
|
// Track-back (Tools › Trail › Track back): retrace the recorded trail in
|
||||||
|
// reverse using NavView. _tb_idx is the current target breadcrumb; it walks
|
||||||
|
// down to 0 (the start) as each is reached within TB_ARRIVE_M.
|
||||||
|
static const int TB_ARRIVE_M = 20;
|
||||||
|
int _tb_idx = 0;
|
||||||
|
navview::EtaTracker _tb_eta;
|
||||||
|
|
||||||
// GPS averaging (Tools › Trail › Settings › Mark avg). When enabled, markHere()
|
// GPS averaging (Tools › Trail › Settings › Mark avg). When enabled, markHere()
|
||||||
// accumulates fixes for gps_avg_idx seconds and marks the mean position — a
|
// accumulates fixes for gps_avg_idx seconds and marks the mean position — a
|
||||||
// steadier mark than one instantaneous fix. Sampling runs in poll() on a 1 s
|
// steadier mark than one instantaneous fix. Sampling runs in poll() on a 1 s
|
||||||
@@ -226,6 +233,20 @@ class WaypointsView {
|
|||||||
navview::draw(display, have, mylat, mylon, tlat, tlon, label, cogv, cog, useImperial());
|
navview::draw(display, have, mylat, mylon, tlat, tlon, label, cogv, cog, useImperial());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Navigate to the current track-back breadcrumb. The header doubles as the
|
||||||
|
// progress readout: "Trail start" on the last leg, else points still to go.
|
||||||
|
void renderTrackBack(DisplayDriver& display) {
|
||||||
|
if (_tb_idx < 0 || _tb_idx >= _store->count()) { _mode = OFF; return; }
|
||||||
|
const TrailPoint& t = _store->at(_tb_idx);
|
||||||
|
int32_t mylat, mylon; bool have = ownPos(mylat, mylon);
|
||||||
|
int cog; bool cogv = _task->currentCourse(cog);
|
||||||
|
char label[20];
|
||||||
|
if (_tb_idx == 0) snprintf(label, sizeof(label), "Trail start");
|
||||||
|
else snprintf(label, sizeof(label), "Back: %d pt", _tb_idx);
|
||||||
|
navview::draw(display, have, mylat, mylon, t.lat_1e6, t.lon_1e6,
|
||||||
|
label, cogv, cog, useImperial(), &_tb_eta);
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve a combined-list row to a nav target. Row 0 is the trail start when
|
// Resolve a combined-list row to a nav target. Row 0 is the trail start when
|
||||||
// a trail exists; the rest are saved waypoints.
|
// a trail exists; the rest are saved waypoints.
|
||||||
bool rowTarget(int row, int32_t& lat, int32_t& lon, const char*& label) {
|
bool rowTarget(int row, int32_t& lat, int32_t& lon, const char*& label) {
|
||||||
@@ -271,11 +292,34 @@ public:
|
|||||||
_mode = AVG;
|
_mode = AVG;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accumulate GPS fixes while averaging. Driven from TrailScreen::poll() so it
|
// Retrace the recorded trail back to its start. Snaps onto the route at the
|
||||||
// ticks on the main loop, not on the (slow on e-ink) render cadence. No-op
|
// nearest recorded point, then NavView guides to each earlier breadcrumb in
|
||||||
// unless an averaging window is open.
|
// turn (poll() advances the target) until the start is reached.
|
||||||
|
void startTrackBack() {
|
||||||
|
if (_store->count() < 2) { _task->showAlert("No trail", 1000); return; }
|
||||||
|
int idx = _store->count() - 1; // default: the newest end of the trail
|
||||||
|
int32_t lat, lon;
|
||||||
|
if (ownPos(lat, lon)) { // else snap to the nearest recorded point
|
||||||
|
float best = 1e30f;
|
||||||
|
for (int i = 0; i < _store->count(); i++) {
|
||||||
|
float d = geo::haversineKm(lat, lon, _store->at(i).lat_1e6, _store->at(i).lon_1e6);
|
||||||
|
if (d < best) { best = d; idx = i; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_tb_idx = idx;
|
||||||
|
_tb_eta.reset();
|
||||||
|
_mode = TRACKBACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driven from TrailScreen::poll() so the position-tracking sub-modes tick on
|
||||||
|
// the main loop, not on the (slow on e-ink) render cadence.
|
||||||
void poll() {
|
void poll() {
|
||||||
if (_mode != AVG) return;
|
if (_mode == AVG) pollAvg();
|
||||||
|
else if (_mode == TRACKBACK) pollTrackBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate GPS fixes while averaging; mark the mean when the window closes.
|
||||||
|
void pollAvg() {
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if ((int32_t)(now - _avg_next_ms) >= 0) {
|
if ((int32_t)(now - _avg_next_ms) >= 0) {
|
||||||
int32_t lat, lon;
|
int32_t lat, lon;
|
||||||
@@ -290,6 +334,18 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advance the track-back target when the current breadcrumb is reached; once
|
||||||
|
// at the start (index 0) and within range, announce arrival and exit.
|
||||||
|
void pollTrackBack() {
|
||||||
|
int32_t lat, lon;
|
||||||
|
if (!ownPos(lat, lon)) return;
|
||||||
|
float d_m = geo::haversineKm(lat, lon, _store->at(_tb_idx).lat_1e6,
|
||||||
|
_store->at(_tb_idx).lon_1e6) * 1000.0f;
|
||||||
|
if (d_m > (float)TB_ARRIVE_M) return;
|
||||||
|
if (_tb_idx > 0) { _tb_idx--; _tb_eta.reset(); }
|
||||||
|
else { _task->showAlert("Back at start", 1500); _mode = OFF; }
|
||||||
|
}
|
||||||
|
|
||||||
// Only called while active().
|
// Only called while active().
|
||||||
int render(DisplayDriver& display) {
|
int render(DisplayDriver& display) {
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
@@ -297,6 +353,7 @@ public:
|
|||||||
if (_kb_active) return _task->keyboard().render(display); // keyboard owns the screen
|
if (_kb_active) return _task->keyboard().render(display); // keyboard owns the screen
|
||||||
if (_mode == ADD) { renderAddForm(display); return 1000; }
|
if (_mode == ADD) { renderAddForm(display); return 1000; }
|
||||||
if (_mode == AVG) { renderAvg(display); return display.isEink() ? 1000 : 300; }
|
if (_mode == AVG) { renderAvg(display); return display.isEink() ? 1000 : 300; }
|
||||||
|
if (_mode == TRACKBACK) { renderTrackBack(display); return 1000; }
|
||||||
if (_mode == NAV) { renderWpNav(display); return 1000; }
|
if (_mode == NAV) { renderWpNav(display); return 1000; }
|
||||||
renderWpList(display); // LIST
|
renderWpList(display); // LIST
|
||||||
if (_ctx.active) _ctx.render(display);
|
if (_ctx.active) _ctx.render(display);
|
||||||
@@ -400,6 +457,13 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track-back — launched from the action menu, so Cancel returns to the
|
||||||
|
// trail views (not the waypoint list).
|
||||||
|
if (_mode == TRACKBACK) {
|
||||||
|
if (c == KEY_CANCEL) _mode = OFF;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// List (row 0 is the synthetic "Trail start" when a trail exists; the final
|
// List (row 0 is the synthetic "Trail start" when a trail exists; the final
|
||||||
// row is "+ Add by coords"). Cancel returns control to the trail views.
|
// row is "+ Add by coords"). Cancel returns control to the trail views.
|
||||||
int n = wpListCount();
|
int n = wpListCount();
|
||||||
|
|||||||
Reference in New Issue
Block a user