From 2742f160d29655f0bf450dadb950a34b699bbbc4 Mon Sep 17 00:00:00 2001 From: Jody Bentley Date: Wed, 24 Jun 2026 19:15:21 -0400 Subject: [PATCH] ThinkNode M6: run UITask headless (status LED, button, low-battery shutdown) The M6's two LEDs work in the bootloader and on other firmware, but the MeshCore companion builds gave no status indication: the red LED (P0.12) was initialised off and never used, and the blue LED only blinked on LoRa TX. The companion status LED, user-button handling and low-battery auto-shutdown all live in UITask, which is only instantiated when DISPLAY_CLASS is defined. The M6 is screenless, so UITask never ran and none of those worked - including AUTO_SHUTDOWN_MILLIVOLTS, which was set in the env but had no effect. Run UITask headless via NullDisplayDriver, the same way the other screenless boards (t1000-e, RAK WisMesh Tag) do, and define PIN_STATUS_LED. This enables, on both companion_radio_ble and companion_radio_usb: - status LED on the previously-unused red LED (heartbeat, longer blink on unread messages); blue keeps its LoRa-TX function - the user button (navigation + long-press), groundwork for the shutdown request in #2313 - the already-configured low-battery auto-shutdown (3.3V) Because the M6 is screenless, also hide the Bluetooth toggle page from the button menu (new UI_HIDE_BLUETOOTH_PAGE flag, opt-in, no effect on boards that don't set it). On a screenless node a blind long-press on that page would silently disable BLE with no way to see or undo it; removing it leaves advert / GPS-toggle / hibernate, which are safe. The LED pins/polarity were already correct (RED=12, BLUE=7, active-high), so no hardware-config change is needed. Relates to #2313. --- examples/companion_radio/ui-new/UITask.cpp | 6 ++++++ variants/thinknode_m6/platformio.ini | 8 ++++++++ variants/thinknode_m6/target.h | 4 +++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c84201941..b37f72c58e 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -88,7 +88,9 @@ class HomeScreen : public UIScreen { FIRST, RECENT, RADIO, +#ifndef UI_HIDE_BLUETOOTH_PAGE BLUETOOTH, +#endif ADVERT, #if ENV_INCLUDE_GPS == 1 GPS, @@ -278,6 +280,7 @@ class HomeScreen : public UIScreen { display.setCursor(0, 53); sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor()); display.print(tmp); +#ifndef UI_HIDE_BLUETOOTH_PAGE } else if (_page == HomePage::BLUETOOTH) { display.setColor(DisplayDriver::GREEN); display.drawXbm((display.width() - 32) / 2, 18, @@ -285,6 +288,7 @@ class HomeScreen : public UIScreen { 32, 32); display.setTextSize(1); display.drawTextCentered(display.width() / 2, 64 - 11, "toggle: " PRESS_LABEL); +#endif } else if (_page == HomePage::ADVERT) { display.setColor(DisplayDriver::GREEN); display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32); @@ -425,6 +429,7 @@ class HomeScreen : public UIScreen { } return true; } +#ifndef UI_HIDE_BLUETOOTH_PAGE if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) { if (_task->isSerialEnabled()) { // toggle Bluetooth on/off _task->disableSerial(); @@ -433,6 +438,7 @@ class HomeScreen : public UIScreen { } return true; } +#endif if (c == KEY_ENTER && _page == HomePage::ADVERT) { _task->notify(UIEventType::ack); if (the_mesh.advert()) { diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini index 6fe9043668..54e82092ed 100644 --- a/variants/thinknode_m6/platformio.ini +++ b/variants/thinknode_m6/platformio.ini @@ -85,12 +85,16 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 -D QSPIFLASH=1 + -D DISPLAY_CLASS=NullDisplayDriver + -D PIN_STATUS_LED=PIN_LED_RED + -D UI_HIDE_BLUETOOTH_PAGE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ThinkNode_M6.build_src_filter} + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ThinkNode_M6.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -109,10 +113,14 @@ build_flags = -D QSPIFLASH=1 -D OFFLINE_QUEUE_SIZE=256 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D DISPLAY_CLASS=NullDisplayDriver + -D PIN_STATUS_LED=PIN_LED_RED + -D UI_HIDE_BLUETOOTH_PAGE build_src_filter = ${ThinkNode_M6.build_src_filter} + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ThinkNode_M6.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/thinknode_m6/target.h b/variants/thinknode_m6/target.h index 76188e584e..07ff322947 100644 --- a/variants/thinknode_m6/target.h +++ b/variants/thinknode_m6/target.h @@ -10,7 +10,9 @@ #include #include #ifdef DISPLAY_CLASS - #include + // The M6 is screenless; DISPLAY_CLASS=NullDisplayDriver is used only to run + // UITask headless so the status LED (PIN_STATUS_LED) and button work. + #include #include #endif