diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 82c8c21d9..c9a43b233 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -99,6 +99,11 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store #endif ); +// Power saving timing variables +unsigned long lastActive = 0; // Last time there was activity +unsigned long nextSleepInSecs = 120; // Wait 2 minutes before first sleep +const unsigned long WORK_TIME_SECS = 5; // Stay awake 5 seconds after wake/activity + /* END GLOBAL OBJECTS */ void halt() { @@ -219,6 +224,9 @@ void setup() { #ifdef DISPLAY_CLASS ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved #endif + + // Initialize power saving timer + lastActive = millis(); } void loop() { @@ -228,4 +236,25 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); + + // Power saving when BLE/WiFi is disabled + // Don't sleep if GPS is enabled - it needs continuous operation to maintain fix + // Note: Disabling BLE/WiFi via UI actually turns off the radio to save power + if (!serial_interface.isEnabled() && !the_mesh.getNodePrefs()->gps_enabled) { + // Check for pending work and update activity timer + if (the_mesh.hasPendingWork()) { + lastActive = millis(); + if (nextSleepInSecs < 10) { + nextSleepInSecs += 5; // Extend work time by 5s if still busy + } + } + + // Only sleep if enough time has passed since last activity + if (millis() >= lastActive + (nextSleepInSecs * 1000)) { + board.sleep(1800); // Sleep for 30 minutes, wake on LoRa packet, timer, or button + // Just woke up - reset timers + lastActive = millis(); + nextSleepInSecs = WORK_TIME_SECS; // Stay awake for 5s after wake + } + } } diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 33e32a68a..993b27796 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1116,8 +1116,3 @@ void MyMesh::loop() { uptime_millis += now - last_millis; last_millis = now; } - -// To check if there is pending work -bool MyMesh::hasPendingWork() const { - return _mgr->getOutboundCount(0xFFFFFFFF) > 0; -} diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 343aa44f5..ed9f0c5fc 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -225,7 +225,4 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bridge.begin(); } #endif - - // To check if there is pending work - bool hasPendingWork() const; }; diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index d096d14b2..91bb53bb2 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -2,6 +2,12 @@ #include #include +// Default button polarity: Active-LOW (pressed = LOW) +// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices. +#ifndef USER_BTN_PRESSED +#define USER_BTN_PRESSED LOW +#endif + #define AUTO_OFF_MILLIS 20000 // 20 seconds #define BOOT_SCREEN_MILLIS 4000 // 4 seconds @@ -85,7 +91,7 @@ void UITask::loop() { if (millis() >= _next_read) { int btnState = digitalRead(PIN_USER_BTN); if (btnState != _prevBtnState) { - if (btnState == LOW) { // pressed? + if (btnState == USER_BTN_PRESSED) { // pressed? if (_display->isOn()) { // TODO: any action ? } else { diff --git a/examples/simple_room_server/UITask.cpp b/examples/simple_room_server/UITask.cpp index 46311c5eb..57ea1ee59 100644 --- a/examples/simple_room_server/UITask.cpp +++ b/examples/simple_room_server/UITask.cpp @@ -2,6 +2,12 @@ #include #include +// Default button polarity: Active-LOW (pressed = LOW) +// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices. +#ifndef USER_BTN_PRESSED +#define USER_BTN_PRESSED LOW +#endif + #define AUTO_OFF_MILLIS 20000 // 20 seconds #define BOOT_SCREEN_MILLIS 4000 // 4 seconds @@ -85,7 +91,7 @@ void UITask::loop() { if (millis() >= _next_read) { int btnState = digitalRead(PIN_USER_BTN); if (btnState != _prevBtnState) { - if (btnState == LOW) { // pressed? + if (btnState == USER_BTN_PRESSED) { // pressed? if (_display->isOn()) { // TODO: any action ? } else { diff --git a/examples/simple_sensor/UITask.cpp b/examples/simple_sensor/UITask.cpp index 0694bc3c1..b7fb05cf8 100644 --- a/examples/simple_sensor/UITask.cpp +++ b/examples/simple_sensor/UITask.cpp @@ -2,6 +2,12 @@ #include #include +// Default button polarity: Active-LOW (pressed = LOW) +// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices. +#ifndef USER_BTN_PRESSED +#define USER_BTN_PRESSED LOW +#endif + #define AUTO_OFF_MILLIS 20000 // 20 seconds #define BOOT_SCREEN_MILLIS 4000 // 4 seconds @@ -85,7 +91,7 @@ void UITask::loop() { if (millis() >= _next_read) { int btnState = digitalRead(PIN_USER_BTN); if (btnState != _prevBtnState) { - if (btnState == LOW) { // pressed? + if (btnState == USER_BTN_PRESSED) { // pressed? if (_display->isOn()) { // TODO: any action ? } else { diff --git a/src/Mesh.h b/src/Mesh.h index 00f7ed00f..cb1762a5e 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -220,6 +220,12 @@ class Mesh : public Dispatcher { */ void sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0); + /** + * \brief Check if there is pending work (packets to send) + * \returns true if there are outbound packets waiting + */ + bool hasPendingWork() const { return _mgr->getOutboundCount(0xFFFFFFFF) > 0; } + }; } diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 01b4c980c..f21ef29ab 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -3,6 +3,13 @@ #include #include +// Default button polarity: Active-LOW (pressed = LOW) +// Most LoRa boards use GPIO as a boot button that pulls to GND when pressed. +// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices. +#ifndef USER_BTN_PRESSED +#define USER_BTN_PRESSED LOW +#endif + #if defined(ESP_PLATFORM) #include @@ -56,14 +63,26 @@ class ESP32Board : public mesh::MainBoard { return raw / 4; } - void enterLightSleep(uint32_t secs) { + void enterLightSleep(uint32_t secs, int pin_wake_btn = -1, bool btn_active_high = true) { #if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet + + // Configure wakeup sources: LoRa packet (always active-high) and optionally button + if (pin_wake_btn < 0) { + // No button - just wake on LoRa packet + esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); + } else if (btn_active_high) { + // Button is active-high (same as LoRa) - can use single ext1 source + esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1) | (1ULL << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); + } else { + // Button is active-low (different from LoRa) - use ext0 for button, ext1 for LoRa + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, 0); // Wake when button goes LOW + esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // Wake on LoRa packet + } if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs + esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up after specified seconds } esp_light_sleep_start(); // CPU enters light sleep @@ -75,9 +94,18 @@ class ESP32Board : public mesh::MainBoard { // To check for WiFi status to see if there is active OTA wifi_mode_t mode; esp_err_t err = esp_wifi_get_mode(&mode); - + if (err != ESP_OK) { // WiFi is off ~ No active OTA, safe to go to sleep - enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet +#ifdef PIN_USER_BTN + // Sleep with button wake support (USER_BTN_PRESSED defaults to LOW if not defined) + #if USER_BTN_PRESSED == LOW + enterLightSleep(secs, PIN_USER_BTN, false); // Button is active-low + #else + enterLightSleep(secs, PIN_USER_BTN, true); // Button is active-high + #endif +#else + enterLightSleep(secs); +#endif } } diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/esp32/SerialWifiInterface.cpp index 462e3ecc3..f4ebf5d69 100644 --- a/src/helpers/esp32/SerialWifiInterface.cpp +++ b/src/helpers/esp32/SerialWifiInterface.cpp @@ -4,18 +4,38 @@ void SerialWifiInterface::begin(int port) { // wifi setup is handled outside of this class, only starts the server server.begin(port); + + // Store WiFi credentials for re-enable +#ifdef WIFI_SSID + _ssid = WIFI_SSID; + _password = WIFI_PWD; + _isEnabled = true; // WiFi starts enabled +#else + _ssid = nullptr; + _password = nullptr; +#endif } // ---------- public methods -void SerialWifiInterface::enable() { +void SerialWifiInterface::enable() { if (_isEnabled) return; _isEnabled = true; clearBuffers(); + + // Re-enable WiFi with stored credentials + if (_ssid != nullptr && _password != nullptr) { + WiFi.mode(WIFI_STA); + WiFi.begin(_ssid, _password); + } } void SerialWifiInterface::disable() { _isEnabled = false; + + // Actually turn off WiFi to save power + WiFi.disconnect(true); // Disconnect and clear config + WiFi.mode(WIFI_OFF); // Turn off WiFi radio } size_t SerialWifiInterface::writeFrame(const uint8_t src[], size_t len) { diff --git a/src/helpers/esp32/SerialWifiInterface.h b/src/helpers/esp32/SerialWifiInterface.h index 19291497f..f900d18bc 100644 --- a/src/helpers/esp32/SerialWifiInterface.h +++ b/src/helpers/esp32/SerialWifiInterface.h @@ -8,6 +8,8 @@ class SerialWifiInterface : public BaseSerialInterface { bool _isEnabled; unsigned long _last_write; unsigned long adv_restart_time; + const char* _ssid; + const char* _password; WiFiServer server; WiFiClient client; @@ -39,6 +41,8 @@ class SerialWifiInterface : public BaseSerialInterface { deviceConnected = false; _isEnabled = false; _last_write = 0; + _ssid = nullptr; + _password = nullptr; send_queue_len = recv_queue_len = 0; received_frame_header.type = 0; received_frame_header.length = 0; diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 805f9dfa8..64210bd75 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -63,7 +63,8 @@ class MinewsemiME25LS01Board : public NRF52BoardOTA { digitalWrite(LED_PIN, LOW); #endif #ifdef BUTTON_PIN - nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_HIGH); + // Wake on button press (active-LOW: pressed = LOW) + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); #endif sd_power_system_off(); } diff --git a/variants/minewsemi_me25ls01/platformio.ini b/variants/minewsemi_me25ls01/platformio.ini index fd9c3819f..dacd8d34e 100644 --- a/variants/minewsemi_me25ls01/platformio.ini +++ b/variants/minewsemi_me25ls01/platformio.ini @@ -21,7 +21,6 @@ build_flags = ${nrf52840_me25ls01.build_flags} -I variants/minewsemi_me25ls01 -D me25ls01 -D PIN_USER_BTN=27 - -D USER_BTN_PRESSED=HIGH -D PIN_STATUS_LED=39 -D P_LORA_TX_LED=22 -D RADIO_CLASS=CustomLR1110 diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index f26bdf02e..7bd9b293d 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -77,14 +77,15 @@ class T1000eBoard : public NRF52BoardDCDC { digitalWrite(LED_PIN, HIGH); #endif #ifdef BUTTON_PIN - while(digitalRead(BUTTON_PIN)); + while(digitalRead(BUTTON_PIN) == LOW); // wait for button release #endif #ifdef LED_PIN digitalWrite(LED_PIN, LOW); #endif #ifdef BUTTON_PIN - nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH); + // Wake on button press (active-LOW: pressed = LOW) + nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); #endif sd_power_system_off(); diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 555b182fb..7d165876a 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -10,7 +10,6 @@ build_flags = ${nrf52_base.build_flags} -I src/helpers/ui -D T1000_E -D PIN_USER_BTN=6 - -D USER_BTN_PRESSED=HIGH -D PIN_STATUS_LED=24 -D RADIO_CLASS=CustomLR1110 -D WRAPPER_CLASS=CustomLR1110Wrapper diff --git a/variants/thinknode_m3/platformio.ini b/variants/thinknode_m3/platformio.ini index 8ef2ba54a..88fd487aa 100644 --- a/variants/thinknode_m3/platformio.ini +++ b/variants/thinknode_m3/platformio.ini @@ -10,7 +10,6 @@ build_flags = ${nrf52_base.build_flags} -I src/helpers/ui -D THINKNODE_M3 -D PIN_USER_BTN=12 - -D USER_BTN_PRESSED=LOW -D PIN_STATUS_LED=35 -D RADIO_CLASS=CustomLR1110 -D WRAPPER_CLASS=CustomLR1110Wrapper diff --git a/variants/wio-e5-mini/platformio.ini b/variants/wio-e5-mini/platformio.ini index 837844437..f589ea032 100644 --- a/variants/wio-e5-mini/platformio.ini +++ b/variants/wio-e5-mini/platformio.ini @@ -9,7 +9,6 @@ build_flags = ${stm32_base.build_flags} -D RX_BOOSTED_GAIN=true -D P_LORA_TX_LED=LED_RED -D PIN_USER_BTN=USER_BTN - -D USER_BTN_PRESSED=LOW -I variants/wio-e5-mini build_src_filter = ${stm32_base.build_src_filter} +<../variants/wio-e5-mini> diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 1c46dfeee..fdd7a4fa6 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -4,6 +4,12 @@ #include #include +// Default button polarity: Active-LOW (pressed = LOW) +// Override with -D USER_BTN_PRESSED=HIGH in platformio.ini for rare active-high devices. +#ifndef USER_BTN_PRESSED +#define USER_BTN_PRESSED LOW +#endif + #ifdef XIAO_NRF52 class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { @@ -44,7 +50,7 @@ class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { // set led on and wait for button release before poweroff digitalWrite(PIN_LED, LOW); #ifdef PIN_USER_BTN - while(digitalRead(PIN_USER_BTN) == LOW); + while(digitalRead(PIN_USER_BTN) == USER_BTN_PRESSED); // wait for button release #endif digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_BLUE, HIGH); @@ -52,7 +58,11 @@ class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { #ifdef PIN_USER_BTN // configure button press to wake up when in powered off state + #if USER_BTN_PRESSED == LOW nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); + #else + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH); + #endif #endif sd_power_system_off();