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
@@ -290,7 +290,7 @@ protected:
void sortStored() {
uint32_t now_ts = rtc_clock.getCurrentTime();
const bool fav_first = !(_prefs && _prefs->fav_sort_off);
const bool fav_first = (!_prefs || _prefs->fav_sort);
for (int i = 0; i < _count - 1; i++) {
int best = i;
for (int j = i + 1; j < _count; j++) {
@@ -195,8 +195,7 @@ static const Setting ALL[] = {
IDX("Brightness", nullptr, SEC_DISPLAY, display_brightness, 5, optBrightness, applyDisplay),
MAP("Screen off after", "Without a touch", SEC_DISPLAY, auto_off_secs, AUTO_OFF, optAutoOff, nullptr),
MAP("Wake on message", "Screen on for new messages", SEC_DISPLAY, msg_wake_screen_off, INVERTED,
nullptr, nullptr),
SW("Wake on message", "Screen on for new messages", SEC_DISPLAY, msg_wake, nullptr),
SW("Lock screen", "Slide to unlock after sleep", SEC_DISPLAY, auto_lock, nullptr),
IDX("Lock clock", "Big, or compact with the clock fields", SEC_DISPLAY, lock_compact, 2, optLockLook, nullptr),
IDX("Battery display", "In the status bar", SEC_DISPLAY, batt_display_mode, 3, optBattDisplay, nullptr),
@@ -215,7 +214,7 @@ static const Setting ALL[] = {
IDX("Contact expiry", "Prune after inactivity", SEC_CONTACTS, contact_expiry_idx,
NodePrefs::CONTACT_EXPIRY_COUNT, optExpiry, nullptr),
MAP("Favourites first", "In contact and node lists", SEC_CONTACTS, fav_sort_off, INVERTED, nullptr, nullptr),
SW("Favourites first", "In contact and node lists", SEC_CONTACTS, fav_sort, nullptr),
// On / Off / Auto spans two fields (soundctl::setMode): the frontend's own row.
IDX("Volume", nullptr, SEC_SOUND, buzzer_volume, 5, optVolume, applySound),
@@ -234,12 +233,12 @@ static const int COUNT = (int)(sizeof(ALL) / sizeof(ALL[0]));
// eight-character value); a setting not listed uses its own.
static const struct { uint16_t offset; const char* text; } SHORT_LABELS[] = {
{ NP_OFF(display_brightness), "Bright" }, { NP_OFF(auto_off_secs), "Auto off" },
{ NP_OFF(msg_wake_screen_off), "Msg wake" }, { NP_OFF(auto_lock), "Auto lock" },
{ NP_OFF(msg_wake), "Msg wake" }, { NP_OFF(auto_lock), "Auto lock" },
{ NP_OFF(batt_display_mode), "Batt disp" }, { NP_OFF(low_batt_mv), "Low batt" },
{ NP_OFF(gps_interval), "GPS pwr" }, { NP_OFF(clock_12h), "12h clock" },
{ NP_OFF(clock_hide_seconds), "Seconds" }, { NP_OFF(units_imperial), "Imperial" },
{ NP_OFF(dm_resend_count), "Resend" }, { NP_OFF(contact_expiry_idx), "Expire" },
{ NP_OFF(fav_sort_off), "Favs top" }, { NP_OFF(buzzer_volume), "Buzzer vol" },
{ NP_OFF(fav_sort), "Favs top" }, { NP_OFF(buzzer_volume), "Buzzer vol" },
{ NP_OFF(quiet_hours), "Quiet hrs" }, { NP_OFF(quiet_from), " from" },
{ NP_OFF(quiet_to), " until" }, { NP_OFF(notif_melody_dm), "DM sound" },
{ NP_OFF(notif_melody_ch), "Ch sound" }, { NP_OFF(notif_melody_ad), "AD sound" },