mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
Merge branch 'main' into feat/t9-onscreen-keyboard
This commit is contained in:
@@ -319,7 +319,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
rd(&_prefs.clock_12h, sizeof(_prefs.clock_12h));
|
||||
rd(&_prefs.use_lemon_font, sizeof(_prefs.use_lemon_font));
|
||||
rd(&_prefs.display_rotation, sizeof(_prefs.display_rotation));
|
||||
rd(_prefs.page_order, sizeof(_prefs.page_order));
|
||||
rd(_prefs.page_order, NodePrefs::PAGE_ORDER_LEN_V1); // tail slots read below (append-only)
|
||||
rd(&_prefs.joystick_rotation, sizeof(_prefs.joystick_rotation));
|
||||
#if !FEAT_JOYSTICK_ROTATION_SETTING
|
||||
// No UI to change it on this build, so force the default — this also corrects
|
||||
@@ -464,6 +464,17 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
// which is out of range — fall back to the default of 2 resends.
|
||||
if (_prefs.dm_resend_count > 5) _prefs.dm_resend_count = 2;
|
||||
|
||||
// → 0xC0DE0019: page_order grew 11 → 13 so Shutdown and Map become reorderable.
|
||||
// The extra slots are appended here at the tail (not inline) so a pre-0x19 save,
|
||||
// whose order ended right after the old 11 bytes, still loads without shifting
|
||||
// every field after it. On such files these bytes are the old sentinel tail or
|
||||
// EOF, so clamp anything out of range back to 0 (empty); ensurePageOrderInit
|
||||
// then appends the missing pages into the freed slots.
|
||||
for (uint8_t i = NodePrefs::PAGE_ORDER_LEN_V1; i < NodePrefs::PAGE_ORDER_LEN; i++) {
|
||||
rd(&_prefs.page_order[i], sizeof(_prefs.page_order[i]));
|
||||
if (_prefs.page_order[i] > NodePrefs::HPB_COUNT) _prefs.page_order[i] = 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.
|
||||
@@ -472,12 +483,21 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
if (sentinel != NodePrefs::SCHEMA_SENTINEL) {
|
||||
MESH_DEBUG_PRINTLN("prefs schema sentinel mismatch: got 0x%08X, expected 0x%08X — re-saving on next change",
|
||||
(unsigned)sentinel, (unsigned)NodePrefs::SCHEMA_SENTINEL);
|
||||
// 0xC0DE0001 → 0xC0DE0002: FAVOURITES home page added. Older saves have it
|
||||
// implicitly off (their mask doesn't include HP_FAVOURITES); turn it on so
|
||||
// upgraded users see the new page by default and can toggle it later.
|
||||
if (_prefs.home_pages_mask != 0) {
|
||||
// 0xC0DE0001 → 0xC0DE0002: FAVOURITES home page added. Only pre-0x0002 saves
|
||||
// lack the bit; turn it on once so those upgraders see the new page by
|
||||
// default. Must be gated to that transition — running it on every sentinel
|
||||
// mismatch (as it did) re-enabled Favourites on each firmware update,
|
||||
// clobbering a user who had deliberately hidden it.
|
||||
if (sentinel < 0xC0DE0002 && _prefs.home_pages_mask != 0) {
|
||||
_prefs.home_pages_mask |= NodePrefs::HP_FAVOURITES;
|
||||
}
|
||||
// 0xC0DE0017 → 0xC0DE0018: MAP home page moved into home_pages_mask (it was
|
||||
// always-on before, with no visibility toggle). Turn its bit on once for
|
||||
// pre-0x0018 saves so upgraders keep seeing the page; gated to this
|
||||
// transition so a user who later hides it isn't overridden on the next update.
|
||||
if (sentinel < 0xC0DE0018 && _prefs.home_pages_mask != 0) {
|
||||
_prefs.home_pages_mask |= NodePrefs::HP_MAP;
|
||||
}
|
||||
// 0xC0DE0003 → 0xC0DE0004: trail_units_idx added after trail_min_delta_idx.
|
||||
// On a 0xC0DE0003 file the sentinel bytes sit where trail_units_idx is now,
|
||||
// so rd() picks up 0x03 (low byte of the old sentinel) — reset just that
|
||||
@@ -580,7 +600,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.clock_12h, sizeof(_prefs.clock_12h));
|
||||
file.write((uint8_t *)&_prefs.use_lemon_font, sizeof(_prefs.use_lemon_font));
|
||||
file.write((uint8_t *)&_prefs.display_rotation, sizeof(_prefs.display_rotation));
|
||||
file.write((uint8_t *)_prefs.page_order, sizeof(_prefs.page_order));
|
||||
file.write((uint8_t *)_prefs.page_order, NodePrefs::PAGE_ORDER_LEN_V1); // head; tail slots written below
|
||||
file.write((uint8_t *)&_prefs.joystick_rotation, sizeof(_prefs.joystick_rotation));
|
||||
file.write((uint8_t *)&_prefs.eink_full_refresh_every, sizeof(_prefs.eink_full_refresh_every));
|
||||
file.write((uint8_t *)&_prefs.page_order_set, sizeof(_prefs.page_order_set));
|
||||
@@ -636,6 +656,10 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.alarm_hour, sizeof(_prefs.alarm_hour));
|
||||
file.write((uint8_t *)&_prefs.alarm_min, sizeof(_prefs.alarm_min));
|
||||
file.write((uint8_t *)&_prefs.keyboard_type, sizeof(_prefs.keyboard_type));
|
||||
// page_order tail slots (see loadPrefsInt): entries beyond PAGE_ORDER_LEN_V1,
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
|
||||
@@ -1504,7 +1504,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.ringtone2_bpm_idx = 2; // 120 bpm default
|
||||
_prefs.notif_melody_ad = 0; // built-in advert sound by default
|
||||
_prefs.advert_sound_scope = ADVERT_SOUND_SCOPE_ALL; // sound every advert by default
|
||||
_prefs.home_pages_mask = 0x01FF; // all pages visible
|
||||
_prefs.home_pages_mask = NodePrefs::HP_ALL; // all pages visible (incl. Favourites + Map)
|
||||
_prefs.bot_enabled = 0;
|
||||
_prefs.bot_channel_enabled = 0;
|
||||
_prefs.bot_channel_idx = 0;
|
||||
|
||||
@@ -125,9 +125,11 @@ struct NodePrefs { // persisted to file
|
||||
// Home screen page order: each byte = HomePageBit + 1. 0 terminates the list.
|
||||
// Validity gated by page_order_set magic (see below) — not by entry value range,
|
||||
// so a junk byte in 1..HPB_COUNT cannot trigger custom-order mode.
|
||||
// (Array length = HPB_COUNT, declared as literal so the field offset is stable
|
||||
// across builds that add new HomePageBit entries.)
|
||||
uint8_t page_order[11];
|
||||
// Declared as a literal (not HPB_COUNT) so the field offset stays stable across
|
||||
// builds that add HomePageBit entries. The first PAGE_ORDER_LEN_V1 bytes persist
|
||||
// at this offset (backward-compatible); the remaining slots live at the file
|
||||
// tail (see DataStore) so pre-0x0019 saves still load without shifting.
|
||||
uint8_t page_order[13];
|
||||
uint8_t joystick_rotation; // 0-3 steps CW; independent of display_rotation
|
||||
uint8_t eink_full_refresh_every; // index into {0,5,10,20,30}: full refresh every N partials (0=off)
|
||||
uint8_t page_order_set; // 0xA5 = page_order is user-configured; anything else = use default
|
||||
@@ -349,7 +351,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 = 0xC0DE0018;
|
||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0019;
|
||||
|
||||
// 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
|
||||
@@ -367,13 +369,19 @@ struct NodePrefs { // persisted to file
|
||||
HPB_SETTINGS = 9,
|
||||
HPB_QUICK_MSG = 10,
|
||||
HPB_FAVOURITES = 11,
|
||||
HPB_COUNT = 12,
|
||||
HPB_MAP = 12,
|
||||
HPB_COUNT = 13,
|
||||
};
|
||||
|
||||
// Length of the persisted page_order[] array. Stable across firmware versions
|
||||
// (don't grow without a schema migration). When HPB_COUNT exceeds this, extra
|
||||
// visible pages are appended at navigation time via buildVisibleOrder fallback.
|
||||
static const uint8_t PAGE_ORDER_LEN = 11;
|
||||
// Number of usable page_order[] slots — one per home page (== HPB_COUNT), so
|
||||
// every page can be reordered. Any page still missing from a saved order is
|
||||
// appended at navigation time via buildVisibleOrder's fallback.
|
||||
static const uint8_t PAGE_ORDER_LEN = 13;
|
||||
// Bytes of page_order persisted at the original file offset. Slots beyond this
|
||||
// (PAGE_ORDER_LEN - PAGE_ORDER_LEN_V1) are stored at the file tail, so a
|
||||
// pre-0x0019 save — whose order was exactly this long — loads without shifting
|
||||
// every field written after page_order.
|
||||
static const uint8_t PAGE_ORDER_LEN_V1 = 11;
|
||||
|
||||
// Bitmasks for home_pages_mask (bit=1 → page visible; 0 field = all visible).
|
||||
// SETTINGS and QUICK_MSG have no mask bit — they're always visible.
|
||||
@@ -387,14 +395,15 @@ struct NodePrefs { // persisted to file
|
||||
static const uint16_t HP_TOOLS = 1 << HPB_TOOLS;
|
||||
static const uint16_t HP_SHUTDOWN = 1 << HPB_SHUTDOWN;
|
||||
static const uint16_t HP_FAVOURITES = 1 << HPB_FAVOURITES;
|
||||
static const uint16_t HP_ALL = 0x01FF | HP_FAVOURITES;
|
||||
static const uint16_t HP_MAP = 1 << HPB_MAP;
|
||||
static const uint16_t HP_ALL = 0x01FF | HP_FAVOURITES | HP_MAP;
|
||||
|
||||
// Label for home page by bit-index; returns "" for out-of-range.
|
||||
// Array indices match HomePageBit values.
|
||||
static const char* homePageLabel(uint8_t bit) {
|
||||
static const char* labels[HPB_COUNT] = {
|
||||
"Clock", "Recent", "Radio", "Bluetooth", "Advert",
|
||||
"GPS", "Sensors", "Tools", "Shutdown", "Settings", "Messages", "Favourites"
|
||||
"GPS", "Sensors", "Tools", "Shutdown", "Settings", "Messages", "Favourites", "Map"
|
||||
};
|
||||
return (bit < HPB_COUNT) ? labels[bit] : "";
|
||||
}
|
||||
@@ -432,7 +441,7 @@ struct NodePrefs { // persisted to file
|
||||
// 3. clamp it on load (an upgrader's file lacks it → stray bytes)
|
||||
// 4. bump SCHEMA_SENTINEL's low byte
|
||||
// (Padding can also shift sizeof; a "false" trip just means re-check + rebump.)
|
||||
static_assert(sizeof(NodePrefs) == 2488,
|
||||
static_assert(sizeof(NodePrefs) == 2496,
|
||||
"NodePrefs layout changed — sync DataStore save/load + clamp, bump "
|
||||
"SCHEMA_SENTINEL, then update this size (see steps above).");
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class SettingsScreen : public UIScreen {
|
||||
HOME_SENSORS,
|
||||
#endif
|
||||
HOME_SETTINGS, HOME_QUICK_MSG,
|
||||
HOME_TOOLS, HOME_SHUTDOWN,
|
||||
HOME_TOOLS, HOME_SHUTDOWN, HOME_MAP,
|
||||
// Radio section
|
||||
SECTION_RADIO,
|
||||
TX_POWER,
|
||||
@@ -220,7 +220,7 @@ class SettingsScreen : public UIScreen {
|
||||
return item == HOME_CLOCK || item == HOME_RECENT || item == HOME_RADIO ||
|
||||
item == HOME_BT || item == HOME_ADVERT || item == HOME_TOOLS ||
|
||||
item == HOME_SHUTDOWN || item == HOME_SETTINGS || item == HOME_QUICK_MSG ||
|
||||
item == HOME_FAVOURITES
|
||||
item == HOME_FAVOURITES || item == HOME_MAP
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
|| item == HOME_GPS
|
||||
#endif
|
||||
@@ -272,6 +272,7 @@ class SettingsScreen : public UIScreen {
|
||||
#endif
|
||||
if (item == HOME_TOOLS) return NodePrefs::HPB_TOOLS;
|
||||
if (item == HOME_SHUTDOWN) return NodePrefs::HPB_SHUTDOWN;
|
||||
if (item == HOME_MAP) return NodePrefs::HPB_MAP;
|
||||
if (item == HOME_SETTINGS) return NodePrefs::HPB_SETTINGS;
|
||||
if (item == HOME_QUICK_MSG) return NodePrefs::HPB_QUICK_MSG;
|
||||
return -1;
|
||||
@@ -313,9 +314,9 @@ class SettingsScreen : public UIScreen {
|
||||
memset(p->page_order, 0, sizeof(p->page_order));
|
||||
} else {
|
||||
if (!has_fav) {
|
||||
// Insert FAVOURITES right after CLOCK. If the array is full, the last
|
||||
// entry (typically SHUTDOWN, which still appears via the missing-page
|
||||
// fallback at end of nav) is overwritten to make room.
|
||||
// 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;
|
||||
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];
|
||||
@@ -333,6 +334,9 @@ class SettingsScreen : public UIScreen {
|
||||
present |= (uint16_t)(1u << (v - 1));
|
||||
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.
|
||||
static const uint8_t REQUIRED[] = {
|
||||
NodePrefs::HPB_CLOCK, NodePrefs::HPB_FAVOURITES,
|
||||
NodePrefs::HPB_RECENT, NodePrefs::HPB_RADIO,
|
||||
@@ -343,26 +347,9 @@ class SettingsScreen : public UIScreen {
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
NodePrefs::HPB_SENSORS,
|
||||
#endif
|
||||
NodePrefs::HPB_SETTINGS, NodePrefs::HPB_TOOLS, NodePrefs::HPB_QUICK_MSG,
|
||||
NodePrefs::HPB_SETTINGS, NodePrefs::HPB_MAP, NodePrefs::HPB_TOOLS,
|
||||
NodePrefs::HPB_QUICK_MSG, NodePrefs::HPB_SHUTDOWN,
|
||||
};
|
||||
// If any required page is missing, evict SHUTDOWN to make room — it is
|
||||
// handled by buildVisibleOrder's fallback and need not be in the explicit
|
||||
// list. This frees a slot regardless of whether the array is full or not,
|
||||
// so multiple missing pages (e.g. TOOLS + QUICK_MSG) can all be appended.
|
||||
bool any_missing = false;
|
||||
for (int ri = 0; ri < (int)(sizeof(REQUIRED)/sizeof(REQUIRED[0])); ri++) {
|
||||
if (!(present & (uint16_t)(1u << REQUIRED[ri]))) { any_missing = true; break; }
|
||||
}
|
||||
if (any_missing && (present & (uint16_t)(1u << NodePrefs::HPB_SHUTDOWN))) {
|
||||
for (int i = 0; i < cur_len; i++) {
|
||||
if (p->page_order[i] == NodePrefs::HPB_SHUTDOWN + 1) {
|
||||
for (int j = i; j < cur_len - 1; j++) p->page_order[j] = p->page_order[j + 1];
|
||||
p->page_order[--cur_len] = 0;
|
||||
present &= ~(uint16_t)(1u << NodePrefs::HPB_SHUTDOWN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int ri = 0; ri < (int)(sizeof(REQUIRED)/sizeof(REQUIRED[0])); ri++) {
|
||||
uint8_t bit = REQUIRED[ri];
|
||||
if (!(present & (uint16_t)(1u << bit)) && cur_len < NodePrefs::PAGE_ORDER_LEN)
|
||||
@@ -372,10 +359,9 @@ class SettingsScreen : public UIScreen {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Default: CLOCK FAVOURITES RECENT RADIO BT ADVERT [GPS] [SENSORS] SETTINGS TOOLS MESSAGES
|
||||
// SHUTDOWN is omitted from the explicit list (PAGE_ORDER_LEN = 11 leaves room for the
|
||||
// common case GPS+SENSORS+all-others) and appended by buildVisibleOrder's missing-page
|
||||
// fallback at the end of the navigation sequence.
|
||||
// Default: CLOCK FAVOURITES RECENT RADIO BT ADVERT [GPS] [SENSORS] SETTINGS
|
||||
// MAP TOOLS MESSAGES SHUTDOWN — mirrors the home-carousel enum order. Every
|
||||
// page has an explicit slot now (PAGE_ORDER_LEN == HPB_COUNT).
|
||||
int j = 0;
|
||||
p->page_order[j++] = NodePrefs::HPB_CLOCK + 1;
|
||||
p->page_order[j++] = NodePrefs::HPB_FAVOURITES + 1;
|
||||
@@ -390,8 +376,10 @@ class SettingsScreen : public UIScreen {
|
||||
p->page_order[j++] = NodePrefs::HPB_SENSORS + 1;
|
||||
#endif
|
||||
p->page_order[j++] = NodePrefs::HPB_SETTINGS + 1;
|
||||
p->page_order[j++] = NodePrefs::HPB_MAP + 1;
|
||||
p->page_order[j++] = NodePrefs::HPB_TOOLS + 1;
|
||||
p->page_order[j++] = NodePrefs::HPB_QUICK_MSG + 1;
|
||||
p->page_order[j++] = NodePrefs::HPB_SHUTDOWN + 1;
|
||||
while (j < NodePrefs::PAGE_ORDER_LEN) p->page_order[j++] = 0;
|
||||
p->page_order_set = NodePrefs::PAGE_ORDER_MAGIC;
|
||||
}
|
||||
|
||||
@@ -359,6 +359,7 @@ class HomeScreen : public UIScreen {
|
||||
#endif
|
||||
if (page == TOOLS) return NodePrefs::HPB_TOOLS;
|
||||
if (page == SHUTDOWN) return NodePrefs::HPB_SHUTDOWN;
|
||||
if (page == MAP) return NodePrefs::HPB_MAP;
|
||||
return -1; // SETTINGS, QUICK_MSG always visible (no mask bit)
|
||||
}
|
||||
|
||||
@@ -382,6 +383,7 @@ class HomeScreen : public UIScreen {
|
||||
case NodePrefs::HPB_SHUTDOWN: return SHUTDOWN;
|
||||
case NodePrefs::HPB_SETTINGS: return SETTINGS;
|
||||
case NodePrefs::HPB_QUICK_MSG: return QUICK_MSG;
|
||||
case NodePrefs::HPB_MAP: return MAP;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user