mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-08-02 02:06:11 +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;
|
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
|
// Schema sentinel: bumped on layout changes. Mismatch means an older file
|
||||||
// (or a different schema); rd() already zero-inits any fields not present,
|
// (or a different schema); rd() already zero-inits any fields not present,
|
||||||
// so we just log it — next savePrefs writes the current sentinel.
|
// 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.
|
// appended here so the on-disk head stays the original 11 bytes.
|
||||||
file.write((uint8_t *)&_prefs.page_order[NodePrefs::PAGE_ORDER_LEN_V1],
|
file.write((uint8_t *)&_prefs.page_order[NodePrefs::PAGE_ORDER_LEN_V1],
|
||||||
NodePrefs::PAGE_ORDER_LEN - 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
|
// 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
|
// 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).
|
// (phone-keypad groups, cycled with repeated Enter presses — see KeyboardWidget.h).
|
||||||
uint8_t keyboard_type;
|
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
|
// Single source of truth for the live-share option tables (shared by the Map
|
||||||
// UI labels and the auto-send engine in UITask).
|
// UI labels and the auto-send engine in UITask).
|
||||||
static const uint8_t LOC_SHARE_MOVE_COUNT = 4;
|
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
|
// adding/removing/reordering fields in DataStore::savePrefs/loadPrefsInt so
|
||||||
// older saves are detected on load and skipped (zero-init defaults kept).
|
// 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.
|
// 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
|
// 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
|
// 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
|
// (Start/Stop tracking, Reset). PopupMenu stores label pointers verbatim, so
|
||||||
// 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_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 };
|
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
|
||||||
@@ -89,6 +89,7 @@ class TrailScreen : public UIScreen {
|
|||||||
char _act_grid_label[16];
|
char _act_grid_label[16];
|
||||||
char _act_autopause_label[24];
|
char _act_autopause_label[24];
|
||||||
char _act_mark_avg_label[24];
|
char _act_mark_avg_label[24];
|
||||||
|
char _act_autosave_label[24];
|
||||||
char _act_toggle_label[20];
|
char _act_toggle_label[20];
|
||||||
|
|
||||||
static const int SUMMARY_ITEM_COUNT = 5;
|
static const int SUMMARY_ITEM_COUNT = 5;
|
||||||
@@ -178,6 +179,7 @@ public:
|
|||||||
case ACT_UNITS:
|
case ACT_UNITS:
|
||||||
case ACT_GRID:
|
case ACT_GRID:
|
||||||
case ACT_MARK_AVG:
|
case ACT_MARK_AVG:
|
||||||
|
case ACT_AUTOSAVE:
|
||||||
case ACT_AUTOPAUSE: cycleSetting(act, 1); reopenSettingsAt(sel); return true;
|
case ACT_AUTOPAUSE: cycleSetting(act, 1); reopenSettingsAt(sel); return true;
|
||||||
case ACT_SHARE_NOW: shareMyLocationNow(); break;
|
case ACT_SHARE_NOW: shareMyLocationNow(); break;
|
||||||
case ACT_TOGGLE:
|
case ACT_TOGGLE:
|
||||||
@@ -327,6 +329,8 @@ private:
|
|||||||
"Auto-pause: %s", NodePrefs::trailAutoPauseLabel(p ? p->trail_autopause_idx : 0));
|
"Auto-pause: %s", NodePrefs::trailAutoPauseLabel(p ? p->trail_autopause_idx : 0));
|
||||||
snprintf(_act_mark_avg_label, sizeof(_act_mark_avg_label),
|
snprintf(_act_mark_avg_label, sizeof(_act_mark_avg_label),
|
||||||
"Mark avg: %s", NodePrefs::gpsAvgLabel(p ? p->gps_avg_idx : 0));
|
"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),
|
snprintf(_act_toggle_label, sizeof(_act_toggle_label),
|
||||||
"%s tracking", _store->isActive() ? "Stop" : "Start");
|
"%s tracking", _store->isActive() ? "Stop" : "Start");
|
||||||
}
|
}
|
||||||
@@ -394,6 +398,7 @@ private:
|
|||||||
pushAction(ACT_MIN_DIST, _act_min_dist_label);
|
pushAction(ACT_MIN_DIST, _act_min_dist_label);
|
||||||
pushAction(ACT_AUTOPAUSE, _act_autopause_label);
|
pushAction(ACT_AUTOPAUSE, _act_autopause_label);
|
||||||
pushAction(ACT_MARK_AVG, _act_mark_avg_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_SUMMARY) pushAction(ACT_UNITS, _act_units_label);
|
||||||
if (_view == V_MAP) pushAction(ACT_GRID, _act_grid_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_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_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_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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1927,6 +1927,19 @@ bool UITask::savePrefsIfDirty(bool& dirty) {
|
|||||||
void UITask::shutdown(bool restart){
|
void UITask::shutdown(bool restart){
|
||||||
the_mesh.saveRTCTime();
|
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
|
#ifdef PIN_BUZZER
|
||||||
/* note: we have a choice here -
|
/* note: we have a choice here -
|
||||||
we can do a blocking buzzer.loop() with non-deterministic consequences
|
we can do a blocking buzzer.loop() with non-deterministic consequences
|
||||||
|
|||||||
Reference in New Issue
Block a user