feat(prefs): v2.0 settings file -- the struct as it is, behind a CRC

/prefs holds a header (magic, format version, size, CRC-32), the node's
position and NodePrefs as it is in memory, in place of /new_prefs' two
hand-kept field lists and 0xC0DE00xx schema bumps. A new field goes at the
end of the struct; moving one bumps PREFS_VERSION. sanitize() keeps every
value in range.

The first boot after an update from before 2.0 imports only the first
block of the old file, laid out the same in upstream MeshCore and Solo
(name, position, radio, BLE PIN, scope), then deletes it; everything else
starts from the defaults. Contacts, channels, scopes, waypoints and trails
are untouched.

Gone: the page-order and GPS-interval migrations, the unused
trail_interval_idx / trail_units_idx / repeat_extra_scopes; fav_sort_off
and msg_wake_screen_off become fav_sort and msg_wake (on by default).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-03 00:12:18 +02:00
co-authored by Claude Opus 5.5
parent 9597e1e2a4
commit c3782cb8e5
12 changed files with 268 additions and 893 deletions
+10 -6
View File
@@ -348,12 +348,16 @@ slow e-ink refresh:
Device settings live in one `NodePrefs` struct (`NodePrefs.h`), saved via
`the_mesh.savePrefs()` and loaded by `DataStore.cpp`. Rules when adding a field:
- **Append only**, and bump `NodePrefs::SCHEMA_SENTINEL`. Serialization is
binary-positional, so order is the on-disk format; never insert in the middle.
- Add a matching `rd(...)` in `DataStore::loadPrefsInt()` and a `file.write(...)`
in `savePrefs()`, in the same position, and **clamp on load** (an upgrader's
file lacks the field and reads stray bytes — clamp to a sane default). Saves
are atomic (temp-file + rename), so a crash mid-save can't corrupt settings.
- `/prefs` holds the struct as it is in memory, behind a header with a format
version and a CRC. **Add a new field at the end of the struct**; nothing
else is needed: an older file is shorter, so the field keeps its default
(set in `MyMesh`'s constructor).
- Moving, resizing or removing a field shifts the ones after it: bump
`NodePrefs::PREFS_VERSION`, and every device starts again from the
defaults.
- Give a field with a range a line in `sanitize()` (`DataStore.cpp`), so a
damaged byte reads as the default. Saves are atomic (temp-file + rename),
so a crash mid-save can't corrupt settings.
**The `_dirty` convention:** a multi-field editor screen mutates `_node_prefs`
live for instant feedback but only persists once, on exit, gated by a `_dirty`