mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
feat(trail): auto-save GPS trail on shutdown (Trail settings toggle)
Losing the whole route on a low-battery auto-shutdown was the worst solo-mode failure. New NodePrefs::trail_autosave_lowbatt toggle (Tools > Trail > Settings > Auto-save, default OFF): UITask::shutdown() writes the live trail to /trail when the toggle is on and _trail.count() > 0. The count guard stops an empty trail from wiping a previously saved one; it overwrites the same /trail file the manual Trail > Save uses. Schema bumped 0xC0DE001A -> 0xC0DE001B: append-only tail field with a load clamp, so pre-0x1B saves default to off. sizeof(NodePrefs) is unchanged (the byte fits existing padding after keyboard_type), so the tripwire assert stays 2496. Builds green: WioTrackerL1_companion_solo_dual (RAM 69.9%, Flash 62.8%). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -476,6 +476,12 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
if (_prefs.page_order[i] > NodePrefs::HPB_COUNT) _prefs.page_order[i] = 0;
|
||||
}
|
||||
|
||||
// → 0xC0DE001B: append trail_autosave_lowbatt at the tail. A pre-0x1B file has
|
||||
// the old sentinel bytes / EOF here; rd() zero-inits when absent and the clamp
|
||||
// below turns any stray value into 0 (off), so upgraders default to off.
|
||||
rd(&_prefs.trail_autosave_lowbatt, sizeof(_prefs.trail_autosave_lowbatt));
|
||||
if (_prefs.trail_autosave_lowbatt > 1) _prefs.trail_autosave_lowbatt = 0;
|
||||
|
||||
// Schema sentinel: bumped on layout changes. Mismatch means an older file
|
||||
// (or a different schema); rd() already zero-inits any fields not present,
|
||||
// so we just log it — next savePrefs writes the current sentinel.
|
||||
@@ -661,6 +667,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
// appended here so the on-disk head stays the original 11 bytes.
|
||||
file.write((uint8_t *)&_prefs.page_order[NodePrefs::PAGE_ORDER_LEN_V1],
|
||||
NodePrefs::PAGE_ORDER_LEN - NodePrefs::PAGE_ORDER_LEN_V1);
|
||||
file.write((uint8_t *)&_prefs.trail_autosave_lowbatt, sizeof(_prefs.trail_autosave_lowbatt));
|
||||
|
||||
// Tail sentinel — must be last. See NodePrefs::SCHEMA_SENTINEL. Its write is
|
||||
// the one we check: once the flash fills, writes return 0, so a good
|
||||
|
||||
@@ -289,6 +289,12 @@ struct NodePrefs { // persisted to file
|
||||
// (phone-keypad groups, cycled with repeated Enter presses — see KeyboardWidget.h).
|
||||
uint8_t keyboard_type;
|
||||
|
||||
// Auto-save the live GPS trail to /trail on shutdown (covers the low-battery
|
||||
// auto-shutdown, which otherwise loses the whole route). Only writes when the
|
||||
// trail has points and the toggle is on. 0 = off (default), 1 = on.
|
||||
// [Tools › Trail › Settings › Auto-save]
|
||||
uint8_t trail_autosave_lowbatt;
|
||||
|
||||
// Single source of truth for the live-share option tables (shared by the Map
|
||||
// UI labels and the auto-send engine in UITask).
|
||||
static const uint8_t LOC_SHARE_MOVE_COUNT = 4;
|
||||
@@ -351,7 +357,7 @@ struct NodePrefs { // persisted to file
|
||||
// adding/removing/reordering fields in DataStore::savePrefs/loadPrefsInt so
|
||||
// older saves are detected on load and skipped (zero-init defaults kept).
|
||||
// High 24 bits identify the file format; low byte is the schema revision.
|
||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE001A;
|
||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE001B;
|
||||
|
||||
// Bit-index for each home page. Used by page_order (entries store bit+1) and
|
||||
// by home_pages_mask. Single source of truth — both HomeScreen::pageBit/bitToPage
|
||||
|
||||
@@ -72,7 +72,7 @@ class TrailScreen : public UIScreen {
|
||||
// (Start/Stop tracking, Reset). PopupMenu stores label pointers verbatim, so
|
||||
// the labels live in member buffers that get refreshed in openActionMenu()
|
||||
// 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_AUTOSAVE, 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_TRACKBACK };
|
||||
// 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
|
||||
@@ -89,6 +89,7 @@ class TrailScreen : public UIScreen {
|
||||
char _act_grid_label[16];
|
||||
char _act_autopause_label[24];
|
||||
char _act_mark_avg_label[24];
|
||||
char _act_autosave_label[24];
|
||||
char _act_toggle_label[20];
|
||||
|
||||
static const int SUMMARY_ITEM_COUNT = 5;
|
||||
@@ -178,6 +179,7 @@ public:
|
||||
case ACT_UNITS:
|
||||
case ACT_GRID:
|
||||
case ACT_MARK_AVG:
|
||||
case ACT_AUTOSAVE:
|
||||
case ACT_AUTOPAUSE: cycleSetting(act, 1); reopenSettingsAt(sel); return true;
|
||||
case ACT_SHARE_NOW: shareMyLocationNow(); break;
|
||||
case ACT_TOGGLE:
|
||||
@@ -327,6 +329,8 @@ private:
|
||||
"Auto-pause: %s", NodePrefs::trailAutoPauseLabel(p ? p->trail_autopause_idx : 0));
|
||||
snprintf(_act_mark_avg_label, sizeof(_act_mark_avg_label),
|
||||
"Mark avg: %s", NodePrefs::gpsAvgLabel(p ? p->gps_avg_idx : 0));
|
||||
snprintf(_act_autosave_label, sizeof(_act_autosave_label),
|
||||
"Auto-save: %s", (p && p->trail_autosave_lowbatt) ? "ON" : "OFF");
|
||||
snprintf(_act_toggle_label, sizeof(_act_toggle_label),
|
||||
"%s tracking", _store->isActive() ? "Stop" : "Start");
|
||||
}
|
||||
@@ -394,6 +398,7 @@ private:
|
||||
pushAction(ACT_MIN_DIST, _act_min_dist_label);
|
||||
pushAction(ACT_AUTOPAUSE, _act_autopause_label);
|
||||
pushAction(ACT_MARK_AVG, _act_mark_avg_label);
|
||||
pushAction(ACT_AUTOSAVE, _act_autosave_label);
|
||||
if (_view == V_SUMMARY) pushAction(ACT_UNITS, _act_units_label);
|
||||
if (_view == V_MAP) pushAction(ACT_GRID, _act_grid_label);
|
||||
}
|
||||
@@ -406,6 +411,7 @@ private:
|
||||
if (act == ACT_GRID) { _map_grid = !_map_grid; refreshActionLabels(); return true; }
|
||||
if (act == ACT_AUTOPAUSE && p){ cycleAutoPause(p, dir); _cfg_dirty = true; refreshActionLabels(); return true; }
|
||||
if (act == ACT_MARK_AVG && p){ cycleMarkAvg(p, dir); _cfg_dirty = true; refreshActionLabels(); return true; }
|
||||
if (act == ACT_AUTOSAVE && p){ p->trail_autosave_lowbatt = !p->trail_autosave_lowbatt; _cfg_dirty = true; refreshActionLabels(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1927,6 +1927,19 @@ bool UITask::savePrefsIfDirty(bool& dirty) {
|
||||
void UITask::shutdown(bool restart){
|
||||
the_mesh.saveRTCTime();
|
||||
|
||||
// Auto-save the live GPS trail before power-off when the user enabled it
|
||||
// (Tools › Trail › Settings › Auto-save). This covers the low-battery
|
||||
// auto-shutdown, which otherwise loses the whole route. Overwrites /trail
|
||||
// (same file as the manual Trail › Save); guarded on count()>0 so an empty
|
||||
// trail can't wipe a previously saved one.
|
||||
if (_node_prefs && _node_prefs->trail_autosave_lowbatt && _trail.count() > 0) {
|
||||
DataStore* ds = the_mesh.getDataStore();
|
||||
if (ds) {
|
||||
File f = ds->openWrite("/trail");
|
||||
if (f) { _trail.writeTo(f); f.close(); }
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
/* note: we have a choice here -
|
||||
we can do a blocking buzzer.loop() with non-deterministic consequences
|
||||
|
||||
Reference in New Issue
Block a user