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
@@ -741,7 +741,7 @@ class MessagesScreen : public UIScreen {
if (fav_only && !fav) continue;
uint8_t k = rooms ? 0 : _history.dmHistCountForContact(c.id.pub_key);
if (k > 127) k = 127;
if (fav && !(p && p->fav_sort_off)) k += 128;
if (fav && (!p || p->fav_sort)) k += 128;
keys[_num_contacts] = k;
// Store the RAW table index (not the bare loop counter) -- every other
// call site in this file (render()'s mesh_idx, toggleFav(), the
@@ -772,7 +772,7 @@ class MessagesScreen : public UIScreen {
_channel_indices[_num_channels++] = (uint8_t)i;
}
// Favourites to the front, everything else keeping channel-slot order.
if (!p || p->fav_sort_off) return;
if (!p || !p->fav_sort) return;
int front = 0;
for (int i = 0; i < _num_channels; i++) {
if (!chIsFav(_channel_indices[i])) continue;
@@ -325,46 +325,23 @@ class SettingsScreen : public UIScreen {
}
// Initialises page_order to the default display sequence if not already set.
// Also repairs a partially-initialised order where CLOCK is absent; migrates
// older orders by inserting FAVOURITES after CLOCK; and appends any pages that
// are absent from a stale saved order (e.g. TOOLS/MESSAGES added by later firmware).
// Also repairs a partially-initialised order where CLOCK is absent, and
// appends any pages absent from a saved order (pages a later firmware added).
void ensurePageOrderInit(NodePrefs* p) const {
if (!p) return;
if (p->page_order_set == NodePrefs::PAGE_ORDER_MAGIC) {
bool has_clock = false;
bool has_fav = false;
int len = 0;
int clock_at = -1;
for (int i = 0; i < NodePrefs::PAGE_ORDER_LEN; i++) {
uint8_t v = p->page_order[i];
if (v < 1 || v > NodePrefs::HPB_COUNT) break;
if ((int)(v - 1) == NodePrefs::HPB_CLOCK) { has_clock = true; clock_at = i; }
if ((int)(v - 1) == NodePrefs::HPB_FAVOURITES) { has_fav = true; }
len = i + 1;
if ((int)(v - 1) == NodePrefs::HPB_CLOCK) has_clock = true;
}
if (!has_clock) {
// Corrupted/partial — full re-init below.
memset(p->page_order, 0, sizeof(p->page_order));
} else {
if (!has_fav) {
// Insert FAVOURITES right after CLOCK. Real orders are shorter than
// PAGE_ORDER_LEN so there's room; only a pathologically full order would
// drop its last entry, which buildVisibleOrder's fallback re-appends.
int insert_at = clock_at + 1;
// Guard against a saved order with all PAGE_ORDER_LEN slots already
// valid and CLOCK in the last one: insert_at would be PAGE_ORDER_LEN,
// one past the array, and the shift loop below wouldn't run (tail is
// clamped to the last index) to catch it -- the write would land one
// byte past page_order, into whatever NodePrefs field follows.
if (insert_at < NodePrefs::PAGE_ORDER_LEN) {
int tail = (len < NodePrefs::PAGE_ORDER_LEN) ? len : NodePrefs::PAGE_ORDER_LEN - 1;
for (int i = tail; i > insert_at; i--) p->page_order[i] = p->page_order[i - 1];
p->page_order[insert_at] = NodePrefs::HPB_FAVOURITES + 1;
}
}
// Append any pages that are absent from the saved order (e.g. added by a
// later firmware version). Recount first since the block above may have
// just inserted FAVOURITES.
// later firmware version).
{
uint16_t present = 0;
int cur_len = 0;
@@ -375,8 +352,8 @@ class SettingsScreen : public UIScreen {
cur_len++;
}
// Every page has a slot now (PAGE_ORDER_LEN == HPB_COUNT), so all pages
// are required — any missing from a stale saved order (SHUTDOWN and MAP
// for pre-0x0019 upgraders) is appended into the free tail slots below.
// are required — any missing from a saved order is appended into the
// free tail slots below.
static const uint8_t REQUIRED[] = {
NodePrefs::HPB_CLOCK, NodePrefs::HPB_FAVOURITES,
NodePrefs::HPB_RECENT, NodePrefs::HPB_RADIO,
+1 -1
View File
@@ -2202,7 +2202,7 @@ void UITask::onMessageArrived(const UiEvent& ev) {
}
if (_display != NULL && !_locked) {
bool wake_disabled = _node_prefs && (_node_prefs->msg_wake_screen_off || inQuietHours(*_node_prefs, rtc_clock.getCurrentTime()));
bool wake_disabled = _node_prefs && (!_node_prefs->msg_wake || inQuietHours(*_node_prefs, rtc_clock.getCurrentTime()));
if (!wake_disabled && !_display->isOn() && !isClientConnected()) { // wake for the msg unless an app (BLE/USB) is already showing it, or the user disabled msg-wake
_display->turnOn();
}