mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-27 21:26:37 +00:00
feat(ui): message-wake toggle + all home pages visible by default
Two independent changes, requested together: 1. Settings > Sound > "Msg wake" (NodePrefs::msg_wake_screen_off, 0xC0DE002A) lets a user disable UITask::newMsg()'s existing behaviour of turning the display on for an incoming message when it was off and no companion app is connected. Stored inverted so both a fresh device and an existing saved-prefs file default to "on" (today's behaviour). New sim_test_set_msg_wake_disabled() hook verifies it without scripting Settings navigation -- confirmed end to end via a real A<->R<->B mesh in Playwright: message delivered while B's screen is off either wakes it (enabled) or doesn't (disabled), checked via real canvas pixels. 2. A brand-new or factory-reset device now shows ALL home pages by default instead of a curated 5-page carousel (MyMesh.cpp used to seed home_pages_mask = HP_DEFAULT; now seeds 0, which every other read site already treated as "all visible" -- HP_DEFAULT is now unused, removed). Existing users' saved masks are untouched. New sim_test_get_home_pages_mask() getter confirms a fresh instance reads back 0. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -611,6 +611,13 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
|||||||
rd(&_prefs.fav_sort_off, sizeof(_prefs.fav_sort_off));
|
rd(&_prefs.fav_sort_off, sizeof(_prefs.fav_sort_off));
|
||||||
if (_prefs.fav_sort_off > 1) _prefs.fav_sort_off = 0;
|
if (_prefs.fav_sort_off > 1) _prefs.fav_sort_off = 0;
|
||||||
|
|
||||||
|
// → 0xC0DE002A: append msg_wake_screen_off. Inverted (see NodePrefs), so
|
||||||
|
// both a pre-0x2A file's stray sentinel byte here and a file that ends
|
||||||
|
// before this field clamp/zero to 0 = wake screen for incoming msgs,
|
||||||
|
// which is the existing default behaviour.
|
||||||
|
rd(&_prefs.msg_wake_screen_off, sizeof(_prefs.msg_wake_screen_off));
|
||||||
|
if (_prefs.msg_wake_screen_off > 1) _prefs.msg_wake_screen_off = 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.
|
||||||
@@ -830,6 +837,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
|||||||
file.write((uint8_t *)_prefs.repeat_extra_scopes, sizeof(_prefs.repeat_extra_scopes));
|
file.write((uint8_t *)_prefs.repeat_extra_scopes, sizeof(_prefs.repeat_extra_scopes));
|
||||||
file.write((uint8_t *)_prefs.favourite_kinds, sizeof(_prefs.favourite_kinds));
|
file.write((uint8_t *)_prefs.favourite_kinds, sizeof(_prefs.favourite_kinds));
|
||||||
file.write((uint8_t *)&_prefs.fav_sort_off, sizeof(_prefs.fav_sort_off));
|
file.write((uint8_t *)&_prefs.fav_sort_off, sizeof(_prefs.fav_sort_off));
|
||||||
|
file.write((uint8_t *)&_prefs.msg_wake_screen_off, sizeof(_prefs.msg_wake_screen_off));
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -1697,7 +1697,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
|||||||
_prefs.ringtone2_bpm_idx = 2; // 120 bpm default
|
_prefs.ringtone2_bpm_idx = 2; // 120 bpm default
|
||||||
_prefs.notif_melody_ad = 0; // built-in advert sound by 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.advert_sound_scope = ADVERT_SOUND_SCOPE_ALL; // sound every advert by default
|
||||||
_prefs.home_pages_mask = NodePrefs::HP_DEFAULT; // curated everyday carousel; rest opt-in via Home Pages
|
_prefs.home_pages_mask = 0; // all home pages visible by default (0 = all, see NodePrefs.h)
|
||||||
_prefs.bot_enabled = 0;
|
_prefs.bot_enabled = 0;
|
||||||
_prefs.bot_channel_enabled = 0;
|
_prefs.bot_channel_enabled = 0;
|
||||||
_prefs.bot_channel_idx = 0;
|
_prefs.bot_channel_idx = 0;
|
||||||
|
|||||||
@@ -307,6 +307,12 @@ struct NodePrefs { // persisted to file
|
|||||||
uint8_t buzzer_quiet;
|
uint8_t buzzer_quiet;
|
||||||
uint8_t buzzer_volume; // 0=min..4=max, default 4
|
uint8_t buzzer_volume; // 0=min..4=max, default 4
|
||||||
uint8_t buzzer_auto; // 0=manual (default), 1=auto-mute when BT connected
|
uint8_t buzzer_auto; // 0=manual (default), 1=auto-mute when BT connected
|
||||||
|
// Settings > Sound > "Msg wake". Stored inverted (same reason as
|
||||||
|
// fav_sort_off below) so both a fresh memset and an older prefs file (no
|
||||||
|
// bytes here at all) mean "on" -- today's behaviour, where an incoming
|
||||||
|
// message turns the display on (UITask::newMsg()) if it was off and no
|
||||||
|
// companion app is already showing it.
|
||||||
|
uint8_t msg_wake_screen_off; // 0=wake display for incoming msgs (default), 1=disabled
|
||||||
uint8_t ringtone_bpm_idx; // index into {60,90,120,150,180}
|
uint8_t ringtone_bpm_idx; // index into {60,90,120,150,180}
|
||||||
uint8_t ringtone_len; // number of notes in custom ringtone (0 = use default)
|
uint8_t ringtone_len; // number of notes in custom ringtone (0 = use default)
|
||||||
uint8_t ringtone_notes[32]; // packed: bits0-2=pitch, bits3-4=octave-4, bits5-6=dur_idx
|
uint8_t ringtone_notes[32]; // packed: bits0-2=pitch, bits3-4=octave-4, bits5-6=dur_idx
|
||||||
@@ -562,7 +568,7 @@ struct NodePrefs { // persisted to file
|
|||||||
// repeat_* fields) instead of at the tail, which shifted every field after
|
// repeat_* fields) instead of at the tail, which shifted every field after
|
||||||
// them by 25 bytes when loading an older file. Never released, but a dev
|
// them by 25 bytes when loading an older file. Never released, but a dev
|
||||||
// build wrote it, so the number must not be reused for anything else.
|
// build wrote it, so the number must not be reused for anything else.
|
||||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0029;
|
static const uint32_t SCHEMA_SENTINEL = 0xC0DE002A;
|
||||||
|
|
||||||
// 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
|
||||||
@@ -608,13 +614,6 @@ struct NodePrefs { // persisted to file
|
|||||||
static const uint16_t HP_FAVOURITES = 1 << HPB_FAVOURITES;
|
static const uint16_t HP_FAVOURITES = 1 << HPB_FAVOURITES;
|
||||||
static const uint16_t HP_MAP = 1 << HPB_MAP;
|
static const uint16_t HP_MAP = 1 << HPB_MAP;
|
||||||
static const uint16_t HP_ALL = 0x01FF | HP_FAVOURITES | HP_MAP;
|
static const uint16_t HP_ALL = 0x01FF | HP_FAVOURITES | HP_MAP;
|
||||||
// Factory-default carousel — the everyday pages only, so a fresh device isn't
|
|
||||||
// 13 pages to joystick through. Messages + Settings are always visible (no
|
|
||||||
// mask bit), so the mask covers: Clock, Tools, Shutdown, Favourites, Map.
|
|
||||||
// Recent / Radio / Bluetooth / Advert / GPS / Sensors are opt-in via
|
|
||||||
// Settings › Home Pages. Existing users keep their saved mask (loaded from
|
|
||||||
// /new_prefs); this only seeds brand-new / factory-reset devices.
|
|
||||||
static const uint16_t HP_DEFAULT = HP_CLOCK | HP_TOOLS | HP_SHUTDOWN | HP_FAVOURITES | HP_MAP;
|
|
||||||
|
|
||||||
// Label for home page by bit-index; returns "" for out-of-range.
|
// Label for home page by bit-index; returns "" for out-of-range.
|
||||||
// Array indices match HomePageBit values.
|
// Array indices match HomePageBit values.
|
||||||
@@ -706,6 +705,9 @@ struct NodePrefs { // persisted to file
|
|||||||
// Display (a better thematic fit), which shifted padding again and put
|
// Display (a better thematic fit), which shifted padding again and put
|
||||||
// sizeof back at 2760 -- also confirmed via a real
|
// sizeof back at 2760 -- also confirmed via a real
|
||||||
// Heltec_v3_companion_radio_ble build. Still no schema change.
|
// Heltec_v3_companion_radio_ble build. Still no schema change.
|
||||||
|
// msg_wake_screen_off (0xC0DE002A) landed in the 1 byte of padding the
|
||||||
|
// 0xC0DE0029 bump left over -- confirmed via a real sim_companion_radio
|
||||||
|
// (native) build, sizeof unchanged at 2760.
|
||||||
static_assert(sizeof(NodePrefs) == 2760,
|
static_assert(sizeof(NodePrefs) == 2760,
|
||||||
"NodePrefs layout changed — sync DataStore save/load + clamp, bump "
|
"NodePrefs layout changed — sync DataStore save/load + clamp, bump "
|
||||||
"SCHEMA_SENTINEL, then update this size (see steps above).");
|
"SCHEMA_SENTINEL, then update this size (see steps above).");
|
||||||
|
|||||||
@@ -446,14 +446,12 @@ extern "C" EMSCRIPTEN_KEEPALIVE void sim_radio_get_params(float* out_freq, float
|
|||||||
*out_cr = p ? p->cr : 0;
|
*out_cr = p ? p->cr : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Real hardware ships with NodePrefs::HP_DEFAULT -- a curated 5-page Home
|
// A brand-new device's home_pages_mask defaults to 0 = all pages visible
|
||||||
// carousel (Clock/Tools/Shutdown/Favourites/Map) -- so a first-time user
|
// (see NodePrefs.h and MyMesh.cpp) -- this is now a no-op on a freshly
|
||||||
// isn't handed 13 pages to joystick through; the rest (Recent/Radio/
|
// booted sim instance, but is kept for a saved-prefs instance whose mask
|
||||||
// Bluetooth/Advert/GPS/Sensors) are opt-in via Settings > Home Pages. The
|
// was narrowed by an actual Settings > Home Pages visit (an upgrader whose
|
||||||
// demo site exists specifically to show off the whole feature set, so it
|
// IDBFS identity predates this default, or a visitor who toggled some
|
||||||
// calls this once right after boot to opt every instance into all of them
|
// pages off before this hook runs on a later boot).
|
||||||
// instead -- 0 means "all visible" (see the home_pages_mask comment in
|
|
||||||
// NodePrefs.h), same as an as-yet-unset field on a factory-fresh device.
|
|
||||||
extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_show_all_home_pages() {
|
extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_show_all_home_pages() {
|
||||||
if (!g_sim_ready) return 0;
|
if (!g_sim_ready) return 0;
|
||||||
NodePrefs* prefs = the_mesh.getNodePrefs();
|
NodePrefs* prefs = the_mesh.getNodePrefs();
|
||||||
@@ -498,6 +496,30 @@ extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_disable_screen_timeout() {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets NodePrefs::msg_wake_screen_off directly (the same field Settings >
|
||||||
|
// Sound > "Msg wake" toggles -- SettingsScreen.h's MSG_WAKE item), so a test
|
||||||
|
// harness can verify UITask::newMsg()'s wake-gating without scripting the
|
||||||
|
// on-device Settings accordion navigation key-by-key.
|
||||||
|
extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_set_msg_wake_disabled(int disabled) {
|
||||||
|
if (!g_sim_ready) return 0;
|
||||||
|
NodePrefs* prefs = the_mesh.getNodePrefs();
|
||||||
|
if (!prefs) return 0;
|
||||||
|
prefs->msg_wake_screen_off = disabled ? 1 : 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw home_pages_mask readback -- verifies a fresh instance really does
|
||||||
|
// default to 0 (= all pages visible, MyMesh.cpp) without having to count
|
||||||
|
// carousel frames on-canvas (unreliable: several home pages, e.g. Clock,
|
||||||
|
// redraw with live-changing content every tick, so a page revisited later
|
||||||
|
// in the cycle rarely hashes identically to its first visit).
|
||||||
|
extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_get_home_pages_mask() {
|
||||||
|
if (!g_sim_ready) return -1;
|
||||||
|
NodePrefs* prefs = the_mesh.getNodePrefs();
|
||||||
|
if (!prefs) return -1;
|
||||||
|
return (int)prefs->home_pages_mask;
|
||||||
|
}
|
||||||
|
|
||||||
// Re-anchors this instance's RTC to the host's real wall clock. SimRTCClock
|
// Re-anchors this instance's RTC to the host's real wall clock. SimRTCClock
|
||||||
// (variants/sim/SimRTCClock.h) already starts out reading time(NULL), so a
|
// (variants/sim/SimRTCClock.h) already starts out reading time(NULL), so a
|
||||||
// freshly booted instance needs no help -- but the clock is a shared, live
|
// freshly booted instance needs no help -- but the clock is a shared, live
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class SettingsScreen : public UIScreen {
|
|||||||
CH_MELODY,
|
CH_MELODY,
|
||||||
AD_SOUND,
|
AD_SOUND,
|
||||||
AD_SOUND_SCOPE,
|
AD_SOUND_SCOPE,
|
||||||
|
MSG_WAKE,
|
||||||
// Home pages section
|
// Home pages section
|
||||||
SECTION_HOME_PAGES,
|
SECTION_HOME_PAGES,
|
||||||
HOME_CLOCK, HOME_FAVOURITES, HOME_RADIO, HOME_BT, HOME_ADVERT,
|
HOME_CLOCK, HOME_FAVOURITES, HOME_RADIO, HOME_BT, HOME_ADVERT,
|
||||||
@@ -494,6 +495,10 @@ class SettingsScreen : public UIScreen {
|
|||||||
display.setCursor(valCol(display), y);
|
display.setCursor(valCol(display), y);
|
||||||
{ uint8_t v = p ? p->advert_sound_scope : ADVERT_SOUND_SCOPE_ALL;
|
{ uint8_t v = p ? p->advert_sound_scope : ADVERT_SOUND_SCOPE_ALL;
|
||||||
display.print(AD_SCOPE_LABELS[v < AD_SCOPE_COUNT ? v : 0]); }
|
display.print(AD_SCOPE_LABELS[v < AD_SCOPE_COUNT ? v : 0]); }
|
||||||
|
} else if (item == MSG_WAKE) {
|
||||||
|
display.print("Msg wake");
|
||||||
|
display.setCursor(valCol(display), y);
|
||||||
|
display.print((p && p->msg_wake_screen_off) ? "OFF" : "ON");
|
||||||
} else if (isHomePage(item)) {
|
} else if (isHomePage(item)) {
|
||||||
if (p) ensurePageOrderInit(p);
|
if (p) ensurePageOrderInit(p);
|
||||||
int pos = homePagePosition(item, p);
|
int pos = homePagePosition(item, p);
|
||||||
@@ -908,6 +913,10 @@ public:
|
|||||||
p->advert_sound_scope ^= 1;
|
p->advert_sound_scope ^= 1;
|
||||||
_dirty = true; return true;
|
_dirty = true; return true;
|
||||||
}
|
}
|
||||||
|
if (_selected == MSG_WAKE && p && (left || right || enter)) {
|
||||||
|
p->msg_wake_screen_off ^= 1;
|
||||||
|
_dirty = true; return true;
|
||||||
|
}
|
||||||
if (isHomePage(_selected) && p) {
|
if (isHomePage(_selected) && p) {
|
||||||
if (left || right) {
|
if (left || right) {
|
||||||
movePageInOrder(_selected, left ? -1 : 1, p);
|
movePageInOrder(_selected, left ? -1 : 1, p);
|
||||||
|
|||||||
@@ -1933,7 +1933,8 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
|||||||
showAlert(alert_buf, 3000);
|
showAlert(alert_buf, 3000);
|
||||||
|
|
||||||
if (_display != NULL && !_locked) {
|
if (_display != NULL && !_locked) {
|
||||||
if (!_display->isOn() && !isClientConnected()) { // wake for the msg unless an app (BLE/USB) is already showing it
|
bool wake_disabled = _node_prefs && _node_prefs->msg_wake_screen_off;
|
||||||
|
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();
|
_display->turnOn();
|
||||||
}
|
}
|
||||||
if (_display->isOn()) {
|
if (_display->isOn()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user