From 9f8f4471aa5997b364fa3a316641a1dbbdaf44e8 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 27 Dec 2025 22:36:34 +1100 Subject: [PATCH 1/5] PIN_PWR_DELAY_MS --> PERIPHERAL_WARMUP_MS (#8467) It turns out we had two methods for delaying startup while peripherals warmed up. They were invented within months of each other and just missed the chance to merge. Let's delete PIN_PWR_DELAY_MS and use PERIPHERAL_WARMUP_MS, since it's most common and earlier in the sequence. --- src/main.cpp | 5 ----- variants/nrf52840/canaryone/variant.h | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d28eb13f0..9ac060d37 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1186,11 +1186,6 @@ void setup() #endif #endif -#ifdef PIN_PWR_DELAY_MS - // This may be required to give the peripherals time to power up. - delay(PIN_PWR_DELAY_MS); -#endif - #ifdef ARCH_PORTDUINO // as one can't use a function pointer to the class constructor: auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 204ca6306..61d1e8df9 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -103,7 +103,7 @@ static const uint8_t A0 = PIN_A0; #define EXTERNAL_FLASH_USE_QSPI // Add a delay on startup to allow LoRa and GPS to power up -#define PIN_PWR_DELAY_MS 100 +#define PERIPHERAL_WARMUP_MS 100 /* * Lora radio @@ -178,4 +178,4 @@ static const uint8_t A0 = PIN_A0; * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif From 2c68710e8c252c798281ccdf8775c96e9f643d61 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sat, 27 Dec 2025 11:17:55 -0600 Subject: [PATCH 2/5] Improve sanitizeString function for Node Names (#9086) --- src/graphics/SharedUIDisplay.cpp | 37 +++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 5660810e6..f5ca6ed03 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -470,18 +470,49 @@ bool isAllowedPunctuation(char c) return allowed.find(c) != std::string::npos; } +static void replaceAll(std::string &s, const std::string &from, const std::string &to) +{ + if (from.empty()) + return; + size_t pos = 0; + while ((pos = s.find(from, pos)) != std::string::npos) { + s.replace(pos, from.size(), to); + pos += to.size(); + } +} + std::string sanitizeString(const std::string &input) { std::string output; bool inReplacement = false; - for (char c : input) { - if (std::isalnum(static_cast(c)) || isAllowedPunctuation(c)) { + // Make a mutable copy so we can normalize UTF-8 “smart punctuation” into ASCII first. + std::string s = input; + + // Curly single quotes: ‘ ’ + replaceAll(s, "\xE2\x80\x98", "'"); // U+2018 + replaceAll(s, "\xE2\x80\x99", "'"); // U+2019 + + // Curly double quotes: “ ” + replaceAll(s, "\xE2\x80\x9C", "\""); // U+201C + replaceAll(s, "\xE2\x80\x9D", "\""); // U+201D + + // En dash / Em dash: – — + replaceAll(s, "\xE2\x80\x93", "-"); // U+2013 + replaceAll(s, "\xE2\x80\x94", "-"); // U+2014 + + // Non-breaking space + replaceAll(s, "\xC2\xA0", " "); // U+00A0 + + // Now do your original sanitize pass over the normalized string. + for (unsigned char uc : s) { + char c = static_cast(uc); + if (std::isalnum(uc) || isAllowedPunctuation(c)) { output += c; inReplacement = false; } else { if (!inReplacement) { - output += 0xbf; // ISO-8859-1 for inverted question mark + output += static_cast(0xBF); // ISO-8859-1 for inverted question mark inReplacement = true; } } From d1db4433f41a074b1131073f7fc3fb97e335a954 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sat, 27 Dec 2025 11:18:16 -0600 Subject: [PATCH 3/5] Add menus for Smart Position, Broadcast Interval and Position Interval (#9080) * Add menus for Smart Position, Broadcast Interval and Position Interval * Realigned time intervals to match Android app options * Fixed missing last option --- src/graphics/draw/MenuHandler.cpp | 237 +++++++++++++++++++++++++++++- src/graphics/draw/MenuHandler.h | 6 + 2 files changed, 238 insertions(+), 5 deletions(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index ac877e150..0aed81cfb 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -1092,11 +1092,23 @@ void menuHandler::favoriteBaseMenu() void menuHandler::positionBaseMenu() { - enum optionsNumbers { Back, GPSToggle, GPSFormat, CompassMenu, CompassCalibrate, enumEnd }; + enum optionsNumbers { + Back, + GPSToggle, + GPSFormat, + CompassMenu, + CompassCalibrate, + GPSSmartPosition, + GPSUpdateInterval, + GPSPositionBroadcast, + enumEnd + }; - static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "GPS Format", "Compass"}; - static int optionsEnumArray[enumEnd] = {Back, GPSToggle, GPSFormat, CompassMenu}; - int options = 4; + static const char *optionsArray[enumEnd] = { + "Back", "On/Off Toggle", "Format", "Smart Position", "Update Interval", "Broadcast Interval", "Compass"}; + static int optionsEnumArray[enumEnd] = { + Back, GPSToggle, GPSFormat, GPSSmartPosition, GPSUpdateInterval, GPSPositionBroadcast, CompassMenu}; + int options = 7; if (accelerometerThread) { optionsArray[options] = "Compass Calibrate"; @@ -1104,7 +1116,7 @@ void menuHandler::positionBaseMenu() } BannerOverlayOptions bannerOptions; - bannerOptions.message = "Position Action"; + bannerOptions.message = "GPS Action"; bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsCount = options; @@ -1120,6 +1132,15 @@ void menuHandler::positionBaseMenu() screen->runNow(); } else if (selected == CompassCalibrate) { accelerometerThread->calibrate(30); + } else if (selected == GPSSmartPosition) { + menuQueue = gps_smart_position_menu; + screen->runNow(); + } else if (selected == GPSUpdateInterval) { + menuQueue = gps_update_interval_menu; + screen->runNow(); + } else if (selected == GPSPositionBroadcast) { + menuQueue = gps_position_broadcast_menu; + screen->runNow(); } }; screen->showOverlayBanner(bannerOptions); @@ -1346,6 +1367,203 @@ void menuHandler::GPSFormatMenu() bannerOptions.InitialSelected = uiconfig.gps_format + 1; screen->showOverlayBanner(bannerOptions); } + +void menuHandler::GPSSmartPositionMenu() +{ + static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Toggle Smart Position"; + if (currentResolution == ScreenResolution::UltraLow) { + bannerOptions.message = "Smrt Postn"; + } + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 3; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 0) { + menuQueue = position_base_menu; + screen->runNow(); + } else if (selected == 1) { + config.position.position_broadcast_smart_enabled = true; + saveUIConfig(); + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } else if (selected == 2) { + config.position.position_broadcast_smart_enabled = false; + saveUIConfig(); + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } + }; + bannerOptions.InitialSelected = config.position.position_broadcast_smart_enabled ? 1 : 2; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::GPSUpdateIntervalMenu() +{ + static const char *optionsArray[] = {"Back", "8 seconds", "20 seconds", "40 seconds", "1 minute", "80 seconds", + "2 minutes", "5 minutes", "10 minutes", "15 minutes", "30 minutes", "1 hour", + "6 hours", "12 hours", "24 hours", "At Boot Only"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Update Interval"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 16; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 0) { + menuQueue = position_base_menu; + screen->runNow(); + } else if (selected == 1) { + config.position.gps_update_interval = 8; + } else if (selected == 2) { + config.position.gps_update_interval = 20; + } else if (selected == 3) { + config.position.gps_update_interval = 40; + } else if (selected == 4) { + config.position.gps_update_interval = 60; + } else if (selected == 5) { + config.position.gps_update_interval = 80; + } else if (selected == 6) { + config.position.gps_update_interval = 120; + } else if (selected == 7) { + config.position.gps_update_interval = 300; + } else if (selected == 8) { + config.position.gps_update_interval = 600; + } else if (selected == 9) { + config.position.gps_update_interval = 900; + } else if (selected == 10) { + config.position.gps_update_interval = 1800; + } else if (selected == 11) { + config.position.gps_update_interval = 3600; + } else if (selected == 12) { + config.position.gps_update_interval = 21600; + } else if (selected == 13) { + config.position.gps_update_interval = 43200; + } else if (selected == 14) { + config.position.gps_update_interval = 86400; + } else if (selected == 15) { + config.position.gps_update_interval = 2147483647; // At Boot Only + } + + if (selected != 0) { + saveUIConfig(); + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } + }; + + if (config.position.gps_update_interval == 30) { + bannerOptions.InitialSelected = 1; + } else if (config.position.gps_update_interval == 60) { + bannerOptions.InitialSelected = 2; + } else if (config.position.gps_update_interval == 120) { + bannerOptions.InitialSelected = 3; + } else if (config.position.gps_update_interval == 300) { + bannerOptions.InitialSelected = 4; + } else if (config.position.gps_update_interval == 600) { + bannerOptions.InitialSelected = 5; + } else if (config.position.gps_update_interval == 900) { + bannerOptions.InitialSelected = 6; + } else if (config.position.gps_update_interval == 1800) { + bannerOptions.InitialSelected = 7; + } else if (config.position.gps_update_interval == 3600) { + bannerOptions.InitialSelected = 8; + } else if (config.position.gps_update_interval == 21600) { + bannerOptions.InitialSelected = 9; + } else if (config.position.gps_update_interval == 43200) { + bannerOptions.InitialSelected = 10; + } else if (config.position.gps_update_interval == 86400) { + bannerOptions.InitialSelected = 11; + } else if (config.position.gps_update_interval == 2147483647) { // At Boot Only + bannerOptions.InitialSelected = 12; + } else { + bannerOptions.InitialSelected = 0; + } + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::GPSPositionBroadcastMenu() +{ + static const char *optionsArray[] = {"Back", "1 minute", "90 seconds", "5 minutes", "15 minutes", "1 hour", + "2 hours", "3 hours", "4 hours", "5 hours", "6 hours", "12 hours", + "18 hours", "24 hours", "36 hours", "48 hours", "72 hours"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Broadcast Interval"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 17; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == 0) { + menuQueue = position_base_menu; + screen->runNow(); + } else if (selected == 1) { + config.position.position_broadcast_secs = 60; + } else if (selected == 2) { + config.position.position_broadcast_secs = 90; + } else if (selected == 3) { + config.position.position_broadcast_secs = 300; + } else if (selected == 4) { + config.position.position_broadcast_secs = 900; + } else if (selected == 5) { + config.position.position_broadcast_secs = 3600; + } else if (selected == 6) { + config.position.position_broadcast_secs = 7200; + } else if (selected == 7) { + config.position.position_broadcast_secs = 10800; + } else if (selected == 8) { + config.position.position_broadcast_secs = 14400; + } else if (selected == 9) { + config.position.position_broadcast_secs = 18000; + } else if (selected == 10) { + config.position.position_broadcast_secs = 21600; + } else if (selected == 11) { + config.position.position_broadcast_secs = 43200; + } else if (selected == 12) { + config.position.position_broadcast_secs = 64800; + } else if (selected == 13) { + config.position.position_broadcast_secs = 86400; + } else if (selected == 14) { + config.position.position_broadcast_secs = 129600; + } else if (selected == 15) { + config.position.position_broadcast_secs = 172800; + } else if (selected == 16) { + config.position.position_broadcast_secs = 259200; + } + + if (selected != 0) { + saveUIConfig(); + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } + }; + + if (config.position.position_broadcast_secs == 3600) { + bannerOptions.InitialSelected = 1; + } else if (config.position.position_broadcast_secs == 7200) { + bannerOptions.InitialSelected = 2; + } else if (config.position.position_broadcast_secs == 10800) { + bannerOptions.InitialSelected = 3; + } else if (config.position.position_broadcast_secs == 14400) { + bannerOptions.InitialSelected = 4; + } else if (config.position.position_broadcast_secs == 18000) { + bannerOptions.InitialSelected = 5; + } else if (config.position.position_broadcast_secs == 21600) { + bannerOptions.InitialSelected = 6; + } else if (config.position.position_broadcast_secs == 43200) { + bannerOptions.InitialSelected = 7; + } else if (config.position.position_broadcast_secs == 64800) { + bannerOptions.InitialSelected = 8; + } else if (config.position.position_broadcast_secs == 86400) { + bannerOptions.InitialSelected = 9; + } else if (config.position.position_broadcast_secs == 129600) { + bannerOptions.InitialSelected = 10; + } else if (config.position.position_broadcast_secs == 172800) { + bannerOptions.InitialSelected = 11; + } else if (config.position.position_broadcast_secs == 259200) { + bannerOptions.InitialSelected = 12; + } else { + bannerOptions.InitialSelected = 0; + } + screen->showOverlayBanner(bannerOptions); +} + #endif void menuHandler::BluetoothToggleMenu() @@ -2126,6 +2344,15 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display) case gps_format_menu: GPSFormatMenu(); break; + case gps_smart_position_menu: + GPSSmartPositionMenu(); + break; + case gps_update_interval_menu: + GPSUpdateIntervalMenu(); + break; + case gps_position_broadcast_menu: + GPSPositionBroadcastMenu(); + break; #endif case compass_point_north_menu: compassNorthMenu(); diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index e53b4baf7..bda744a66 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -22,6 +22,9 @@ class menuHandler node_base_menu, gps_toggle_menu, gps_format_menu, + gps_smart_position_menu, + gps_update_interval_menu, + gps_position_broadcast_menu, compass_point_north_menu, reset_node_db_menu, buzzermodemenupicker, @@ -77,6 +80,9 @@ class menuHandler static void compassNorthMenu(); static void GPSToggleMenu(); static void GPSFormatMenu(); + static void GPSSmartPositionMenu(); + static void GPSUpdateIntervalMenu(); + static void GPSPositionBroadcastMenu(); static void BuzzerModeMenu(); static void switchToMUIMenu(); static void TFTColorPickerMenu(OLEDDisplay *display); From 759a972f779301dce4f24d0aed7c97610a14cffc Mon Sep 17 00:00:00 2001 From: Jason P Date: Sat, 27 Dec 2025 12:42:22 -0600 Subject: [PATCH 4/5] GPS Menu Validation Fix - Missed in Reviews (#9093) * Reviews sometimes miss things, whoops * Validation is hard - but this fixes it --- src/graphics/draw/MenuHandler.cpp | 62 +++++++++++++++++++------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 0aed81cfb..d9db84c35 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -1450,30 +1450,36 @@ void menuHandler::GPSUpdateIntervalMenu() } }; - if (config.position.gps_update_interval == 30) { + if (config.position.gps_update_interval == 8) { bannerOptions.InitialSelected = 1; - } else if (config.position.gps_update_interval == 60) { + } else if (config.position.gps_update_interval == 20) { bannerOptions.InitialSelected = 2; - } else if (config.position.gps_update_interval == 120) { + } else if (config.position.gps_update_interval == 40) { bannerOptions.InitialSelected = 3; - } else if (config.position.gps_update_interval == 300) { + } else if (config.position.gps_update_interval == 60) { bannerOptions.InitialSelected = 4; - } else if (config.position.gps_update_interval == 600) { + } else if (config.position.gps_update_interval == 80) { bannerOptions.InitialSelected = 5; - } else if (config.position.gps_update_interval == 900) { + } else if (config.position.gps_update_interval == 120) { bannerOptions.InitialSelected = 6; - } else if (config.position.gps_update_interval == 1800) { + } else if (config.position.gps_update_interval == 300) { bannerOptions.InitialSelected = 7; - } else if (config.position.gps_update_interval == 3600) { + } else if (config.position.gps_update_interval == 600) { bannerOptions.InitialSelected = 8; - } else if (config.position.gps_update_interval == 21600) { + } else if (config.position.gps_update_interval == 900) { bannerOptions.InitialSelected = 9; - } else if (config.position.gps_update_interval == 43200) { + } else if (config.position.gps_update_interval == 1800) { bannerOptions.InitialSelected = 10; - } else if (config.position.gps_update_interval == 86400) { + } else if (config.position.gps_update_interval == 3600) { bannerOptions.InitialSelected = 11; - } else if (config.position.gps_update_interval == 2147483647) { // At Boot Only + } else if (config.position.gps_update_interval == 21600) { bannerOptions.InitialSelected = 12; + } else if (config.position.gps_update_interval == 43200) { + bannerOptions.InitialSelected = 13; + } else if (config.position.gps_update_interval == 86400) { + bannerOptions.InitialSelected = 14; + } else if (config.position.gps_update_interval == 2147483647) { // At Boot Only + bannerOptions.InitialSelected = 15; } else { bannerOptions.InitialSelected = 0; } @@ -1534,30 +1540,38 @@ void menuHandler::GPSPositionBroadcastMenu() } }; - if (config.position.position_broadcast_secs == 3600) { + if (config.position.position_broadcast_secs == 60) { bannerOptions.InitialSelected = 1; - } else if (config.position.position_broadcast_secs == 7200) { + } else if (config.position.position_broadcast_secs == 90) { bannerOptions.InitialSelected = 2; - } else if (config.position.position_broadcast_secs == 10800) { + } else if (config.position.position_broadcast_secs == 300) { bannerOptions.InitialSelected = 3; - } else if (config.position.position_broadcast_secs == 14400) { + } else if (config.position.position_broadcast_secs == 900) { bannerOptions.InitialSelected = 4; - } else if (config.position.position_broadcast_secs == 18000) { + } else if (config.position.position_broadcast_secs == 3600) { bannerOptions.InitialSelected = 5; - } else if (config.position.position_broadcast_secs == 21600) { + } else if (config.position.position_broadcast_secs == 7200) { bannerOptions.InitialSelected = 6; - } else if (config.position.position_broadcast_secs == 43200) { + } else if (config.position.position_broadcast_secs == 10800) { bannerOptions.InitialSelected = 7; - } else if (config.position.position_broadcast_secs == 64800) { + } else if (config.position.position_broadcast_secs == 14400) { bannerOptions.InitialSelected = 8; - } else if (config.position.position_broadcast_secs == 86400) { + } else if (config.position.position_broadcast_secs == 18000) { bannerOptions.InitialSelected = 9; - } else if (config.position.position_broadcast_secs == 129600) { + } else if (config.position.position_broadcast_secs == 21600) { bannerOptions.InitialSelected = 10; - } else if (config.position.position_broadcast_secs == 172800) { + } else if (config.position.position_broadcast_secs == 43200) { bannerOptions.InitialSelected = 11; - } else if (config.position.position_broadcast_secs == 259200) { + } else if (config.position.position_broadcast_secs == 64800) { bannerOptions.InitialSelected = 12; + } else if (config.position.position_broadcast_secs == 86400) { + bannerOptions.InitialSelected = 13; + } else if (config.position.position_broadcast_secs == 129600) { + bannerOptions.InitialSelected = 14; + } else if (config.position.position_broadcast_secs == 172800) { + bannerOptions.InitialSelected = 15; + } else if (config.position.position_broadcast_secs == 259200) { + bannerOptions.InitialSelected = 16; } else { bannerOptions.InitialSelected = 0; } From 63aadba526b5db610b076043dbba3d35eabe86c5 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 28 Dec 2025 09:49:41 -0600 Subject: [PATCH 5/5] Use IF_SCREEN macro to guard against null screen object --- src/mesh/MeshService.cpp | 16 +++++++--------- src/modules/TextMessageModule.cpp | 21 ++++++++++----------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index c63e6d2d2..e1037f789 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -195,15 +195,13 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p) p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone -#if HAS_SCREEN - if (p.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && p.decoded.payload.size > 0 && p.to != NODENUM_BROADCAST && - p.to != 0) // DM only - { - perhapsDecode(&p); - const StoredMessage &sm = messageStore.addFromPacket(p); - graphics::MessageRenderer::handleNewMessage(nullptr, sm, p); // notify UI - } -#endif + IF_SCREEN(if (p.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && p.decoded.payload.size > 0 && + p.to != NODENUM_BROADCAST && p.to != 0) // DM only + { + perhapsDecode(&p); + const StoredMessage &sm = messageStore.addFromPacket(p); + graphics::MessageRenderer::handleNewMessage(nullptr, sm, p); // notify UI + }) // Send the packet into the mesh DEBUG_HEAP_BEFORE; auto a = packetPool.allocCopy(p); diff --git a/src/modules/TextMessageModule.cpp b/src/modules/TextMessageModule.cpp index 76e063436..7f889e087 100644 --- a/src/modules/TextMessageModule.cpp +++ b/src/modules/TextMessageModule.cpp @@ -21,18 +21,17 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp // We only store/display messages destined for us. devicestate.rx_text_message = mp; devicestate.has_rx_text_message = true; -#if HAS_SCREEN - // Guard against running in MeshtasticUI - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { - // Store in the central message history - const StoredMessage &sm = messageStore.addFromPacket(mp); + IF_SCREEN( + // Guard against running in MeshtasticUI or with no screen + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { + // Store in the central message history + const StoredMessage &sm = messageStore.addFromPacket(mp); - // Pass message to renderer (banner + thread switching + scroll reset) - // Use the global Screen singleton to retrieve the current OLED display - auto *display = screen ? screen->getDisplayDevice() : nullptr; - graphics::MessageRenderer::handleNewMessage(display, sm, mp); - } -#endif + // Pass message to renderer (banner + thread switching + scroll reset) + // Use the global Screen singleton to retrieve the current OLED display + auto *display = screen ? screen->getDisplayDevice() : nullptr; + graphics::MessageRenderer::handleNewMessage(display, sm, mp); + }) // Only trigger screen wake if configuration allows it if (shouldWakeOnReceivedMessage()) { powerFSM.trigger(EVENT_RECEIVED_MSG);