From 3b98c4e243a31ced46f1a50fca96cf157c2f29e2 Mon Sep 17 00:00:00 2001 From: Tesso M Costa Date: Wed, 29 Jul 2026 12:17:24 -0600 Subject: [PATCH] ThinkNode M6: button shutdown/wake for repeater and room server Fixes #2313. Hold the Function Button ~5s to power off (matches the board's stock firmware timing); any press wakes it back up, since the chip is fully halted in SYSTEMOFF and can't measure hold duration while off. The power LED blinks 3x on both shutdown and wake/boot to confirm the action registered. ThinkNodeM6Board::powerOff() now arms the button as a GPIO wake source (pull-up + SENSE_LOW) before entering SYSTEMOFF via NRF52Board::powerOff(). Scoped to simple_repeater and simple_room_server only, since companion_radio already has an in-flight change (#2837) moving its button handling into UITask. Tested on hardware: shutdown, wake, and LED feedback all verified on a physical ThinkNode M6 repeater. --- examples/simple_repeater/main.cpp | 19 +++++++++++--- examples/simple_room_server/main.cpp | 29 ++++++++++++++++++++++ variants/thinknode_m6/ThinkNodeM6Board.cpp | 14 +++++++++++ variants/thinknode_m6/ThinkNodeM6Board.h | 6 +++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index a714db68e..772b9809b 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -30,10 +30,14 @@ static char ethernet_command[160]; // For power saving unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot -#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_) +#if defined(PIN_USER_BTN) && (defined(_SEEED_SENSECAP_SOLAR_H_) || defined(THINKNODE_M6)) static unsigned long userBtnDownAt = 0; +#if defined(THINKNODE_M6) +#define USER_BTN_HOLD_OFF_MILLIS 5000 // matches Elecrow's stock firmware UX +#else #define USER_BTN_HOLD_OFF_MILLIS 1500 #endif +#endif void setup() { Serial.begin(115200); @@ -170,13 +174,22 @@ void loop() { } #endif -#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_) && !defined(DISPLAY_CLASS) - // Hold the user button to power off the SenseCAP Solar repeater. +#if defined(PIN_USER_BTN) && (defined(_SEEED_SENSECAP_SOLAR_H_) || defined(THINKNODE_M6)) && !defined(DISPLAY_CLASS) + // Hold the user button to power off the repeater. int btnState = digitalRead(PIN_USER_BTN); if (btnState == LOW) { if (userBtnDownAt == 0) { userBtnDownAt = millis(); } else if ((unsigned long)(millis() - userBtnDownAt) >= USER_BTN_HOLD_OFF_MILLIS) { +#if defined(THINKNODE_M6) && defined(PIN_LED_RED) + // blink the power LED a few times to confirm the hold was registered + for (int i = 0; i < 3; i++) { + digitalWrite(PIN_LED_RED, HIGH); + delay(150); + digitalWrite(PIN_LED_RED, LOW); + delay(150); + } +#endif Serial.println("Powering off..."); board.powerOff(); // does not return } diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index d833fff39..f0e9e4e83 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -26,6 +26,11 @@ static char command[MAX_POST_TEXT_LEN+1]; static char ethernet_command[MAX_POST_TEXT_LEN+1]; #endif +#if defined(PIN_USER_BTN) && defined(THINKNODE_M6) +static unsigned long userBtnDownAt = 0; +#define USER_BTN_HOLD_OFF_MILLIS 5000 // matches Elecrow's stock firmware UX +#endif + void setup() { Serial.begin(115200); delay(1000); @@ -148,6 +153,30 @@ void loop() { } #endif +#if defined(PIN_USER_BTN) && defined(THINKNODE_M6) && !defined(DISPLAY_CLASS) + // Hold the user button to power off the room server. + int btnState = digitalRead(PIN_USER_BTN); + if (btnState == LOW) { + if (userBtnDownAt == 0) { + userBtnDownAt = millis(); + } else if ((unsigned long)(millis() - userBtnDownAt) >= USER_BTN_HOLD_OFF_MILLIS) { +#ifdef PIN_LED_RED + // blink the power LED a few times to confirm the hold was registered + for (int i = 0; i < 3; i++) { + digitalWrite(PIN_LED_RED, HIGH); + delay(150); + digitalWrite(PIN_LED_RED, LOW); + delay(150); + } +#endif + Serial.println("Powering off..."); + board.powerOff(); // does not return + } + } else { + userBtnDownAt = 0; + } +#endif + the_mesh.loop(); sensors.loop(); #ifdef DISPLAY_CLASS diff --git a/variants/thinknode_m6/ThinkNodeM6Board.cpp b/variants/thinknode_m6/ThinkNodeM6Board.cpp index 8ebae64c6..d34cf6848 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.cpp +++ b/variants/thinknode_m6/ThinkNodeM6Board.cpp @@ -15,6 +15,20 @@ void ThinkNodeM6Board::begin() { digitalWrite(P_LORA_TX_LED, LOW); #endif +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#ifdef PIN_LED_RED + // blink the power LED a few times to confirm power-on/wake + for (int i = 0; i < 3; i++) { + digitalWrite(PIN_LED_RED, HIGH); + delay(150); + digitalWrite(PIN_LED_RED, LOW); + delay(150); + } +#endif + delay(10); // give sx1262 some time to power up } diff --git a/variants/thinknode_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h index 78815e2c9..c3a94cb01 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.h +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -43,6 +43,12 @@ public: digitalWrite(P_LORA_TX_LED, LOW); #endif + #ifdef PIN_USER_BTN + while (digitalRead(PIN_USER_BTN) == LOW); // wait for release, avoid instant re-trigger + // keep pull-up enabled in system-off so the wake line doesn't float low + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); + #endif + // power off board NRF52Board::powerOff(); }