From b5ee89cdb99f1d9a53cfdb74d06298e8a54e4bea Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:33:23 +0200 Subject: [PATCH 01/24] refactor sleep and PowerFSM to allow dynamic light sleep --- src/PowerFSM.cpp | 247 ++++++++-------- src/PowerFSM.h | 12 +- src/main.cpp | 8 +- src/platform/esp32/main-esp32.cpp | 2 - src/sleep.cpp | 461 ++++++++++++++++-------------- src/sleep.h | 45 ++- 6 files changed, 406 insertions(+), 369 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 322b877ff..4fd28aef3 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -19,13 +19,19 @@ #include "sleep.h" #include "target_specific.h" -#if HAS_WIFI && !defined(ARCH_PORTDUINO) || defined(MESHTASTIC_EXCLUDE_WIFI) +#ifdef ARCH_ESP32 +#include "esp32/pm.h" +#include "esp_pm.h" +#endif + +#if HAS_WIFI && !defined(ARCH_PORTDUINO) #include "mesh/wifi/WiFiAPClient.h" #endif #ifndef SLEEP_TIME #define SLEEP_TIME 30 #endif + #if MESHTASTIC_EXCLUDE_POWER_FSM FakeFsm powerFSM; void PowerFSM_setup(){}; @@ -77,85 +83,104 @@ static void shutdownEnter() #include "error.h" -static uint32_t secsSlept; +uint32_t sleepStart; +uint32_t sleepTime; static void lsEnter() { - LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs); + LOG_DEBUG("State: LS"); if (screen) screen->setOn(false); - secsSlept = 0; // How long have we been sleeping this time + ledBlink.set(false); - // LOG_INFO("lsEnter end"); + if (!doPreflightSleep()) { + LOG_DEBUG("State change to LS aborted"); + sleepStart = -1; + powerFSM.trigger(EVENT_WAKE_TIMER); + return; + } + + sleepStart = millis(); + sleepTime = 0; + + powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); + +#ifdef ARCH_ESP32 + doLightSleep(SLEEP_TIME * 1000LL); +#endif } static void lsIdle() { - // LOG_INFO("lsIdle begin ls_secs=%u", getPref_ls_secs()); + if (!doPreflightSleep()) { + powerFSM.trigger(EVENT_WAKE_TIMER); + return; + } + + sleepTime = millis() - sleepStart; #ifdef ARCH_ESP32 + esp_sleep_source_t cause = esp_sleep_get_wakeup_cause(); - // Do we have more sleeping to do? - if (secsSlept < config.power.ls_secs) { - // If some other service would stall sleep, don't let sleep happen yet - if (doPreflightSleep()) { - // Briefly come out of sleep long enough to blink the led once every few seconds - uint32_t sleepTime = SLEEP_TIME; + switch (cause) { + case ESP_SLEEP_WAKEUP_UART: + LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_UART"); + powerFSM.trigger(EVENT_INPUT); + return; - powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); - ledBlink.set(false); // Never leave led on while in light sleep - esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL); - powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); + case ESP_SLEEP_WAKEUP_EXT0: + LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_EXT0"); + powerFSM.trigger(EVENT_LORA_INTERRUPT); + return; - switch (wakeCause2) { - case ESP_SLEEP_WAKEUP_TIMER: - // Normal case: timer expired, we should just go back to sleep ASAP + case ESP_SLEEP_WAKEUP_EXT1: + LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_EXT1"); + powerFSM.trigger(EVENT_PRESS); + return; - ledBlink.set(true); // briefly turn on led - wakeCause2 = doLightSleep(100); // leave led on for 1ms - - secsSlept += sleepTime; - // LOG_INFO("Sleep, flash led!"); - break; - - case ESP_SLEEP_WAKEUP_UART: - // Not currently used (because uart triggers in hw have problems) - powerFSM.trigger(EVENT_SERIAL_CONNECTED); - break; - - default: - // We woke for some other reason (button press, device IRQ interrupt) - -#ifdef BUTTON_PIN - bool pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); -#else - bool pressed = false; -#endif - if (pressed) { // If we woke because of press, instead generate a PRESS event. - powerFSM.trigger(EVENT_PRESS); - } else { - // Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc) - // we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code - powerFSM.trigger(EVENT_WAKE_TIMER); - } - break; - } - } else { - // Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so - delay(100); - } - } else { - // Time to stop sleeping! - ledBlink.set(false); - LOG_INFO("Reached ls_secs, service loop()"); + case ESP_SLEEP_WAKEUP_GPIO: + LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_GPIO"); powerFSM.trigger(EVENT_WAKE_TIMER); + return; + + case ESP_SLEEP_WAKEUP_UNDEFINED: + LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_UNDEFINED"); + powerFSM.trigger(EVENT_WAKE_TIMER); + return; + + default: + if (sleepTime > config.power.ls_secs * 1000LL) { + powerFSM.trigger(EVENT_WAKE_TIMER); + return; + } + break; } + + uint32_t sleepLeft; + + sleepLeft = config.power.ls_secs * 1000LL - sleepTime; + if (sleepLeft > SLEEP_TIME * 1000LL) { + sleepLeft = SLEEP_TIME * 1000LL; + } + + doLightSleep(sleepLeft); #endif } static void lsExit() { - LOG_INFO("Exit state: LS"); +#ifdef ARCH_ESP32 + doLightSleep(LIGHT_SLEEP_ABORT); +#endif + + if (sleepStart != -1) { + sleepTime = millis() - sleepStart; + sleepStart = 0; + + powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); + + LOG_DEBUG("Exit state: LS, slept %d ms", sleepTime); + } } static void nbEnter() @@ -167,12 +192,11 @@ static void nbEnter() // Only ESP32 should turn off bluetooth setBluetoothEnable(false); #endif - - // FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE } static void darkEnter() { + // LOG_DEBUG("State: DARK"); setBluetoothEnable(true); if (screen) screen->setOn(false); @@ -195,11 +219,12 @@ static void serialExit() static void powerEnter() { - // LOG_DEBUG("State: POWER"); + LOG_DEBUG("State: POWER"); if (!isPowered()) { // If we got here, we are in the wrong state - we should be in powered, let that state handle things LOG_INFO("Loss of power in Powered"); powerFSM.trigger(EVENT_POWER_DISCONNECTED); + } else { if (screen) screen->setOn(true); @@ -261,23 +286,16 @@ void PowerFSM_setup() { bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); bool hasPower = isPowered(); + State *stateIDLE; - LOG_INFO("PowerFSM init, USB power=%d", hasPower ? 1 : 0); - powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout"); - - // wake timer expired or a packet arrived - // if we are a router node, we go to NB (no need for bluetooth) otherwise we go to DARK (so we can send message to phone) #ifdef ARCH_ESP32 - powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer"); -#else // Don't go into a no-bluetooth state on low power platforms - powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer"); + stateIDLE = isRouter ? &stateNB : &stateDARK; +#else + stateIDLE = &stateDARK; #endif + LOG_INFO("PowerFSM init, USB power=%d", hasPower ? 1 : 0); - // We need this transition, because we might not transition if we were waiting to enter light-sleep, because when we wake from - // light sleep we _always_ transition to NB or dark and - powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, - "Received packet, exiting light sleep"); - powerFSM.add_transition(&stateNB, &stateNB, EVENT_PACKET_FOR_PHONE, NULL, "Received packet, resetting win wake"); + powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout"); // Handle press events - note: we ignore button presses when in API mode powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press"); @@ -314,12 +332,17 @@ void PowerFSM_setup() powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); - // if we are a router we don't turn the screen on for these things + // stay in dark state as long as we continue talking with phone + powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone"); + powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); + if (!isRouter) { - // if any packet destined for phone arrives, turn on bluetooth at least - powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); + powerFSM.add_transition(&stateLS, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone"); + powerFSM.add_transition(&stateLS, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); + powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); // Removed 2.7: we don't show the nodes individually for every node on the screen anymore + // powerFSM.add_transition(&stateLS, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); // powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); // powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); // powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); @@ -329,29 +352,41 @@ void PowerFSM_setup() powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); // restarts the sleep timer + + } else { + // if we are a router we don't turn the screen on for these things + powerFSM.add_timed_transition( + &stateDARK, &stateNB, + Default::getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL, + "Bluetooth timeout"); } // If we are not in statePOWER but get a serial connection, suppress sleep (and keep the screen on) while connected - powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); - powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); - powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); - powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); - powerFSM.add_transition(&statePOWER, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API"); + powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); + powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); + powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); + powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); + powerFSM.add_transition(&statePOWER, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); // If we get power connected, go to the power connect state - powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); - powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); - powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); - powerFSM.add_transition(&stateON, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect"); + powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect"); + powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect"); + powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect"); + powerFSM.add_transition(&stateON, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect"); - powerFSM.add_transition(&statePOWER, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected"); - // powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected"); + powerFSM.add_transition(&statePOWER, &stateON, EVENT_POWER_DISCONNECTED, NULL, "Power disconnected"); + powerFSM.add_transition(&stateLS, &stateON, EVENT_POWER_DISCONNECTED, NULL, "Power disconnected"); + powerFSM.add_transition(&stateNB, &stateON, EVENT_POWER_DISCONNECTED, NULL, "Power disconnected"); + powerFSM.add_transition(&stateDARK, &stateON, EVENT_POWER_DISCONNECTED, NULL, "Power disconnected"); // the only way to leave state serial is for the client to disconnect (or we timeout and force disconnect them) // when we leave, go to ON (which might not be the correct state if we have power connected, we will fix that in onEnter) - powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect"); + powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_SERIAL_DISCONNECTED, NULL, "Serial disconnect"); - powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone"); + powerFSM.add_transition(&stateLS, stateIDLE, EVENT_LORA_INTERRUPT, NULL, "LoRa interrupt"); + powerFSM.add_transition(&stateLS, stateIDLE, EVENT_WAKE_TIMER, NULL, "Wake timer"); + + powerFSM.add_transition(stateIDLE, stateIDLE, EVENT_WAKE_TIMER, NULL, "Wake timer"); #ifdef USE_EINK // Allow E-Ink devices to suppress the screensaver, if screen timeout set to 0 @@ -368,39 +403,9 @@ void PowerFSM_setup() // We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally) #ifdef ARCH_ESP32 - // See: https://github.com/meshtastic/firmware/issues/1071 - // Don't add power saving transitions if we are a power saving tracker or sensor or have Wifi enabled. Sleep will be initiated - // through the modules - -#if HAS_WIFI || !defined(MESHTASTIC_EXCLUDE_WIFI) - bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || - config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || - config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR; - - if ((isRouter || config.power.is_power_saving) && !isWifiAvailable() && !isTrackerOrSensor) { - powerFSM.add_timed_transition(&stateNB, &stateLS, - Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL, - "Min wake timeout"); - - // If ESP32 and using power-saving, timer mover from DARK to light-sleep - // Also serves purpose of the old DARK to DARK transition(?) See https://github.com/meshtastic/firmware/issues/3517 - powerFSM.add_timed_transition( - &stateDARK, &stateLS, - Default::getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL, - "Bluetooth timeout"); - } else { - // If ESP32, but not using power-saving, check periodically if config has drifted out of stateDark - powerFSM.add_timed_transition(&stateDARK, &stateDARK, - Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), - NULL, "Screen-on timeout"); + if (config.power.is_power_saving) { + powerFSM.add_timed_transition(stateIDLE, &stateLS, WAKE_TIME_MS, NULL, "Min wake timeout"); } -#endif // HAS_WIFI || !defined(MESHTASTIC_EXCLUDE_WIFI) - -#else // (not) ARCH_ESP32 - // If not ESP32, light-sleep not used. Check periodically if config has drifted out of stateDark - powerFSM.add_timed_transition(&stateDARK, &stateDARK, - Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, - "Screen-on timeout"); #endif powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state diff --git a/src/PowerFSM.h b/src/PowerFSM.h index 6330a5fc6..0bfef470e 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -21,6 +21,16 @@ #define EVENT_FIRMWARE_UPDATE 15 // We just received a new firmware update packet from the phone #define EVENT_SHUTDOWN 16 // force a full shutdown now (not just sleep) #define EVENT_INPUT 17 // input broker wants something, we need to wake up and enable screen +#define EVENT_LORA_INTERRUPT 18 +#define EVENT_WEB_REQUEST 19 + +#if defined(ARCH_ESP32) && !defined(WAKE_TIME_MS) +#ifdef CONFIG_FREERTOS_USE_TICKLESS_IDLE +#define WAKE_TIME_MS 500 +#else +#define WAKE_TIME_MS Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs) +#endif +#endif #if MESHTASTIC_EXCLUDE_POWER_FSM class FakeFsm @@ -45,7 +55,7 @@ void PowerFSM_setup(); #else #include extern Fsm powerFSM; -extern State stateON, statePOWER, stateSERIAL, stateDARK; +extern State stateON, statePOWER, stateSERIAL, stateDARK, stateNB, stateLS; void PowerFSM_setup(); #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 31ce039f9..578e518b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -260,7 +260,7 @@ static int32_t ledBlinker() ledBlink.set(ledOn); // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that - return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000); + return (powerStatus->getIsCharging() && powerFSM.getState() != &stateLS) ? 1000 : (ledOn ? 1 : 1000); } uint32_t timeLastPowered = 0; @@ -1482,6 +1482,12 @@ void setup() 1000); } +#ifdef ARCH_ESP32 + if (config.power.is_power_saving) { + initLightSleep(); + } +#endif + // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS powerFSMthread = new PowerFSMThread(); diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 760964119..7b6b9dcff 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -162,8 +162,6 @@ void esp32Setup() WiFiOTA::initialize(); #endif - // enableModemSleep(); - // Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any // false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now. // #define APP_WATCHDOG_SECS 45 diff --git a/src/sleep.cpp b/src/sleep.cpp index 1a5f246c5..162181359 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -10,6 +10,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerMon.h" +#include "concurrency/Lock.h" #include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" @@ -17,18 +18,16 @@ #include "target_specific.h" #ifdef ARCH_ESP32 -// "esp_pm_config_esp32_t is deprecated, please include esp_pm.h and use esp_pm_config_t instead" +#ifdef CONFIG_PM_ENABLE #include "esp32/pm.h" #include "esp_pm.h" +#endif #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif #include "rom/rtc.h" -#include #include #include - -esp_sleep_source_t wakeCause; // the reason we booted this time #endif #include "Throttle.h" @@ -46,11 +45,25 @@ Observable notifyDeepSleep; Observable notifyReboot; #ifdef ARCH_ESP32 +// Wake cause when returning from a deep sleep +esp_sleep_source_t wakeCause; + /// Called to tell observers that light sleep is about to begin Observable notifyLightSleep; /// Called to tell observers that light sleep has just ended, and why it ended Observable notifyLightSleepEnd; + +#ifdef CONFIG_PM_ENABLE +esp_pm_lock_handle_t pmHandle; +#endif + +// internal helper functions +void gpioResetHold(void); +void enableButtonInterrupt(void); + +void enableLoraInterrupt(void); +bool shouldLoraWake(uint32_t msecToWake); #endif // deep sleep support @@ -68,30 +81,21 @@ RTC_DATA_ATTR int bootCount = 0; */ void setCPUFast(bool on) { -#if defined(ARCH_ESP32) && HAS_WIFI && !HAS_TFT - +#if defined(ARCH_ESP32) && !HAS_TFT +#ifdef HAS_WIFI if (isWifiAvailable()) { - /* - * - * There's a newly introduced bug in the espressif framework where WiFi is - * unstable when the frequency is less than 240MHz. - * - * This mostly impacts WiFi AP mode but we'll bump the frequency for - * all WiFi use cases. - * (Added: Dec 23, 2021 by Jm Casler) - */ -#ifndef CONFIG_IDF_TARGET_ESP32C3 +#if !defined(CONFIG_IDF_TARGET_ESP32C3) && defined(WIFI_MAX_PERFORMANCE) LOG_DEBUG("Set CPU to 240MHz because WiFi is in use"); setCpuFrequencyMhz(240); -#endif return; +#endif } +#endif // The Heltec LORA32 V1 runs at 26 MHz base frequency and doesn't react well to switching to 80 MHz... #if !defined(ARDUINO_HELTEC_WIFI_LORA_32) && !defined(CONFIG_IDF_TARGET_ESP32C3) setCpuFrequencyMhz(on ? 240 : 80); #endif - #endif } @@ -100,6 +104,7 @@ void initDeepSleep() { #ifdef ARCH_ESP32 bootCount++; + const char *reason; wakeCause = esp_sleep_get_wakeup_cause(); @@ -147,30 +152,17 @@ void initDeepSleep() LOG_INFO("Booted, wake cause %d (boot count %d), reset_reason=%s", wakeCause, bootCount, reason); #endif -#if SOC_RTCIO_HOLD_SUPPORTED - // If waking from sleep, release any and all RTC GPIOs +#ifdef ARCH_ESP32 if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) { - LOG_DEBUG("Disable any holds on RTC IO pads"); - for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) { - if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) - rtc_gpio_hold_dis((gpio_num_t)i); - - // ESP32 (original) - else if (GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)i)) - gpio_hold_dis((gpio_num_t)i); - } + gpioResetHold(); } #endif - #endif } bool doPreflightSleep() { - if (preflightSleep.notifyObservers(NULL) != 0) - return false; // vetoed - else - return true; + return preflightSleep.notifyObservers(NULL) == 0; } /// Tell devices we are going to sleep and wait for them to handle things @@ -199,6 +191,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN { if (INCLUDE_vTaskSuspend && (msecToWake == portMAX_DELAY)) { LOG_INFO("Enter deep sleep forever"); + } else { LOG_INFO("Enter deep sleep for %u seconds", msecToWake / 1000); } @@ -269,32 +262,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN if (shouldLoraWake(msecToWake)) { enableLoraInterrupt(); } -#ifdef BUTTON_PIN - // Avoid leakage through button pin - if (GPIO_IS_VALID_OUTPUT_GPIO(BUTTON_PIN)) { -#ifdef BUTTON_NEED_PULLUP - pinMode(BUTTON_PIN, INPUT_PULLUP); -#else - pinMode(BUTTON_PIN, INPUT); -#endif - gpio_hold_en((gpio_num_t)BUTTON_PIN); - } -#endif -#ifdef SENSECAP_INDICATOR - // Portexpander definition does not pass GPIO_IS_VALID_OUTPUT_GPIO - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - gpio_hold_en((gpio_num_t)LORA_CS); -#elif defined(ELECROW_PANEL) - // Elecrow panels do not use LORA_CS, do nothing -#else - if (GPIO_IS_VALID_OUTPUT_GPIO(LORA_CS)) { - // LoRa CS (RADIO_NSS) needs to stay HIGH, even during deep sleep - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - gpio_hold_en((gpio_num_t)LORA_CS); - } -#endif + enableButtonInterrupt(); #endif #ifdef HAS_PMU @@ -340,166 +308,256 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN pinMode(I2C_SCL, ANALOG); #endif +#if defined(ARCH_ESP32) && defined(I2C_SDA1) + // Added by https://github.com/meshtastic/firmware/pull/4418 + // Possibly to support Heltec Capsule Sensor? + Wire1.end(); + pinMode(I2C_SDA1, ANALOG); + pinMode(I2C_SCL1, ANALOG); +#endif + console->flush(); cpuDeepSleep(msecToWake); } #ifdef ARCH_ESP32 +bool pmLockAcquired; +concurrency::Lock *pmLightSleepLock; + /** * enter light sleep (preserves ram but stops everything about CPU). * * Returns (after restoring hw state) when the user presses a button or we get a LoRa interrupt */ -esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default +void doLightSleep(uint32_t sleepMsec) { - // LOG_DEBUG("Enter light sleep"); + esp_err_t res; - // LORA_DIO1 is an extended IO pin. Setting it as a wake-up pin will cause problems, such as the indicator device not entering - // LightSleep. -#if defined(SENSECAP_INDICATOR) - return ESP_SLEEP_WAKEUP_TIMER; + assert(pmLightSleepLock); + pmLightSleepLock->lock(); + + if (sleepMsec == LIGHT_SLEEP_ABORT) { + if (pmLockAcquired) { + pmLightSleepLock->unlock(); + return; // nothing to do + } + +#ifdef CONFIG_PM_ENABLE + res = esp_pm_lock_acquire(pmHandle); + assert(res == ESP_OK); +#endif + pmLockAcquired = true; + + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); + gpioResetHold(); + + notifyLightSleepEnd.notifyObservers(esp_sleep_get_wakeup_cause()); + + pmLightSleepLock->unlock(); + return; + } + + if (!pmLockAcquired) { + console->flush(); + +#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE + esp_light_sleep_start(); #endif - waitEnterSleep(false); - notifyLightSleep.notifyObservers(NULL); // Button interrupts are detached here + pmLightSleepLock->unlock(); + return; + } - uint64_t sleepUsec = sleepMsec * 1000LL; - - // NOTE! ESP docs say we must disable bluetooth and wifi before light sleep - - // We want RTC peripherals to stay on - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - -#if defined(BUTTON_PIN) && defined(BUTTON_NEED_PULLUP) - gpio_pullup_en((gpio_num_t)BUTTON_PIN); -#endif - -#ifdef SERIAL0_RX_GPIO - // We treat the serial port as a GPIO for a fast/low power way of waking, if we see a rising edge that means - // someone started to send something - - // gpio 3 is RXD for serialport 0 on ESP32 - // Send a few Z characters to wake the port - - // this doesn't work on TBEAMs when the USB is depowered (causes bogus interrupts) - // So we disable this "wake on serial" feature - because now when a TBEAM (only) has power connected it - // never tries to go to sleep if the user is using the API - // gpio_wakeup_enable((gpio_num_t)SERIAL0_RX_GPIO, GPIO_INTR_LOW_LEVEL); - - // doesn't help - I think the USB-UART chip losing power is pulling the signal low - // gpio_pullup_en((gpio_num_t)SERIAL0_RX_GPIO); - - // alas - can only work if using the refclock, which is limited to about 9600 bps - // assert(uart_set_wakeup_threshold(UART_NUM_0, 3) == ESP_OK); - // assert(esp_sleep_enable_uart_wakeup(0) == ESP_OK); -#endif -#ifdef BUTTON_PIN - // The enableLoraInterrupt() method is using ext0_wakeup, so we are forced to use GPIO wakeup - gpio_num_t pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); - - gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); - esp_sleep_enable_gpio_wakeup(); -#endif -#ifdef INPUTDRIVER_ENCODER_BTN - gpio_wakeup_enable((gpio_num_t)INPUTDRIVER_ENCODER_BTN, GPIO_INTR_LOW_LEVEL); -#endif -#if defined(WAKE_ON_TOUCH) - gpio_wakeup_enable((gpio_num_t)SCREEN_TOUCH_INT, GPIO_INTR_LOW_LEVEL); -#endif enableLoraInterrupt(); + enableButtonInterrupt(); + +#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE + res = esp_sleep_enable_timer_wakeup(sleepMsec * 1000LL); + assert(res == ESP_OK); +#endif + + res = uart_set_wakeup_threshold(UART_NUM_0, 3); + assert(res == ESP_OK); + + res = esp_sleep_enable_uart_wakeup(UART_NUM_0); + assert(res == ESP_OK); + #ifdef PMU_IRQ // wake due to PMU can happen repeatedly if there is no battery installed or the battery fills - if (pmu_found) - gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq + if (pmu_found) { + res = gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq + assert(res == ESP_OK); + } #endif - auto res = esp_sleep_enable_gpio_wakeup(); - if (res != ESP_OK) { - LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d", res); - } - assert(res == ESP_OK); - res = esp_sleep_enable_timer_wakeup(sleepUsec); - if (res != ESP_OK) { - LOG_ERROR("esp_sleep_enable_timer_wakeup result %d", res); - } - assert(res == ESP_OK); - console->flush(); - res = esp_light_sleep_start(); - if (res != ESP_OK) { - LOG_ERROR("esp_light_sleep_start result %d", res); - } - // commented out because it's not that crucial; - // if it sporadically happens the node will go into light sleep during the next round - // assert(res == ESP_OK); - -#ifdef BUTTON_PIN - // Disable wake-on-button interrupt. Re-attach normal button-interrupts - gpio_wakeup_disable(pin); +#if defined(VEXT_ENABLE) + gpio_hold_en((gpio_num_t)VEXT_ENABLE); #endif + +#if defined(RESET_OLED) + gpio_hold_en((gpio_num_t)RESET_OLED); +#endif + #if defined(INPUTDRIVER_ENCODER_BTN) - gpio_wakeup_disable((gpio_num_t)INPUTDRIVER_ENCODER_BTN); + res = gpio_wakeup_enable((gpio_num_t)INPUTDRIVER_ENCODER_BTN, GPIO_INTR_LOW_LEVEL); + assert(res == ESP_OK); #endif #if defined(WAKE_ON_TOUCH) - gpio_wakeup_disable((gpio_num_t)SCREEN_TOUCH_INT); -#endif -#if !defined(SOC_PM_SUPPORT_EXT_WAKEUP) && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) - if (radioType != RF95_RADIO) { - gpio_wakeup_disable((gpio_num_t)LORA_DIO1); - } -#endif -#if defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC) - if (radioType == RF95_RADIO) { - gpio_wakeup_disable((gpio_num_t)RF95_IRQ); - } + res = gpio_wakeup_enable((gpio_num_t)SCREEN_TOUCH_INT, GPIO_INTR_LOW_LEVEL); + assert(res == ESP_OK); #endif - esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); - notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here + res = esp_sleep_enable_gpio_wakeup(); + assert(res == ESP_OK); -#ifdef BUTTON_PIN - if (cause == ESP_SLEEP_WAKEUP_GPIO) { - LOG_INFO("Exit light sleep gpio: btn=%d", - !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); - } else + notifyLightSleep.notifyObservers(NULL); + + console->flush(); + +#ifdef CONFIG_PM_ENABLE + res = esp_pm_lock_release(pmHandle); + assert(res == ESP_OK); #endif - { - LOG_INFO("Exit light sleep cause: %d", cause); - } + pmLockAcquired = false; - return cause; +#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE + esp_light_sleep_start(); +#endif + + pmLightSleepLock->unlock(); } -// not legal on the stock android ESP build - -/** - * enable modem sleep mode as needed and available. Should lower our CPU current draw to an average of about 20mA. - * - * per https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/power_management.html - * - * supposedly according to https://github.com/espressif/arduino-esp32/issues/475 this is already done in arduino - */ -void enableModemSleep() +// Initialize power management settings to allow light sleep +void initLightSleep() { -#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) - static esp_pm_config_t esp32_config; // filled with zeros because bss + esp_err_t res; + +#ifdef CONFIG_PM_ENABLE + res = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "meshtastic", &pmHandle); + assert(res == ESP_OK); + + res = esp_pm_lock_acquire(pmHandle); + assert(res == ESP_OK); + + esp_pm_config_esp32_t pm_config; + pm_config.max_freq_mhz = 80; + pm_config.min_freq_mhz = 20; +#ifdef CONFIG_FREERTOS_USE_TICKLESS_IDLE + pm_config.light_sleep_enable = true; #else - static esp_pm_config_esp32_t esp32_config; // filled with zeros because bss + pm_config.light_sleep_enable = false; #endif -#if CONFIG_IDF_TARGET_ESP32S3 - esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; -#elif CONFIG_IDF_TARGET_ESP32S2 - esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ; -#elif CONFIG_IDF_TARGET_ESP32C6 - esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; -#elif CONFIG_IDF_TARGET_ESP32C3 - esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ; + + res = esp_pm_configure(&pm_config); + assert(res == ESP_OK); + + LOG_INFO("PM config enabled - min_freq_mhz=%d, max_freq_mhz=%d, light_sleep_enable=%d", pm_config.min_freq_mhz, + pm_config.max_freq_mhz, pm_config.light_sleep_enable); +#endif + + pmLightSleepLock = new concurrency::Lock(); + pmLockAcquired = true; +} + +void gpioResetHold() +{ + for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) { + if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) { + rtc_gpio_hold_dis((gpio_num_t)i); + rtc_gpio_deinit((gpio_num_t)i); + + } else if (GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)i)) + gpio_hold_dis((gpio_num_t)i); + } +} + +void enableButtonInterrupt() +{ + esp_err_t res; + gpio_num_t pin; + +#ifdef BUTTON_PIN + pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); +#ifdef SOC_PM_SUPPORT_EXT_WAKEUP + if (rtc_gpio_is_valid_gpio(pin)) { + LOG_DEBUG("Setup button pin (GPIO%02d) with wakeup by ext2 source", pin); +#ifdef BUTTON_NEED_PULLUP + res = rtc_gpio_pullup_en(pin); + assert(res == ESP_OK); +#endif + res = rtc_gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + res = esp_sleep_enable_ext1_wakeup(1ULL << pin, ESP_EXT1_WAKEUP_ANY_LOW); + + } else { + LOG_DEBUG("Setup button pin (GPIO%02d) with wakeup by GPIO interrupt", pin); +#ifdef BUTTON_NEED_PULLUP + gpio_pullup_en(pin); + assert(res == ESP_OK); +#endif + res = gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + res = gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); + } #else - esp32_config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ; +#ifdef BUTTON_NEED_PULLUP + gpio_pullup_en(pin); + assert(res == ESP_OK); #endif - esp32_config.min_freq_mhz = 20; // 10Mhz is minimum recommended - esp32_config.light_sleep_enable = false; - int rv = esp_pm_configure(&esp32_config); - LOG_DEBUG("Sleep request result %x", rv); + res = gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + res = gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); + LOG_DEBUG("Setup button pin (GPIO%02d) with wakeup by GPIO interrupt", pin); +#endif + assert(res == ESP_OK); +#endif +} + +void enableLoraInterrupt() +{ + esp_err_t res; + gpio_num_t pin; + + pin = GPIO_NUM_NC; + +#if defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + pin = (gpio_num_t)LORA_DIO1; +#elif defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC) + pin = (gpio_num_t)RF95_IRQ; +#endif + + assert(pin != GPIO_NUM_NC); + +#if defined(LORA_RESET) && (LORA_RESET != RADIOLIB_NC) + gpio_hold_en((gpio_num_t)LORA_RESET); +#endif + +#if SOC_PM_SUPPORT_EXT_WAKEUP + if (rtc_gpio_is_valid_gpio(pin)) { + LOG_DEBUG("Setup radio interrupt (GPIO%02d) with wakeup by ext1 source", pin); + res = rtc_gpio_pulldown_en((gpio_num_t)pin); + assert(res == ESP_OK); + res = rtc_gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + res = esp_sleep_enable_ext0_wakeup(pin, HIGH); + + } else { + LOG_DEBUG("Setup radio interrupt (GPIO%02d) with wakeup by GPIO interrupt", pin); + res = gpio_pulldown_en((gpio_num_t)pin); + assert(res == ESP_OK); + res = gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + res = gpio_wakeup_enable(pin, GPIO_INTR_HIGH_LEVEL); + } +#else + LOG_DEBUG("Setup radio interrupt (GPIO%02d) with wakeup by GPIO interrupt", pin); + res = gpio_pulldown_en((gpio_num_t)pin); + assert(res == ESP_OK); + res = gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + res = gpio_wakeup_enable(pin, GPIO_INTR_HIGH_LEVEL); +#endif + assert(res == ESP_OK); } bool shouldLoraWake(uint32_t msecToWake) @@ -507,39 +565,4 @@ bool shouldLoraWake(uint32_t msecToWake) return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER); } - -void enableLoraInterrupt() -{ - esp_err_t res; -#if SOC_PM_SUPPORT_EXT_WAKEUP && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) - res = gpio_pulldown_en((gpio_num_t)LORA_DIO1); - if (res != ESP_OK) { - LOG_ERROR("gpio_pulldown_en(LORA_DIO1) result %d", res); - } -#if defined(LORA_RESET) && (LORA_RESET != RADIOLIB_NC) - res = gpio_pullup_en((gpio_num_t)LORA_RESET); - if (res != ESP_OK) { - LOG_ERROR("gpio_pullup_en(LORA_RESET) result %d", res); - } -#endif -#if defined(LORA_CS) && (LORA_CS != RADIOLIB_NC) && !defined(ELECROW_PANEL) - gpio_pullup_en((gpio_num_t)LORA_CS); -#endif - - LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1); - gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); - -#elif defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) - if (radioType != RF95_RADIO) { - LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1); - gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); // SX126x/SX128x interrupt, active high - } -#endif -#if defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC) - if (radioType == RF95_RADIO) { - LOG_INFO("setup RF95_IRQ (GPIO%02d) with wakeup by gpio interrupt", RF95_IRQ); - gpio_wakeup_enable((gpio_num_t)RF95_IRQ, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high - } -#endif -} #endif diff --git a/src/sleep.h b/src/sleep.h index f780fb3c0..9ce97258e 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -4,52 +4,47 @@ #include "Observer.h" #include "configuration.h" -void doDeepSleep(uint32_t msecToWake, bool skipPreflight, bool skipSaveNodeDb), cpuDeepSleep(uint32_t msecToWake); - -#ifdef ARCH_ESP32 -#include "esp_sleep.h" -esp_sleep_wakeup_cause_t doLightSleep(uint64_t msecToWake); - -extern esp_sleep_source_t wakeCause; -#endif - #ifdef HAS_PMU #include "XPowersLibInterface.hpp" extern XPowersLibInterface *PMU; #endif -// Perform power on init that we do on each wake from deep sleep +#ifdef ARCH_ESP32 +#include "esp_sleep.h" + +#define LIGHT_SLEEP_ABORT 0 + +void initLightSleep(); +void doLightSleep(uint32_t msecToWake); +#endif + +// perform power on init that we do on each wake from deep sleep void initDeepSleep(); +void doDeepSleep(uint32_t msecToWake, bool skipPreflight, bool skipSaveNodeDb), cpuDeepSleep(uint32_t msecToWake); void setCPUFast(bool on); -/** return true if sleep is allowed right now */ +// returns true if sleep is allowed right now bool doPreflightSleep(); extern int bootCount; -// is bluetooth sw currently running? -extern bool bluetoothOn; - -/// Called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen +// called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen extern Observable preflightSleep; -/// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 +// called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 extern Observable notifyDeepSleep; -/// Called to tell observers we are rebooting ASAP. Must return 0 +// called to tell observers we are rebooting ASAP. Must return 0 extern Observable notifyReboot; #ifdef ARCH_ESP32 -/// Called to tell observers that light sleep is about to begin +// wake cause, set when init from deep sleep is called +extern esp_sleep_source_t wakeCause; + +/// called to tell observers that light sleep is about to begin extern Observable notifyLightSleep; -/// Called to tell observers that light sleep has just ended, and why it ended +/// called to tell observers that light sleep has just ended, and why it ended extern Observable notifyLightSleepEnd; #endif - -void enableModemSleep(); -#ifdef ARCH_ESP32 -void enableLoraInterrupt(); -bool shouldLoraWake(uint32_t msecToWake); -#endif \ No newline at end of file From 7c63147ce772a036aa92e87cac6a0ac524e50d85 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:36:08 +0200 Subject: [PATCH 02/24] prevent light sleep when modules are busy for more than one timer tick --- src/mesh/PhoneAPI.cpp | 4 ++ src/mesh/PhoneAPI.h | 7 +++- src/mesh/RadioLibInterface.cpp | 5 ++- src/mesh/Router.cpp | 10 +++++ src/mesh/Router.h | 10 +++++ src/mesh/StreamAPI.cpp | 4 +- src/mesh/http/ContentHandler.cpp | 40 +++++++++++++++++-- src/mesh/http/ContentHandler.h | 7 +++- .../Telemetry/EnvironmentTelemetry.cpp | 6 +++ src/modules/Telemetry/PowerTelemetry.cpp | 3 ++ 10 files changed, 88 insertions(+), 8 deletions(-) diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index a3a8a2087..eb5cc17bf 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -16,6 +16,7 @@ #include "SPILock.h" #include "TypeConversions.h" #include "main.h" +#include "sleep.h" #include "xmodem.h" #if FromRadio_size > MAX_TO_FROM_RADIO_SIZE @@ -38,10 +39,13 @@ PhoneAPI::PhoneAPI() { lastContactMsec = millis(); std::fill(std::begin(recentToRadioPacketIds), std::end(recentToRadioPacketIds), 0); + + preflightSleepObserver.observe(&preflightSleep); } PhoneAPI::~PhoneAPI() { + preflightSleepObserver.unobserve(&preflightSleep); close(); } diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 0d7772d17..cd475535e 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -172,4 +172,9 @@ class PhoneAPI /// If the mesh service tells us fromNum has changed, tell the phone virtual int onNotify(uint32_t newValue) override; -}; + + int preflightSleepCb(void *unused = NULL) { return available(); } + + CallbackObserver preflightSleepObserver = + CallbackObserver(this, &PhoneAPI::preflightSleepCb); +}; \ No newline at end of file diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 946b1982c..54b04cc1e 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -1,6 +1,7 @@ #include "RadioLibInterface.h" #include "MeshTypes.h" #include "NodeDB.h" +#include "PowerFSM.h" #include "PowerMon.h" #include "SPILock.h" #include "Throttle.h" @@ -245,6 +246,8 @@ currently active. */ void RadioLibInterface::onNotify(uint32_t notification) { + powerFSM.trigger(EVENT_LORA_INTERRUPT); + switch (notification) { case ISR_TX: handleTransmitInterrupt(); @@ -532,4 +535,4 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp) return res == RADIOLIB_ERR_NONE; } -} \ No newline at end of file +} diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index c7e32c4a1..44cd03c25 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -11,6 +11,7 @@ #include "mesh-pb-constants.h" #include "meshUtils.h" #include "modules/RoutingModule.h" +#include "sleep.h" #if !MESHTASTIC_EXCLUDE_MQTT #include "mqtt/MQTT.h" #endif @@ -56,6 +57,13 @@ Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRA // init Lockguard for crypt operations assert(!cryptLock); cryptLock = new concurrency::Lock(); + + preflightSleepObserver.observe(&preflightSleep); +} + +Router::~Router() +{ + preflightSleepObserver.unobserve(&preflightSleep); } /** @@ -65,6 +73,7 @@ Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRA int32_t Router::runOnce() { meshtastic_MeshPacket *mp; + while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { // printPacket("handle fromRadioQ", mp); perhapsHandleReceived(mp); @@ -150,6 +159,7 @@ void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket void Router::setReceivedMessage() { // LOG_DEBUG("set interval to ASAP"); + powerFSM.trigger(EVENT_WAKE_TIMER); setInterval(0); // Run ASAP, so we can figure out our correct sleep time runASAP = true; } diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 58ca50f3d..d6d2869b4 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -29,6 +29,12 @@ class Router : protected concurrency::OSThread, protected PacketHistory */ Router(); + /** + * Destructor + * + */ + ~Router(); + /** * Currently we only allow one interface, that may change in the future */ @@ -138,6 +144,10 @@ class Router : protected concurrency::OSThread, protected PacketHistory /** Frees the provided packet, and generates a NAK indicating the specifed error while sending */ void abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p); + + int preflightSleepCb(void *unused = NULL) { return !fromRadioQueue.isEmpty(); } + + CallbackObserver preflightSleepObserver = CallbackObserver(this, &Router::preflightSleepCb); }; enum DecodeState { DECODE_SUCCESS, DECODE_FAILURE, DECODE_FATAL }; diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 20026767e..e088bf885 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -171,6 +171,8 @@ int32_t StreamAPI::readStream() void StreamAPI::emitTxBuffer(size_t len) { if (len != 0) { + powerFSM.trigger(EVENT_WAKE_TIMER); + txBuf[0] = START1; txBuf[1] = START2; txBuf[2] = (len >> 8) & 0xff; @@ -224,4 +226,4 @@ void StreamAPI::onConnectionChanged(bool connected) // received a packet in a while powerFSM.trigger(EVENT_SERIAL_DISCONNECTED); } -} \ No newline at end of file +} diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 42ebb8417..c1c4512e6 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -148,7 +148,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) { - LOG_DEBUG("webAPI handleAPIv1FromRadio"); /* @@ -163,6 +162,8 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) // std::string paramAll = "all"; std::string valueAll; + powerFSM.trigger(EVENT_WEB_REQUEST); + // Status code is 200 OK by default. res->setHeader("Content-Type", "application/x-protobuf"); res->setHeader("Access-Control-Allow-Origin", "*"); @@ -207,6 +208,8 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) { LOG_DEBUG("webAPI handleAPIv1ToRadio"); + powerFSM.trigger(EVENT_WEB_REQUEST); + /* For documentation, see: https://meshtastic.org/docs/development/device/http-api @@ -316,6 +319,8 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels) void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res) { + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "application/json"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); @@ -349,6 +354,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) ResourceParameters *params = req->getParams(); std::string paramValDelete; + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "application/json"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "DELETE"); @@ -384,6 +391,9 @@ void handleStatic(HTTPRequest *req, HTTPResponse *res) ResourceParameters *params = req->getParams(); std::string parameter1; + + powerFSM.trigger(EVENT_WEB_REQUEST); + // Print the first parameter value if (params->getPathParameter(0, parameter1)) { @@ -469,8 +479,10 @@ void handleStatic(HTTPRequest *req, HTTPResponse *res) void handleFormUpload(HTTPRequest *req, HTTPResponse *res) { - LOG_DEBUG("Form Upload - Disable keep-alive"); + + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Connection", "close"); // First, we need to check the encoding of the form that we have received. @@ -597,6 +609,8 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) ResourceParameters *params = req->getParams(); std::string content; + powerFSM.trigger(EVENT_WEB_REQUEST); + if (!params->getQueryParameter("content", content)) { content = "json"; } @@ -701,6 +715,8 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res) ResourceParameters *params = req->getParams(); std::string content; + powerFSM.trigger(EVENT_WEB_REQUEST); + if (!params->getQueryParameter("content", content)) { content = "json"; } @@ -774,6 +790,8 @@ void handleHotspot(HTTPRequest *req, HTTPResponse *res) { LOG_INFO("Hotspot Request"); + powerFSM.trigger(EVENT_WEB_REQUEST); + /* If we don't do a redirect, be sure to return a "Success" message otherwise iOS will have trouble detecting that the connection to the SoftAP worked. @@ -791,6 +809,8 @@ void handleHotspot(HTTPRequest *req, HTTPResponse *res) void handleDeleteFsContent(HTTPRequest *req, HTTPResponse *res) { + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "text/html"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); @@ -808,6 +828,8 @@ void handleDeleteFsContent(HTTPRequest *req, HTTPResponse *res) void handleAdmin(HTTPRequest *req, HTTPResponse *res) { + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "text/html"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); @@ -820,6 +842,8 @@ void handleAdmin(HTTPRequest *req, HTTPResponse *res) void handleAdminSettings(HTTPRequest *req, HTTPResponse *res) { + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "text/html"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); @@ -842,6 +866,8 @@ void handleAdminSettings(HTTPRequest *req, HTTPResponse *res) void handleAdminSettingsApply(HTTPRequest *req, HTTPResponse *res) { + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "text/html"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "POST"); @@ -854,6 +880,8 @@ void handleAdminSettingsApply(HTTPRequest *req, HTTPResponse *res) void handleFs(HTTPRequest *req, HTTPResponse *res) { + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "text/html"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); @@ -866,6 +894,8 @@ void handleFs(HTTPRequest *req, HTTPResponse *res) void handleRestart(HTTPRequest *req, HTTPResponse *res) { + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "text/html"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); @@ -879,6 +909,8 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res) void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) { + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "application/json"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "POST"); @@ -917,6 +949,8 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) { + powerFSM.trigger(EVENT_WEB_REQUEST); + res->setHeader("Content-Type", "application/json"); res->setHeader("Access-Control-Allow-Origin", "*"); res->setHeader("Access-Control-Allow-Methods", "GET"); @@ -956,4 +990,4 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) res->print(value->Stringify().c_str()); delete value; } -#endif \ No newline at end of file +#endif diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h index 2066a6d57..1c62ffd29 100644 --- a/src/mesh/http/ContentHandler.h +++ b/src/mesh/http/ContentHandler.h @@ -1,4 +1,7 @@ #pragma once + +#define HTTP_API_SESSION_TIMEOUT_MS 5000 + void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer); // Declare some handler functions for the various URLs on the server @@ -33,5 +36,5 @@ class HttpAPI : public PhoneAPI protected: /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this -}; \ No newline at end of file + virtual bool checkIsConnected() override { return millis() - lastContactMsec < HTTP_API_SESSION_TIMEOUT_MS; } +}; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 8926b171c..06e9de1d2 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -536,6 +536,9 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m { bool valid = true; bool hasSensor = false; + + powerFSM.trigger(EVENT_WAKE_TIMER); // ensure we're not light-sleeping + m->time = getTime(); m->which_variant = meshtastic_Telemetry_environment_metrics_tag; m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero; @@ -721,6 +724,9 @@ meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply() bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + + powerFSM.trigger(EVENT_WAKE_TIMER); // ensure we're not light-sleeping + m.which_variant = meshtastic_Telemetry_environment_metrics_tag; m.time = getTime(); #ifdef T1000X_SENSOR_EN diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 35409edef..390ef8654 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -190,6 +190,9 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) { bool valid = false; + + powerFSM.trigger(EVENT_WAKE_TIMER); // ensure we're not light-sleeping + m->time = getTime(); m->which_variant = meshtastic_Telemetry_power_metrics_tag; From 29f58421de3de97c56090071585723bc8e062605 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:36:47 +0200 Subject: [PATCH 03/24] dynamically adjust measurement interval in power management --- src/Power.cpp | 36 +++++++++++++++++++++++++++++++++++- src/power.h | 11 +++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Power.cpp b/src/Power.cpp index bf74f6e53..c97c28028 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -586,6 +586,19 @@ Power::Power() : OSThread("Power") #ifdef DEBUG_HEAP lastheap = memGet.getFreeHeap(); #endif + +#ifdef ARCH_ESP32 + lsObserver.observe(¬ifyLightSleep); + lsEndObserver.observe(¬ifyLightSleepEnd); +#endif +} + +Power::~Power() +{ +#ifdef ARCH_ESP32 + lsObserver.unobserve(¬ifyLightSleep); + lsEndObserver.unobserve(¬ifyLightSleepEnd); +#endif } bool Power::analogInit() @@ -782,6 +795,8 @@ void Power::readPowerStatus() OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time OptionalBool isChargingNow = OptUnknown; + powerFSM.trigger(EVENT_WAKE_TIMER); // ensure we're not light-sleeping + if (batteryLevel) { hasBattery = batteryLevel->isBatteryConnect() ? OptTrue : OptFalse; usbPowered = batteryLevel->isVbusIn() ? OptTrue : OptFalse; @@ -936,10 +951,29 @@ int32_t Power::runOnce() PMU->clearIrqStatus(); } #endif + // Only read once every 20 seconds once the power status for the app has been initialized - return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME; + if (statusHandler && statusHandler->isInitialized() && interval == 0) { + setInterval(20 * 1000UL); + } + + return RUN_SAME; } +#ifdef ARCH_ESP32 +int Power::beforeLightSleep(void *unused) +{ + setInterval(config.power.ls_secs * 1000UL); + return 0; +} + +int Power::afterLightSleep(esp_sleep_wakeup_cause_t cause) +{ + setInterval(20 * 1000UL); + return 0; +} +#endif + /** * Init the power manager chip * diff --git a/src/power.h b/src/power.h index e96f5b022..477f33555 100644 --- a/src/power.h +++ b/src/power.h @@ -109,6 +109,7 @@ class Power : private concurrency::OSThread Observable newStatus; Power(); + ~Power(); void powerCommandsCheck(); void readPowerStatus(); @@ -139,6 +140,16 @@ class Power : private concurrency::OSThread #ifdef DEBUG_HEAP uint32_t lastheap; #endif + +#ifdef ARCH_ESP32 + // Get notified when lightsleep begins and ends to set power refresh interval + CallbackObserver lsObserver = CallbackObserver(this, &Power::beforeLightSleep); + CallbackObserver lsEndObserver = + CallbackObserver(this, &Power::afterLightSleep); + + int beforeLightSleep(void *unused); + int afterLightSleep(esp_sleep_wakeup_cause_t cause); +#endif }; extern Power *power; From 62379b72069493d1f4e49201a3ecfa06e0f66bd7 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:40:27 +0200 Subject: [PATCH 04/24] switch heltec_v3 variant to esp-idf sdk with power management enabled --- variants/esp32s3/heltec_v3/platformio.ini | 6 ++++-- variants/esp32s3/heltec_v3/variant.h | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/variants/esp32s3/heltec_v3/platformio.ini b/variants/esp32s3/heltec_v3/platformio.ini index b521e11ca..cef4f916c 100644 --- a/variants/esp32s3/heltec_v3/platformio.ini +++ b/variants/esp32s3/heltec_v3/platformio.ini @@ -1,10 +1,12 @@ -[env:heltec-v3] +[env:heltec-v3] extends = esp32s3_base +platform_packages = + platformio/framework-arduinoespressif32 @ https://github.com/m1nl/arduino-esp32/archive/refs/tags/2.0.17+5ae9873e.tar.gz ; disable WiFi IRAM optimizations in ESP-IDF board = heltec_wifi_lora_32_V3 board_level = pr board_check = true board_build.partitions = default_8MB.csv -build_flags = +build_flags = ${esp32s3_base.build_flags} -D HELTEC_V3 -I variants/esp32s3/heltec_v3 diff --git a/variants/esp32s3/heltec_v3/variant.h b/variants/esp32s3/heltec_v3/variant.h index d760c3b7f..c888f56f0 100644 --- a/variants/esp32s3/heltec_v3/variant.h +++ b/variants/esp32s3/heltec_v3/variant.h @@ -1,5 +1,7 @@ #define LED_PIN LED +#define HAS_32768HZ + #define USE_SSD1306 // Heltec_v3 has a SSD1306 display #define RESET_OLED RST_OLED From d0af8168d1d4369f2e41685eb328e8f47ef23cdb Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 16:52:24 +0200 Subject: [PATCH 05/24] move sleep wake timeout to PhoneAPI --- src/mesh/PhoneAPI.h | 5 +++-- src/mesh/http/ContentHandler.h | 7 ++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index cd475535e..628896dc5 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -20,6 +20,7 @@ #define SPECIAL_NONCE_ONLY_CONFIG 69420 #define SPECIAL_NONCE_ONLY_NODES 69421 // ( ͡° ͜ʖ ͡°) +#define WAKE_SESSION_TIMEOUT_MS 10000 /** * Provides our protobuf based API which phone/PC clients can use to talk to our device @@ -173,8 +174,8 @@ class PhoneAPI /// If the mesh service tells us fromNum has changed, tell the phone virtual int onNotify(uint32_t newValue) override; - int preflightSleepCb(void *unused = NULL) { return available(); } + int preflightSleepCb(void *unused = NULL) { return available() && (millis() - lastContactMsec) < WAKE_SESSION_TIMEOUT_MS; } CallbackObserver preflightSleepObserver = CallbackObserver(this, &PhoneAPI::preflightSleepCb); -}; \ No newline at end of file +}; diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h index 1c62ffd29..2066a6d57 100644 --- a/src/mesh/http/ContentHandler.h +++ b/src/mesh/http/ContentHandler.h @@ -1,7 +1,4 @@ #pragma once - -#define HTTP_API_SESSION_TIMEOUT_MS 5000 - void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer); // Declare some handler functions for the various URLs on the server @@ -36,5 +33,5 @@ class HttpAPI : public PhoneAPI protected: /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override { return millis() - lastContactMsec < HTTP_API_SESSION_TIMEOUT_MS; } -}; + virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this +}; \ No newline at end of file From 41e79c98205c6c01d4f40a2afef3a52b04fc82c2 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 25 Apr 2025 00:24:59 +0200 Subject: [PATCH 06/24] use events to stay in idle state --- src/PowerFSM.cpp | 6 ++++-- src/PowerFSM.h | 2 +- src/mesh/RadioLibInterface.cpp | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 4fd28aef3..904fe8362 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -130,7 +130,7 @@ static void lsIdle() case ESP_SLEEP_WAKEUP_EXT0: LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_EXT0"); - powerFSM.trigger(EVENT_LORA_INTERRUPT); + powerFSM.trigger(EVENT_RADIO_INTERRUPT); return; case ESP_SLEEP_WAKEUP_EXT1: @@ -383,10 +383,12 @@ void PowerFSM_setup() // when we leave, go to ON (which might not be the correct state if we have power connected, we will fix that in onEnter) powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_SERIAL_DISCONNECTED, NULL, "Serial disconnect"); - powerFSM.add_transition(&stateLS, stateIDLE, EVENT_LORA_INTERRUPT, NULL, "LoRa interrupt"); powerFSM.add_transition(&stateLS, stateIDLE, EVENT_WAKE_TIMER, NULL, "Wake timer"); + powerFSM.add_transition(&stateLS, stateIDLE, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); powerFSM.add_transition(stateIDLE, stateIDLE, EVENT_WAKE_TIMER, NULL, "Wake timer"); + powerFSM.add_transition(stateIDLE, stateIDLE, EVENT_WEB_REQUEST, NULL, "Web request"); + powerFSM.add_transition(stateIDLE, stateIDLE, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); #ifdef USE_EINK // Allow E-Ink devices to suppress the screensaver, if screen timeout set to 0 diff --git a/src/PowerFSM.h b/src/PowerFSM.h index 0bfef470e..887c4dccb 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -21,7 +21,7 @@ #define EVENT_FIRMWARE_UPDATE 15 // We just received a new firmware update packet from the phone #define EVENT_SHUTDOWN 16 // force a full shutdown now (not just sleep) #define EVENT_INPUT 17 // input broker wants something, we need to wake up and enable screen -#define EVENT_LORA_INTERRUPT 18 +#define EVENT_RADIO_INTERRUPT 18 #define EVENT_WEB_REQUEST 19 #if defined(ARCH_ESP32) && !defined(WAKE_TIME_MS) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 54b04cc1e..2b4dcfa4b 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -246,7 +246,7 @@ currently active. */ void RadioLibInterface::onNotify(uint32_t notification) { - powerFSM.trigger(EVENT_LORA_INTERRUPT); + powerFSM.trigger(EVENT_RADIO_INTERRUPT); switch (notification) { case ISR_TX: From 6d241dbc33958178fe7225fd36e89a1ce9ee11b3 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 2 May 2025 21:06:13 +0200 Subject: [PATCH 07/24] add assertion for gpio_wakeup_enable calls --- src/sleep.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index 162181359..425eef613 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -398,11 +398,11 @@ void doLightSleep(uint32_t sleepMsec) gpio_hold_en((gpio_num_t)RESET_OLED); #endif -#if defined(INPUTDRIVER_ENCODER_BTN) +#ifdef INPUTDRIVER_ENCODER_BTN res = gpio_wakeup_enable((gpio_num_t)INPUTDRIVER_ENCODER_BTN, GPIO_INTR_LOW_LEVEL); assert(res == ESP_OK); #endif -#if defined(WAKE_ON_TOUCH) +#if defined(T_WATCH_S3) || defined(ELECROW) res = gpio_wakeup_enable((gpio_num_t)SCREEN_TOUCH_INT, GPIO_INTR_LOW_LEVEL); assert(res == ESP_OK); #endif From 223859d28b7ab33ca82d835f85b3f605fc4d11bf Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 13 May 2025 14:21:16 +0200 Subject: [PATCH 08/24] allow light sleep when MQTT is enabled --- src/mqtt/MQTT.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 7f7a9d511..4e04e39ba 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -589,14 +589,12 @@ int32_t MQTT::runOnce() return 30000; } } else { - // we are connected to server, check often for new requests on the TCP port if (!wantConnection) { LOG_INFO("MQTT link not needed, drop"); pubSub.disconnect(); } - powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // Suppress entering light sleep (because that would turn off bluetooth) - return 20; + return 100; } #else // No networking available, return default interval From 5f3613d0e1a01e0626f6aebd4a85088d4279e125 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 13 May 2025 14:23:50 +0200 Subject: [PATCH 09/24] improve logging fix logging make log entry more consistent --- src/PowerFSM.cpp | 6 +++--- src/sleep.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 904fe8362..65ac1fc0c 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -179,7 +179,7 @@ static void lsExit() powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); - LOG_DEBUG("Exit state: LS, slept %d ms", sleepTime); + LOG_DEBUG("Exit state: LS, stayed %d ms in light-sleep state", sleepTime); } } @@ -196,7 +196,7 @@ static void nbEnter() static void darkEnter() { - // LOG_DEBUG("State: DARK"); + // LOG_DEBUG("State: DARK"); i setBluetoothEnable(true); if (screen) screen->setOn(false); @@ -293,7 +293,7 @@ void PowerFSM_setup() #else stateIDLE = &stateDARK; #endif - LOG_INFO("PowerFSM init, USB power=%d", hasPower ? 1 : 0); + LOG_INFO("PowerFSM init, USB power=%d, is_power_saving=%d", hasPower ? 1 : 0, config.power.is_power_saving ? 1 : 0); powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout"); diff --git a/src/sleep.cpp b/src/sleep.cpp index 425eef613..ac338be0b 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -480,7 +480,7 @@ void enableButtonInterrupt() pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); #ifdef SOC_PM_SUPPORT_EXT_WAKEUP if (rtc_gpio_is_valid_gpio(pin)) { - LOG_DEBUG("Setup button pin (GPIO%02d) with wakeup by ext2 source", pin); + LOG_DEBUG("Setup button pin (GPIO%02d) with wakeup by ext1 source", pin); #ifdef BUTTON_NEED_PULLUP res = rtc_gpio_pullup_en(pin); assert(res == ESP_OK); @@ -534,7 +534,7 @@ void enableLoraInterrupt() #if SOC_PM_SUPPORT_EXT_WAKEUP if (rtc_gpio_is_valid_gpio(pin)) { - LOG_DEBUG("Setup radio interrupt (GPIO%02d) with wakeup by ext1 source", pin); + LOG_DEBUG("Setup radio interrupt (GPIO%02d) with wakeup by ext0 source", pin); res = rtc_gpio_pulldown_en((gpio_num_t)pin); assert(res == ESP_OK); res = rtc_gpio_hold_en((gpio_num_t)pin); From 177fdd674dd7f77241da471bd85ff2d766c7b720 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 6 Jun 2025 21:20:20 +0200 Subject: [PATCH 10/24] prevent zero-byte read from serial causing too frequent wakeups --- src/mesh/StreamAPI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index e088bf885..1d3eb950b 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -155,12 +155,12 @@ int32_t StreamAPI::readStream() // If we didn't just fail the packet and we now have the right # of bytes, parse it handleToRadio(rxBuf + HEADER_LEN, len); + // we had bytes available this time, so assume we might have them next time also + lastRxMsec = millis(); } } } - // we had bytes available this time, so assume we might have them next time also - lastRxMsec = millis(); return 0; } } From 8970cadb75f08d09d742518e76e5d3eb701594d4 Mon Sep 17 00:00:00 2001 From: m1nl Date: Mon, 23 Jun 2025 00:21:41 +0200 Subject: [PATCH 11/24] do not detach button interrupt when going into light sleep --- src/input/ButtonThread.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp index 32882f7ae..6cf43f7ed 100644 --- a/src/input/ButtonThread.cpp +++ b/src/input/ButtonThread.cpp @@ -302,7 +302,8 @@ void ButtonThread::detachButtonInterrupts() // Allows sleep.cpp to configure its own interrupts, which wake the device on user-button press int ButtonThread::beforeLightSleep(void *unused) { - detachButtonInterrupts(); + // detachButtonInterrupts(); + // not really needed and stays in conflict with dynamic light sleep return 0; // Indicates success } From 15483be9375a3f19e7293ff11eb8b90c5721c7b7 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 02:13:59 +0200 Subject: [PATCH 12/24] remove stateNB, improve regular (non-dynamic) light-sleep handling --- src/PowerFSM.cpp | 117 ++++++++++++-------------------------------- src/PowerFSM.h | 7 +-- src/main.cpp | 8 ++- src/mesh/Router.cpp | 1 - 4 files changed, 41 insertions(+), 92 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 65ac1fc0c..bf250507a 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -93,33 +93,37 @@ static void lsEnter() screen->setOn(false); ledBlink.set(false); - if (!doPreflightSleep()) { - LOG_DEBUG("State change to LS aborted"); - sleepStart = -1; - powerFSM.trigger(EVENT_WAKE_TIMER); - return; - } - - sleepStart = millis(); + sleepStart = -1; sleepTime = 0; powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); - -#ifdef ARCH_ESP32 - doLightSleep(SLEEP_TIME * 1000LL); -#endif } static void lsIdle() { if (!doPreflightSleep()) { +#ifdef DYNAMIC_LIGHT_SLEEP powerFSM.trigger(EVENT_WAKE_TIMER); +#endif return; } + if (sleepStart == -1) { + sleepStart = millis(); + } + sleepTime = millis() - sleepStart; #ifdef ARCH_ESP32 + uint32_t sleepLeft; + + sleepLeft = config.power.ls_secs * 1000LL - sleepTime; + if (sleepLeft > SLEEP_TIME * 1000LL) { + sleepLeft = SLEEP_TIME * 1000LL; + } + + doLightSleep(sleepLeft); + esp_sleep_source_t cause = esp_sleep_get_wakeup_cause(); switch (cause) { @@ -128,11 +132,6 @@ static void lsIdle() powerFSM.trigger(EVENT_INPUT); return; - case ESP_SLEEP_WAKEUP_EXT0: - LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_EXT0"); - powerFSM.trigger(EVENT_RADIO_INTERRUPT); - return; - case ESP_SLEEP_WAKEUP_EXT1: LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_EXT1"); powerFSM.trigger(EVENT_PRESS); @@ -143,11 +142,6 @@ static void lsIdle() powerFSM.trigger(EVENT_WAKE_TIMER); return; - case ESP_SLEEP_WAKEUP_UNDEFINED: - LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_UNDEFINED"); - powerFSM.trigger(EVENT_WAKE_TIMER); - return; - default: if (sleepTime > config.power.ls_secs * 1000LL) { powerFSM.trigger(EVENT_WAKE_TIMER); @@ -155,15 +149,6 @@ static void lsIdle() } break; } - - uint32_t sleepLeft; - - sleepLeft = config.power.ls_secs * 1000LL - sleepTime; - if (sleepLeft > SLEEP_TIME * 1000LL) { - sleepLeft = SLEEP_TIME * 1000LL; - } - - doLightSleep(sleepLeft); #endif } @@ -183,21 +168,9 @@ static void lsExit() } } -static void nbEnter() -{ - LOG_DEBUG("State: NB"); - if (screen) - screen->setOn(false); -#ifdef ARCH_ESP32 - // Only ESP32 should turn off bluetooth - setBluetoothEnable(false); -#endif -} - static void darkEnter() { // LOG_DEBUG("State: DARK"); i - setBluetoothEnable(true); if (screen) screen->setOn(false); } @@ -205,18 +178,11 @@ static void darkEnter() static void serialEnter() { LOG_DEBUG("State: SERIAL"); - setBluetoothEnable(false); if (screen) { screen->setOn(true); } } -static void serialExit() -{ - // Turn bluetooth back on when we leave serial stream API - setBluetoothEnable(true); -} - static void powerEnter() { LOG_DEBUG("State: POWER"); @@ -228,7 +194,6 @@ static void powerEnter() } else { if (screen) screen->setOn(true); - setBluetoothEnable(true); // within enter() the function getState() returns the state we came from } } @@ -246,7 +211,6 @@ static void powerExit() { if (screen) screen->setOn(true); - setBluetoothEnable(true); } static void onEnter() @@ -254,7 +218,6 @@ static void onEnter() LOG_DEBUG("State: ON"); if (screen) screen->setOn(true); - setBluetoothEnable(true); } static void onIdle() @@ -274,9 +237,8 @@ State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN"); State stateSDS(sdsEnter, NULL, NULL, "SDS"); State stateLowBattSDS(lowBattSDSEnter, NULL, NULL, "SDS"); State stateLS(lsEnter, lsIdle, lsExit, "LS"); -State stateNB(nbEnter, NULL, NULL, "NB"); State stateDARK(darkEnter, NULL, NULL, "DARK"); -State stateSERIAL(serialEnter, NULL, serialExit, "SERIAL"); +State stateSERIAL(serialEnter, NULL, NULL, "SERIAL"); State stateBOOT(bootEnter, NULL, NULL, "BOOT"); State stateON(onEnter, onIdle, NULL, "ON"); State statePOWER(powerEnter, powerIdle, powerExit, "POWER"); @@ -286,20 +248,14 @@ void PowerFSM_setup() { bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); bool hasPower = isPowered(); - State *stateIDLE; -#ifdef ARCH_ESP32 - stateIDLE = isRouter ? &stateNB : &stateDARK; -#else - stateIDLE = &stateDARK; -#endif - LOG_INFO("PowerFSM init, USB power=%d, is_power_saving=%d", hasPower ? 1 : 0, config.power.is_power_saving ? 1 : 0); + LOG_INFO("PowerFSM init, USB power=%d, is_power_saving=%d, wake_time_ms=%d", hasPower ? 1 : 0, + config.power.is_power_saving ? 1 : 0, WAKE_TIME_MS); powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout"); // Handle press events - note: we ignore button presses when in API mode powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press"); - powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press"); powerFSM.add_transition(&stateDARK, isPowered() ? &statePOWER : &stateON, EVENT_PRESS, NULL, "Press"); powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_PRESS, NULL, "Press"); powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, NULL, "Press"); // reenter On to restart our timers @@ -309,7 +265,6 @@ void PowerFSM_setup() // Handle critically low power battery by forcing deep sleep powerFSM.add_transition(&stateBOOT, &stateLowBattSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); powerFSM.add_transition(&stateLS, &stateLowBattSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); - powerFSM.add_transition(&stateNB, &stateLowBattSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); powerFSM.add_transition(&stateDARK, &stateLowBattSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); powerFSM.add_transition(&stateON, &stateLowBattSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); powerFSM.add_transition(&stateSERIAL, &stateLowBattSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); @@ -317,14 +272,12 @@ void PowerFSM_setup() // Handle being told to power off powerFSM.add_transition(&stateBOOT, &stateSHUTDOWN, EVENT_SHUTDOWN, NULL, "Shutdown"); powerFSM.add_transition(&stateLS, &stateSHUTDOWN, EVENT_SHUTDOWN, NULL, "Shutdown"); - powerFSM.add_transition(&stateNB, &stateSHUTDOWN, EVENT_SHUTDOWN, NULL, "Shutdown"); powerFSM.add_transition(&stateDARK, &stateSHUTDOWN, EVENT_SHUTDOWN, NULL, "Shutdown"); powerFSM.add_transition(&stateON, &stateSHUTDOWN, EVENT_SHUTDOWN, NULL, "Shutdown"); powerFSM.add_transition(&stateSERIAL, &stateSHUTDOWN, EVENT_SHUTDOWN, NULL, "Shutdown"); // Inputbroker powerFSM.add_transition(&stateLS, &stateON, EVENT_INPUT, NULL, "Input Device"); - powerFSM.add_transition(&stateNB, &stateON, EVENT_INPUT, NULL, "Input Device"); powerFSM.add_transition(&stateDARK, &stateON, EVENT_INPUT, NULL, "Input Device"); powerFSM.add_transition(&stateON, &stateON, EVENT_INPUT, NULL, "Input Device"); // restarts the sleep timer powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_INPUT, NULL, "Input Device"); // restarts the sleep timer @@ -339,56 +292,48 @@ void PowerFSM_setup() if (!isRouter) { powerFSM.add_transition(&stateLS, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone"); powerFSM.add_transition(&stateLS, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); - powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); // Removed 2.7: we don't show the nodes individually for every node on the screen anymore // powerFSM.add_transition(&stateLS, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - // powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); // powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); // powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); // Show the received text message powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); - powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); powerFSM.add_transition(&stateDARK, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); // restarts the sleep timer - - } else { - // if we are a router we don't turn the screen on for these things - powerFSM.add_timed_transition( - &stateDARK, &stateNB, - Default::getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL, - "Bluetooth timeout"); } // If we are not in statePOWER but get a serial connection, suppress sleep (and keep the screen on) while connected powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); - powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); powerFSM.add_transition(&statePOWER, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API"); // If we get power connected, go to the power connect state powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect"); - powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect"); powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect"); powerFSM.add_transition(&stateON, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect"); powerFSM.add_transition(&statePOWER, &stateON, EVENT_POWER_DISCONNECTED, NULL, "Power disconnected"); powerFSM.add_transition(&stateLS, &stateON, EVENT_POWER_DISCONNECTED, NULL, "Power disconnected"); - powerFSM.add_transition(&stateNB, &stateON, EVENT_POWER_DISCONNECTED, NULL, "Power disconnected"); powerFSM.add_transition(&stateDARK, &stateON, EVENT_POWER_DISCONNECTED, NULL, "Power disconnected"); // the only way to leave state serial is for the client to disconnect (or we timeout and force disconnect them) // when we leave, go to ON (which might not be the correct state if we have power connected, we will fix that in onEnter) powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_SERIAL_DISCONNECTED, NULL, "Serial disconnect"); - powerFSM.add_transition(&stateLS, stateIDLE, EVENT_WAKE_TIMER, NULL, "Wake timer"); - powerFSM.add_transition(&stateLS, stateIDLE, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); + powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer"); + powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer"); - powerFSM.add_transition(stateIDLE, stateIDLE, EVENT_WAKE_TIMER, NULL, "Wake timer"); - powerFSM.add_transition(stateIDLE, stateIDLE, EVENT_WEB_REQUEST, NULL, "Web request"); - powerFSM.add_transition(stateIDLE, stateIDLE, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); + powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); + powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); + +#ifdef DYNAMIC_LIGHT_SLEEP + // it's better to exit dynamic light sleep when packet is received to ensure routing is properly handled + powerFSM.add_transition(&stateLS, &stateDARK, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); + powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); +#endif #ifdef USE_EINK // Allow E-Ink devices to suppress the screensaver, if screen timeout set to 0 @@ -403,10 +348,10 @@ void PowerFSM_setup() NULL, "Screen-on timeout"); } -// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally) +// We never enter light-sleep on NRF52 (because the CPU uses so little power normally) #ifdef ARCH_ESP32 if (config.power.is_power_saving) { - powerFSM.add_timed_transition(stateIDLE, &stateLS, WAKE_TIME_MS, NULL, "Min wake timeout"); + powerFSM.add_timed_transition(&stateDARK, &stateLS, WAKE_TIME_MS, NULL, "Min wake timeout"); } #endif diff --git a/src/PowerFSM.h b/src/PowerFSM.h index 887c4dccb..964f53a3a 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -24,11 +24,12 @@ #define EVENT_RADIO_INTERRUPT 18 #define EVENT_WEB_REQUEST 19 -#if defined(ARCH_ESP32) && !defined(WAKE_TIME_MS) +#ifdef ARCH_ESP32 #ifdef CONFIG_FREERTOS_USE_TICKLESS_IDLE +#define DYNAMIC_LIGHT_SLEEP #define WAKE_TIME_MS 500 #else -#define WAKE_TIME_MS Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs) +#define WAKE_TIME_MS (Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs)) #endif #endif @@ -55,7 +56,7 @@ void PowerFSM_setup(); #else #include extern Fsm powerFSM; -extern State stateON, statePOWER, stateSERIAL, stateDARK, stateNB, stateLS; +extern State stateON, statePOWER, stateSERIAL, stateDARK, stateLS; void PowerFSM_setup(); #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 578e518b9..c22b2ae18 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1437,12 +1437,16 @@ void setup() #endif #ifndef ARCH_PORTDUINO - - // Initialize Wifi #if HAS_WIFI + // Initialize Wifi initWifi(); #endif +#if HAS_BLUETOOTH + // Enable Bluetooth + setBluetoothEnable(true); +#endif + #if HAS_ETHERNET // Initialize Ethernet initEthernet(); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 44cd03c25..f4626f56c 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -159,7 +159,6 @@ void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket void Router::setReceivedMessage() { // LOG_DEBUG("set interval to ASAP"); - powerFSM.trigger(EVENT_WAKE_TIMER); setInterval(0); // Run ASAP, so we can figure out our correct sleep time runASAP = true; } From 7d695c4aaecfb03374f92879cfbc00f47d6d22e2 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 09:23:37 +0200 Subject: [PATCH 13/24] move feature flags --- src/PowerFSM.cpp | 4 ++-- src/PowerFSM.h | 5 +---- src/platform/esp32/architecture.h | 2 +- src/sleep.cpp | 20 ++++++++++---------- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index bf250507a..85fddbda9 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -102,7 +102,7 @@ static void lsEnter() static void lsIdle() { if (!doPreflightSleep()) { -#ifdef DYNAMIC_LIGHT_SLEEP +#ifdef HAS_DYNAMIC_LIGHT_SLEEP powerFSM.trigger(EVENT_WAKE_TIMER); #endif return; @@ -329,7 +329,7 @@ void PowerFSM_setup() powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); -#ifdef DYNAMIC_LIGHT_SLEEP +#ifdef HAS_DYNAMIC_LIGHT_SLEEP // it's better to exit dynamic light sleep when packet is received to ensure routing is properly handled powerFSM.add_transition(&stateLS, &stateDARK, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); diff --git a/src/PowerFSM.h b/src/PowerFSM.h index 964f53a3a..907632259 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -24,14 +24,11 @@ #define EVENT_RADIO_INTERRUPT 18 #define EVENT_WEB_REQUEST 19 -#ifdef ARCH_ESP32 -#ifdef CONFIG_FREERTOS_USE_TICKLESS_IDLE -#define DYNAMIC_LIGHT_SLEEP +#ifdef HAS_DYNAMIC_LIGHT_SLEEP #define WAKE_TIME_MS 500 #else #define WAKE_TIME_MS (Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs)) #endif -#endif #if MESHTASTIC_EXCLUDE_POWER_FSM class FakeFsm diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index f3954840d..be6113632 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -229,4 +229,4 @@ // Setup flag, which indicates if our device supports dynamic light sleep #if defined(HAS_ESP32_PM_SUPPORT) && defined(CONFIG_FREERTOS_USE_TICKLESS_IDLE) #define HAS_ESP32_DYNAMIC_LIGHT_SLEEP 1 -#endif \ No newline at end of file +#endif diff --git a/src/sleep.cpp b/src/sleep.cpp index ac338be0b..7c1fab5c8 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -18,7 +18,7 @@ #include "target_specific.h" #ifdef ARCH_ESP32 -#ifdef CONFIG_PM_ENABLE +#ifdef HAS_ESP32_PM_SUPPORT #include "esp32/pm.h" #include "esp_pm.h" #endif @@ -54,7 +54,7 @@ Observable notifyLightSleep; /// Called to tell observers that light sleep has just ended, and why it ended Observable notifyLightSleepEnd; -#ifdef CONFIG_PM_ENABLE +#ifdef HAS_ESP32_PM_SUPPORT esp_pm_lock_handle_t pmHandle; #endif @@ -84,7 +84,7 @@ void setCPUFast(bool on) #if defined(ARCH_ESP32) && !HAS_TFT #ifdef HAS_WIFI if (isWifiAvailable()) { -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && defined(WIFI_MAX_PERFORMANCE) +#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(WIFI_MAX_PERFORMANCE) LOG_DEBUG("Set CPU to 240MHz because WiFi is in use"); setCpuFrequencyMhz(240); return; @@ -342,7 +342,7 @@ void doLightSleep(uint32_t sleepMsec) return; // nothing to do } -#ifdef CONFIG_PM_ENABLE +#ifdef HAS_ESP32_PM_SUPPORT res = esp_pm_lock_acquire(pmHandle); assert(res == ESP_OK); #endif @@ -360,7 +360,7 @@ void doLightSleep(uint32_t sleepMsec) if (!pmLockAcquired) { console->flush(); -#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE +#ifndef HAS_DYNAMIC_LIGHT_SLEEP esp_light_sleep_start(); #endif @@ -371,7 +371,7 @@ void doLightSleep(uint32_t sleepMsec) enableLoraInterrupt(); enableButtonInterrupt(); -#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE +#ifndef HAS_DYNAMIC_LIGHT_SLEEP res = esp_sleep_enable_timer_wakeup(sleepMsec * 1000LL); assert(res == ESP_OK); #endif @@ -414,13 +414,13 @@ void doLightSleep(uint32_t sleepMsec) console->flush(); -#ifdef CONFIG_PM_ENABLE +#ifdef HAS_ESP32_PM_SUPPORT res = esp_pm_lock_release(pmHandle); assert(res == ESP_OK); #endif pmLockAcquired = false; -#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE +#ifndef HAS_DYNAMIC_LIGHT_SLEEP esp_light_sleep_start(); #endif @@ -432,7 +432,7 @@ void initLightSleep() { esp_err_t res; -#ifdef CONFIG_PM_ENABLE +#ifdef HAS_ESP32_PM_SUPPORT res = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "meshtastic", &pmHandle); assert(res == ESP_OK); @@ -442,7 +442,7 @@ void initLightSleep() esp_pm_config_esp32_t pm_config; pm_config.max_freq_mhz = 80; pm_config.min_freq_mhz = 20; -#ifdef CONFIG_FREERTOS_USE_TICKLESS_IDLE +#ifdef HAS_DYNAMIC_LIGHT_SLEEP pm_config.light_sleep_enable = true; #else pm_config.light_sleep_enable = false; From 969654645855c6df0daf4b244ab071030491d568 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 09:56:37 +0200 Subject: [PATCH 14/24] implement lower cap on power thread interval --- src/Power.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Power.cpp b/src/Power.cpp index c97c28028..e8da49005 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -963,12 +963,20 @@ int32_t Power::runOnce() #ifdef ARCH_ESP32 int Power::beforeLightSleep(void *unused) { - setInterval(config.power.ls_secs * 1000UL); + // ensure we won't run the thread when light sleeping + unsigned long interval = config.power.ls_secs; + + if (interval < 20) { + interval = 20; + } + + setInterval(interval * 1000UL); return 0; } int Power::afterLightSleep(esp_sleep_wakeup_cause_t cause) { + // restore default thread interval after light sleep setInterval(20 * 1000UL); return 0; } From affc8fbeb2ea427dde7b85d15197de55ad921bc9 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 11:20:13 +0200 Subject: [PATCH 15/24] make some variables static, refactor GPIO reset logic --- src/PowerFSM.cpp | 22 ++----- src/sleep.cpp | 165 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 137 insertions(+), 50 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 85fddbda9..1adef51e6 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -16,6 +16,7 @@ #include "configuration.h" #include "graphics/Screen.h" #include "main.h" +#include "power.h" #include "sleep.h" #include "target_specific.h" @@ -36,6 +37,10 @@ FakeFsm powerFSM; void PowerFSM_setup(){}; #else + +static uint32_t sleepStart; +static uint32_t sleepTime; + /// Should we behave as if we have AC power now? static bool isPowered() { @@ -61,19 +66,12 @@ static bool isPowered() return !isPowerSavingMode && powerStatus && (!powerStatus->getHasBattery() || powerStatus->getHasUSB()); } -static void sdsEnter() -{ - LOG_DEBUG("State: SDS"); - // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw - doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, false); -} - static void lowBattSDSEnter() { LOG_DEBUG("State: Lower batt SDS"); + // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, true); } -extern Power *power; static void shutdownEnter() { @@ -81,11 +79,6 @@ static void shutdownEnter() shutdownAtMsec = millis(); } -#include "error.h" - -uint32_t sleepStart; -uint32_t sleepTime; - static void lsEnter() { LOG_DEBUG("State: LS"); @@ -234,7 +227,6 @@ static void bootEnter() } State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN"); -State stateSDS(sdsEnter, NULL, NULL, "SDS"); State stateLowBattSDS(lowBattSDSEnter, NULL, NULL, "SDS"); State stateLS(lsEnter, lsIdle, lsExit, "LS"); State stateDARK(darkEnter, NULL, NULL, "DARK"); @@ -249,7 +241,7 @@ void PowerFSM_setup() bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); bool hasPower = isPowered(); - LOG_INFO("PowerFSM init, USB power=%d, is_power_saving=%d, wake_time_ms=%d", hasPower ? 1 : 0, + LOG_INFO("PowerFSM init, has_power=%d, is_power_saving=%d, wake_time_ms=%d", hasPower ? 1 : 0, config.power.is_power_saving ? 1 : 0, WAKE_TIME_MS); powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout"); diff --git a/src/sleep.cpp b/src/sleep.cpp index 7c1fab5c8..9f572b9ea 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -58,11 +58,13 @@ Observable notifyLightSleepEnd; esp_pm_lock_handle_t pmHandle; #endif -// internal helper functions -void gpioResetHold(void); +// restores GPIO function after sleep +void gpioReset(void); +// enables button wake-up interrupt void enableButtonInterrupt(void); - +// enables LoRa wake-up-interrupt void enableLoraInterrupt(void); + bool shouldLoraWake(uint32_t msecToWake); #endif @@ -154,7 +156,7 @@ void initDeepSleep() #ifdef ARCH_ESP32 if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) { - gpioResetHold(); + gpioReset(); } #endif #endif @@ -321,8 +323,8 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN } #ifdef ARCH_ESP32 -bool pmLockAcquired; -concurrency::Lock *pmLightSleepLock; +static bool pmLockAcquired; +static concurrency::Lock *pmLightSleepLock; /** * enter light sleep (preserves ram but stops everything about CPU). @@ -349,7 +351,7 @@ void doLightSleep(uint32_t sleepMsec) pmLockAcquired = true; esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); - gpioResetHold(); + gpioReset(); notifyLightSleepEnd.notifyObservers(esp_sleep_get_wakeup_cause()); @@ -382,14 +384,6 @@ void doLightSleep(uint32_t sleepMsec) res = esp_sleep_enable_uart_wakeup(UART_NUM_0); assert(res == ESP_OK); -#ifdef PMU_IRQ - // wake due to PMU can happen repeatedly if there is no battery installed or the battery fills - if (pmu_found) { - res = gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq - assert(res == ESP_OK); - } -#endif - #if defined(VEXT_ENABLE) gpio_hold_en((gpio_num_t)VEXT_ENABLE); #endif @@ -402,10 +396,17 @@ void doLightSleep(uint32_t sleepMsec) res = gpio_wakeup_enable((gpio_num_t)INPUTDRIVER_ENCODER_BTN, GPIO_INTR_LOW_LEVEL); assert(res == ESP_OK); #endif -#if defined(T_WATCH_S3) || defined(ELECROW) +#ifdef WAKE_ON_TOUCH res = gpio_wakeup_enable((gpio_num_t)SCREEN_TOUCH_INT, GPIO_INTR_LOW_LEVEL); assert(res == ESP_OK); #endif +#ifdef PMU_IRQ + // wake due to PMU can happen repeatedly if there is no battery installed or the battery fills + if (pmu_found) { + res = gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq + assert(res == ESP_OK); + } +#endif res = esp_sleep_enable_gpio_wakeup(); assert(res == ESP_OK); @@ -459,15 +460,79 @@ void initLightSleep() pmLockAcquired = true; } -void gpioResetHold() +void gpioReset() { + esp_err_t res; + + // deinitialize RTC GPIOs and holds for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) { if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) { rtc_gpio_hold_dis((gpio_num_t)i); rtc_gpio_deinit((gpio_num_t)i); - } else if (GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)i)) + } else if (GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)i)) { gpio_hold_dis((gpio_num_t)i); + } + } + + // restore negative-edge interrupt triggers for input pins +#ifdef INPUTDRIVER_ENCODER_BTN + res = gpio_set_intr_type((gpio_num_t)INPUTDRIVER_ENCODER_BTN, GPIO_INTR_NEGEDGE); + assert(res == ESP_OK); +#endif +#ifdef WAKE_ON_TOUCH + res = gpio_set_intr_type((gpio_num_t)SCREEN_TOUCH_INT, GPIO_INTR_NEGEDGE); + assert(res == ESP_OK); +#endif +#ifdef PMU_IRQ + if (pmu_found) { + res = gpio_set_intr_type((gpio_num_t)PMU_IRQ, GPIO_INTR_NEGEDGE); + assert(res == ESP_OK); + } +#endif + + gpio_num_t pin = GPIO_NUM_NC; + +#if defined(LORA_DIO1) && (LORA_DIO1 != GPIO_NUM_NC) + pin = (gpio_num_t)LORA_DIO1; +#elif defined(RF95_IRQ) && (RF95_IRQ != GPIO_NUM_NC) + pin = (gpio_num_t)RF95_IRQ; +#endif + + // need to restore original GPIO interrupt trigger when it's not RTC GPIO or we don't support EXT wakeup + if (pin != GPIO_NUM_NC) { +#ifdef SOC_PM_SUPPORT_EXT_WAKEUP + if (!rtc_gpio_is_valid_gpio(pin)) { +#endif + res = gpio_set_intr_type(pin, GPIO_INTR_POSEDGE); + assert(res == ESP_OK); +#ifdef SOC_PM_SUPPORT_EXT_WAKEUP + } +#endif + } + + pin = GPIO_NUM_NC; + + if (config.device.button_gpio) { + pin = (gpio_num_t)config.device.button_gpio; + } + +#ifdef BUTTON_PIN + if (pin == GPIO_NUM_NC) { + pin = (gpio_num_t)BUTTON_PIN; + } +#endif + + // need to restore original GPIO interrupt trigger when it's not RTC GPIO or we don't support EXT wakeup + if (pin != GPIO_NUM_NC) { +#ifdef SOC_PM_SUPPORT_EXT_WAKEUP + if (!rtc_gpio_is_valid_gpio(pin)) { +#endif + res = gpio_set_intr_type(pin, GPIO_INTR_ANYEDGE); + assert(res == ESP_OK); +#ifdef SOC_PM_SUPPORT_EXT_WAKEUP + } +#endif } } @@ -476,8 +541,22 @@ void enableButtonInterrupt() esp_err_t res; gpio_num_t pin; + pin = GPIO_NUM_NC; + + if (config.device.button_gpio) { + pin = (gpio_num_t)config.device.button_gpio; + } + #ifdef BUTTON_PIN - pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN); + if (pin == GPIO_NUM_NC) { + pin = (gpio_num_t)BUTTON_PIN; + } +#endif + + if (pin == GPIO_NUM_NC) { + return; + } + #ifdef SOC_PM_SUPPORT_EXT_WAKEUP if (rtc_gpio_is_valid_gpio(pin)) { LOG_DEBUG("Setup button pin (GPIO%02d) with wakeup by ext1 source", pin); @@ -487,29 +566,38 @@ void enableButtonInterrupt() #endif res = rtc_gpio_hold_en((gpio_num_t)pin); assert(res == ESP_OK); +#ifdef CONFIG_IDF_TARGET_ESP32 + res = esp_sleep_enable_ext1_wakeup(1ULL << pin, ESP_EXT1_WAKEUP_ALL_LOW); +#else res = esp_sleep_enable_ext1_wakeup(1ULL << pin, ESP_EXT1_WAKEUP_ANY_LOW); +#endif + assert(res == ESP_OK); } else { LOG_DEBUG("Setup button pin (GPIO%02d) with wakeup by GPIO interrupt", pin); #ifdef BUTTON_NEED_PULLUP - gpio_pullup_en(pin); + res = gpio_pullup_en(pin); assert(res == ESP_OK); #endif - res = gpio_hold_en((gpio_num_t)pin); - assert(res == ESP_OK); res = gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); + assert(res == ESP_OK); + if (GPIO_IS_VALID_OUTPUT_GPIO(pin)) { + res = gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + } } #else #ifdef BUTTON_NEED_PULLUP - gpio_pullup_en(pin); + res = gpio_pullup_en(pin); assert(res == ESP_OK); #endif - res = gpio_hold_en((gpio_num_t)pin); - assert(res == ESP_OK); res = gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); - LOG_DEBUG("Setup button pin (GPIO%02d) with wakeup by GPIO interrupt", pin); -#endif assert(res == ESP_OK); + LOG_DEBUG("Setup button pin (GPIO%02d) with wakeup by GPIO interrupt", pin); + if (GPIO_IS_VALID_OUTPUT_GPIO(pin)) { + res = gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + } #endif } @@ -526,9 +614,11 @@ void enableLoraInterrupt() pin = (gpio_num_t)RF95_IRQ; #endif - assert(pin != GPIO_NUM_NC); + if (pin == GPIO_NUM_NC) { + return; + } -#if defined(LORA_RESET) && (LORA_RESET != RADIOLIB_NC) +#if defined(LORA_RESET) && (LORA_RESET != GPIO_NUM_NC) gpio_hold_en((gpio_num_t)LORA_RESET); #endif @@ -540,24 +630,29 @@ void enableLoraInterrupt() res = rtc_gpio_hold_en((gpio_num_t)pin); assert(res == ESP_OK); res = esp_sleep_enable_ext0_wakeup(pin, HIGH); - + assert(res == ESP_OK); } else { LOG_DEBUG("Setup radio interrupt (GPIO%02d) with wakeup by GPIO interrupt", pin); res = gpio_pulldown_en((gpio_num_t)pin); assert(res == ESP_OK); - res = gpio_hold_en((gpio_num_t)pin); - assert(res == ESP_OK); res = gpio_wakeup_enable(pin, GPIO_INTR_HIGH_LEVEL); + assert(res == ESP_OK); + if (GPIO_IS_VALID_OUTPUT_GPIO(pin)) { + res = gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + } } #else LOG_DEBUG("Setup radio interrupt (GPIO%02d) with wakeup by GPIO interrupt", pin); res = gpio_pulldown_en((gpio_num_t)pin); assert(res == ESP_OK); - res = gpio_hold_en((gpio_num_t)pin); - assert(res == ESP_OK); res = gpio_wakeup_enable(pin, GPIO_INTR_HIGH_LEVEL); -#endif assert(res == ESP_OK); + if (GPIO_IS_VALID_OUTPUT_GPIO(pin)) { + res = gpio_hold_en((gpio_num_t)pin); + assert(res == ESP_OK); + } +#endif } bool shouldLoraWake(uint32_t msecToWake) From c3a49bc97a6a18db890363139f6dbbab80f9f793 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 20:40:13 +0200 Subject: [PATCH 16/24] another refactor to better handle variants without dynamic light sleep --- src/PowerFSM.cpp | 41 ++++++++++++------- src/PowerFSM.h | 9 +++-- src/sleep.cpp | 101 ++++++++++++++++++++++++++--------------------- src/sleep.h | 1 + 4 files changed, 88 insertions(+), 64 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 1adef51e6..f165ca0e3 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -29,17 +29,13 @@ #include "mesh/wifi/WiFiAPClient.h" #endif -#ifndef SLEEP_TIME -#define SLEEP_TIME 30 -#endif - #if MESHTASTIC_EXCLUDE_POWER_FSM FakeFsm powerFSM; void PowerFSM_setup(){}; #else - static uint32_t sleepStart; static uint32_t sleepTime; +static uint32_t sleepLeft; /// Should we behave as if we have AC power now? static bool isPowered() @@ -89,13 +85,24 @@ static void lsEnter() sleepStart = -1; sleepTime = 0; +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP + if (!doPreflightSleep()) { + LOG_DEBUG("Transition to LS state aborted because of tasks pending"); + powerFSM.trigger(EVENT_WAKE_TIMER); + return; + } + + sleepStart = millis(); + doLightSleep(LIGHT_SLEEP_DYNAMIC); +#endif + powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); } static void lsIdle() { if (!doPreflightSleep()) { -#ifdef HAS_DYNAMIC_LIGHT_SLEEP +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP powerFSM.trigger(EVENT_WAKE_TIMER); #endif return; @@ -106,16 +113,15 @@ static void lsIdle() } sleepTime = millis() - sleepStart; - -#ifdef ARCH_ESP32 - uint32_t sleepLeft; - sleepLeft = config.power.ls_secs * 1000LL - sleepTime; - if (sleepLeft > SLEEP_TIME * 1000LL) { - sleepLeft = SLEEP_TIME * 1000LL; + if (sleepLeft > SLEEP_TIME_QUANTUM_S * 1000LL) { + sleepLeft = SLEEP_TIME_QUANTUM_S * 1000LL; } +#ifdef ARCH_ESP32 +#ifndef HAS_ESP32_DYNAMIC_LIGHT_SLEEP doLightSleep(sleepLeft); +#endif esp_sleep_source_t cause = esp_sleep_get_wakeup_cause(); @@ -125,6 +131,11 @@ static void lsIdle() powerFSM.trigger(EVENT_INPUT); return; + case ESP_SLEEP_WAKEUP_EXT0: + LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_EXT0"); + powerFSM.trigger(EVENT_RADIO_INTERRUPT); + return; + case ESP_SLEEP_WAKEUP_EXT1: LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_EXT1"); powerFSM.trigger(EVENT_PRESS); @@ -132,7 +143,7 @@ static void lsIdle() case ESP_SLEEP_WAKEUP_GPIO: LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_GPIO"); - powerFSM.trigger(EVENT_WAKE_TIMER); + // FSM events should be triggered by interrupt handlers return; default: @@ -147,7 +158,7 @@ static void lsIdle() static void lsExit() { -#ifdef ARCH_ESP32 +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP doLightSleep(LIGHT_SLEEP_ABORT); #endif @@ -321,7 +332,7 @@ void PowerFSM_setup() powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); -#ifdef HAS_DYNAMIC_LIGHT_SLEEP +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP // it's better to exit dynamic light sleep when packet is received to ensure routing is properly handled powerFSM.add_transition(&stateLS, &stateDARK, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); diff --git a/src/PowerFSM.h b/src/PowerFSM.h index 907632259..362c1af86 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -24,12 +24,16 @@ #define EVENT_RADIO_INTERRUPT 18 #define EVENT_WEB_REQUEST 19 -#ifdef HAS_DYNAMIC_LIGHT_SLEEP +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP #define WAKE_TIME_MS 500 #else #define WAKE_TIME_MS (Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs)) #endif +#ifndef SLEEP_TIME_QUANTUM_S +#define SLEEP_TIME_QUANTUM_S 5 +#endif + #if MESHTASTIC_EXCLUDE_POWER_FSM class FakeFsm { @@ -49,11 +53,10 @@ class FakeFsm }; extern FakeFsm powerFSM; void PowerFSM_setup(); - #else #include extern Fsm powerFSM; extern State stateON, statePOWER, stateSERIAL, stateDARK, stateLS; void PowerFSM_setup(); -#endif \ No newline at end of file +#endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 9f572b9ea..f6aa9c506 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -45,7 +45,7 @@ Observable notifyDeepSleep; Observable notifyReboot; #ifdef ARCH_ESP32 -// Wake cause when returning from a deep sleep +// Wake cause when returning from sleep esp_sleep_source_t wakeCause; /// Called to tell observers that light sleep is about to begin @@ -55,7 +55,7 @@ Observable notifyLightSleep; Observable notifyLightSleepEnd; #ifdef HAS_ESP32_PM_SUPPORT -esp_pm_lock_handle_t pmHandle; +esp_pm_lock_handle_t pmLightSleepLock; #endif // restores GPIO function after sleep @@ -323,8 +323,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN } #ifdef ARCH_ESP32 -static bool pmLockAcquired; -static concurrency::Lock *pmLightSleepLock; +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +static bool pmLightSleepLockAcquired; +#endif +static concurrency::Lock *lightSleepConcurrencyLock; /** * enter light sleep (preserves ram but stops everything about CPU). @@ -335,48 +337,45 @@ void doLightSleep(uint32_t sleepMsec) { esp_err_t res; - assert(pmLightSleepLock); - pmLightSleepLock->lock(); + assert(lightSleepConcurrencyLock); + lightSleepConcurrencyLock->lock(); - if (sleepMsec == LIGHT_SLEEP_ABORT) { - if (pmLockAcquired) { - pmLightSleepLock->unlock(); - return; // nothing to do +#ifndef HAS_ESP32_DYNAMIC_LIGHT_SLEEP + assert(sleepMsec != LIGHT_SLEEP_ABORT); + assert(sleepMsec != LIGHT_SLEEP_DYNAMIC); +#else + if (!pmLightSleepLockAcquired) { + if (sleepMsec == LIGHT_SLEEP_DYNAMIC) { + lightSleepConcurrencyLock->unlock(); + return; } -#ifdef HAS_ESP32_PM_SUPPORT - res = esp_pm_lock_acquire(pmHandle); + res = esp_pm_lock_acquire(pmLightSleepLock); assert(res == ESP_OK); -#endif - pmLockAcquired = true; + + wakeCause = esp_sleep_get_wakeup_cause(); + + pmLightSleepLockAcquired = true; esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); gpioReset(); - notifyLightSleepEnd.notifyObservers(esp_sleep_get_wakeup_cause()); - - pmLightSleepLock->unlock(); - return; + notifyLightSleepEnd.notifyObservers(wakeCause); } - if (!pmLockAcquired) { - console->flush(); - -#ifndef HAS_DYNAMIC_LIGHT_SLEEP - esp_light_sleep_start(); + if (sleepMsec == LIGHT_SLEEP_ABORT) { + lightSleepConcurrencyLock->unlock(); + return; + } #endif - pmLightSleepLock->unlock(); - return; - } - enableLoraInterrupt(); enableButtonInterrupt(); -#ifndef HAS_DYNAMIC_LIGHT_SLEEP - res = esp_sleep_enable_timer_wakeup(sleepMsec * 1000LL); - assert(res == ESP_OK); -#endif + if (sleepMsec != LIGHT_SLEEP_DYNAMIC) { + res = esp_sleep_enable_timer_wakeup(sleepMsec * 1000LL); + assert(res == ESP_OK); + } res = uart_set_wakeup_threshold(UART_NUM_0, 3); assert(res == ESP_OK); @@ -387,7 +386,6 @@ void doLightSleep(uint32_t sleepMsec) #if defined(VEXT_ENABLE) gpio_hold_en((gpio_num_t)VEXT_ENABLE); #endif - #if defined(RESET_OLED) gpio_hold_en((gpio_num_t)RESET_OLED); #endif @@ -415,17 +413,25 @@ void doLightSleep(uint32_t sleepMsec) console->flush(); -#ifdef HAS_ESP32_PM_SUPPORT - res = esp_pm_lock_release(pmHandle); - assert(res == ESP_OK); -#endif - pmLockAcquired = false; + if (sleepMsec != LIGHT_SLEEP_DYNAMIC) { + esp_light_sleep_start(); -#ifndef HAS_DYNAMIC_LIGHT_SLEEP - esp_light_sleep_start(); -#endif + wakeCause = esp_sleep_get_wakeup_cause(); - pmLightSleepLock->unlock(); + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); + gpioReset(); + + notifyLightSleepEnd.notifyObservers(wakeCause); + + } else { +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP + res = esp_pm_lock_release(pmLightSleepLock); + assert(res == ESP_OK); + pmLightSleepLockAcquired = false; +#endif + } + + lightSleepConcurrencyLock->unlock(); } // Initialize power management settings to allow light sleep @@ -434,16 +440,16 @@ void initLightSleep() esp_err_t res; #ifdef HAS_ESP32_PM_SUPPORT - res = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "meshtastic", &pmHandle); + res = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "meshtastic", &pmLightSleepLock); assert(res == ESP_OK); - res = esp_pm_lock_acquire(pmHandle); + res = esp_pm_lock_acquire(pmLightSleepLock); assert(res == ESP_OK); esp_pm_config_esp32_t pm_config; pm_config.max_freq_mhz = 80; pm_config.min_freq_mhz = 20; -#ifdef HAS_DYNAMIC_LIGHT_SLEEP +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP pm_config.light_sleep_enable = true; #else pm_config.light_sleep_enable = false; @@ -456,8 +462,11 @@ void initLightSleep() pm_config.max_freq_mhz, pm_config.light_sleep_enable); #endif - pmLightSleepLock = new concurrency::Lock(); - pmLockAcquired = true; + lightSleepConcurrencyLock = new concurrency::Lock(); + +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP + pmLightSleepLockAcquired = true; +#endif } void gpioReset() diff --git a/src/sleep.h b/src/sleep.h index 9ce97258e..14ac890b2 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -13,6 +13,7 @@ extern XPowersLibInterface *PMU; #include "esp_sleep.h" #define LIGHT_SLEEP_ABORT 0 +#define LIGHT_SLEEP_DYNAMIC UINT32_MAX void initLightSleep(); void doLightSleep(uint32_t msecToWake); From ed0eff0a310fd705755750886d02642e651bd853 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 22:11:32 +0200 Subject: [PATCH 17/24] fix gpio reset (rtc gpios not supported with esp32c6) --- src/sleep.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index f6aa9c506..3c230cae8 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -475,11 +475,13 @@ void gpioReset() // deinitialize RTC GPIOs and holds for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) { +#ifdef SOC_PM_SUPPORT_EXT_WAKEUP if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) { rtc_gpio_hold_dis((gpio_num_t)i); rtc_gpio_deinit((gpio_num_t)i); - - } else if (GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)i)) { + } +#endif + if (GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)i)) { gpio_hold_dis((gpio_num_t)i); } } From 4f8fc227b8cdc8c41b176e0fb0f0a42f0c6dbf42 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 27 Jun 2025 18:26:52 +0200 Subject: [PATCH 18/24] exit light-sleep state when bluetooth pairing is received --- src/PowerFSM.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index f165ca0e3..5d9add8af 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -285,6 +285,7 @@ void PowerFSM_setup() powerFSM.add_transition(&stateON, &stateON, EVENT_INPUT, NULL, "Input Device"); // restarts the sleep timer powerFSM.add_transition(&statePOWER, &statePOWER, EVENT_INPUT, NULL, "Input Device"); // restarts the sleep timer + powerFSM.add_transition(&stateLS, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); From 772f4eeaf293ab94999b1bb71600e4593f07dc9c Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 27 Jun 2025 21:41:23 +0200 Subject: [PATCH 19/24] fix failing build for rak4631_eth_gw --- src/main.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index c22b2ae18..b3a9e98e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -259,8 +259,15 @@ static int32_t ledBlinker() ledBlink.set(ledOn); + // we don't have much control over timing when light-sleeping, so let's ensure we don't keep LED on for too long +#if MESHTASTIC_EXCLUDE_POWER_FSM + bool isSleeping = false; +#else + bool isSleeping = powerFSM.getState() == &stateLS; +#endif + // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that - return (powerStatus->getIsCharging() && powerFSM.getState() != &stateLS) ? 1000 : (ledOn ? 1 : 1000); + return (powerStatus->getIsCharging() && !isSleeping) ? 1000 : (ledOn ? 1 : 1000); } uint32_t timeLastPowered = 0; From 32cd4c0868a6b6d3a44376ed673d0401841f0297 Mon Sep 17 00:00:00 2001 From: m1nl Date: Wed, 2 Jul 2025 22:26:19 +0200 Subject: [PATCH 20/24] do not enter light-sleep when GPS is in GPS_ACTIVE state --- src/gps/GPS.cpp | 10 +++++++++- src/gps/GPS.h | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 881021975..542075692 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -813,13 +813,15 @@ bool GPS::setup() } notifyDeepSleepObserver.observe(¬ifyDeepSleep); + preflightSleepObserver.observe(&preflightSleep); return true; } GPS::~GPS() { - // we really should unregister our sleep observer + // we really should unregister our sleep observers + preflightSleepObserver.unobserve(&preflightSleep); notifyDeepSleepObserver.unobserve(¬ifyDeepSleep); } @@ -1179,6 +1181,12 @@ int GPS::prepareDeepSleep(void *unused) return 0; } +// Prevents entering light-sleep when GPS is in active state +int GPS::preflightSleepCb(void *unused) +{ + return (powerState == GPS_ACTIVE); +} + static const char *PROBE_MESSAGE = "Trying %s (%s)..."; static const char *DETECTED_MESSAGE = "%s detected"; diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 9be57017f..f5cd38b5c 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -186,6 +186,7 @@ class GPS : private concurrency::OSThread uint8_t numSatellites = 0; CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); + CallbackObserver preflightSleepObserver = CallbackObserver(this, &GPS::preflightSleepCb); /** If !NULL we will use this serial port to construct our GPS */ #if defined(ARCH_RP2040) @@ -213,6 +214,9 @@ class GPS : private concurrency::OSThread /// always returns 0 to indicate okay to sleep int prepareDeepSleep(void *unused); + // Prevents entering light-sleep when GPS is in active state + int preflightSleepCb(void *unused); + /** Set power with EN pin, if relevant */ void writePinEN(bool on); From 050b967d5f6f296e65620f784272fea21463d1c8 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 22 Aug 2025 13:59:51 +0200 Subject: [PATCH 21/24] keep CPU at full speed when not power saving and WiFi is enabled --- src/main.cpp | 11 +++++++++-- src/mesh/wifi/WiFiAPClient.cpp | 18 +++++++++++++++--- src/sleep.cpp | 19 ++++--------------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b3a9e98e7..91ad24d92 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1503,8 +1503,15 @@ void setup() PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS powerFSMthread = new PowerFSMThread(); -#if !HAS_TFT - setCPUFast(false); // 80MHz is fine for our slow peripherals +#ifndef ARCH_PORTDUINO + auto cpuFast = false; +#if HAS_TFT + cpuFast |= true; +#endif +#if HAS_WIFI + cpuFast |= !config.power.is_power_saving && isWifiAvailable(); +#endif + setCPUFast(cpuFast); #endif #ifdef ARDUINO_ARCH_ESP32 diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 1133ad424..2ed47c9df 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -303,8 +303,20 @@ bool initWifi() WiFi.setAutoReconnect(true); WiFi.setSleep(false); - // This is needed to improve performance. - esp_wifi_set_ps(WIFI_PS_NONE); // Disable radio power saving + bool wifi_high_performance = false; + +#if HAS_UDP_MULTICAST + // need to increase WiFi performance (decrease DTIM interval) to avoid missing UDP packet broadcasts + wifi_high_performance |= + udpHandler && (config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST); +#endif + + if (wifi_high_performance) { + esp_wifi_set_ps(config.power.is_power_saving ? WIFI_PS_MIN_MODEM : WIFI_PS_NONE); + + } else { + esp_wifi_set_ps(config.power.is_power_saving ? WIFI_PS_MAX_MODEM : WIFI_PS_MIN_MODEM); + } WiFi.onEvent( [](WiFiEvent_t event, WiFiEventInfo_t info) { @@ -512,4 +524,4 @@ uint8_t getWifiDisconnectReason() { return wifiDisconnectReason; } -#endif // HAS_WIFI \ No newline at end of file +#endif // HAS_WIFI diff --git a/src/sleep.cpp b/src/sleep.cpp index 3c230cae8..7cfcece8a 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -83,21 +83,10 @@ RTC_DATA_ATTR int bootCount = 0; */ void setCPUFast(bool on) { -#if defined(ARCH_ESP32) && !HAS_TFT -#ifdef HAS_WIFI - if (isWifiAvailable()) { -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(WIFI_MAX_PERFORMANCE) - LOG_DEBUG("Set CPU to 240MHz because WiFi is in use"); - setCpuFrequencyMhz(240); - return; -#endif - } -#endif - -// The Heltec LORA32 V1 runs at 26 MHz base frequency and doesn't react well to switching to 80 MHz... -#if !defined(ARDUINO_HELTEC_WIFI_LORA_32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - setCpuFrequencyMhz(on ? 240 : 80); -#endif +#ifdef ARCH_ESP32 + uint8_t targetFrequency = on ? 240 : 80; + setCpuFrequencyMhz(targetFrequency); + LOG_INFO("CPU frequency set to %u MHz", targetFrequency); #endif } From ed27f91e94c933df68b5a8eae102729bf75513e0 Mon Sep 17 00:00:00 2001 From: m1nl Date: Mon, 25 Aug 2025 19:34:18 +0200 Subject: [PATCH 22/24] set serial clock source to ref_tick for esp32 platform --- src/platform/esp32/main-esp32.cpp | 49 +++++++++++++++++++++++ src/sleep.cpp | 15 ++++++- src/sleep.h | 2 +- variants/esp32/heltec_v2.1/platformio.ini | 6 ++- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 7b6b9dcff..f47f0e1bb 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -22,6 +22,7 @@ #include "target_specific.h" #include #include +#include #include #include @@ -263,3 +264,51 @@ void cpuDeepSleep(uint32_t msecToWake) esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs esp_deep_sleep_start(); // TBD mA sleep current (battery) } + +bool setSerialClockToRefTick(int uart_num) +{ +#if !SOC_UART_SUPPORT_REF_TICK + return false; +#else + uart_config_t uart_config; + uint32_t baudrate; + + if (uart_get_baudrate(uart_num, &baudrate) != ESP_OK) { + LOG_ERROR("Unable to get UART baudrate"); + return false; + } + + uart_config.baud_rate = (int)baudrate; + + if (uart_get_word_length(uart_num, &uart_config.data_bits) != ESP_OK) { + LOG_ERROR("Unable to get UART baudrate"); + return false; + } + if (uart_get_parity(uart_num, &uart_config.parity) != ESP_OK) { + LOG_ERROR("Unable to get UART baudrate"); + return false; + } + if (uart_get_stop_bits(uart_num, &uart_config.stop_bits) != ESP_OK) { + LOG_ERROR("Unable to get UART baudrate"); + return false; + } + if (uart_get_hw_flow_ctrl(uart_num, &uart_config.flow_ctrl) != ESP_OK) { + LOG_ERROR("Unable to get UART baudrate"); + return false; + } + + if (uart_config.flow_ctrl != UART_HW_FLOWCTRL_DISABLE) { + uart_config.rx_flow_ctrl_thresh = 122; + } + + uart_config.source_clk = UART_SCLK_REF_TICK; + + // Configure UART parameters + if (uart_param_config(uart_num, &uart_config) != ESP_OK) { + LOG_ERROR("Unable to get UART baudrate"); + return false; + } + + return true; +#endif +} diff --git a/src/sleep.cpp b/src/sleep.cpp index 7cfcece8a..70d431798 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -58,6 +58,9 @@ Observable notifyLightSleepEnd; esp_pm_lock_handle_t pmLightSleepLock; #endif +// this are imported symbol with target-specific implementation +bool setSerialClockToRefTick(int uart_num); + // restores GPIO function after sleep void gpioReset(void); // enables button wake-up interrupt @@ -68,6 +71,9 @@ void enableLoraInterrupt(void); bool shouldLoraWake(uint32_t msecToWake); #endif +// this are imported symbol with target-specific implementation +void cpuDeepSleep(uint32_t msecToWake); + // deep sleep support RTC_DATA_ATTR int bootCount = 0; @@ -427,6 +433,13 @@ void doLightSleep(uint32_t sleepMsec) void initLightSleep() { esp_err_t res; + bool dfsSupported = true; + +#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2) + if (dfsSupported) { + dfsSupported &= setSerialClockToRefTick(UART_NUM_0); + } +#endif #ifdef HAS_ESP32_PM_SUPPORT res = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "meshtastic", &pmLightSleepLock); @@ -437,7 +450,7 @@ void initLightSleep() esp_pm_config_esp32_t pm_config; pm_config.max_freq_mhz = 80; - pm_config.min_freq_mhz = 20; + pm_config.min_freq_mhz = dfsSupported ? 20 : pm_config.max_freq_mhz; #ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP pm_config.light_sleep_enable = true; #else diff --git a/src/sleep.h b/src/sleep.h index 14ac890b2..ea2906c44 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -21,7 +21,7 @@ void doLightSleep(uint32_t msecToWake); // perform power on init that we do on each wake from deep sleep void initDeepSleep(); -void doDeepSleep(uint32_t msecToWake, bool skipPreflight, bool skipSaveNodeDb), cpuDeepSleep(uint32_t msecToWake); +void doDeepSleep(uint32_t msecToWake, bool skipPreflight, bool skipSaveNodeDb); void setCPUFast(bool on); diff --git a/variants/esp32/heltec_v2.1/platformio.ini b/variants/esp32/heltec_v2.1/platformio.ini index 763f9764c..74d991ce7 100644 --- a/variants/esp32/heltec_v2.1/platformio.ini +++ b/variants/esp32/heltec_v2.1/platformio.ini @@ -1,9 +1,11 @@ [env:heltec-v2_1] board_level = extra -;build_type = debug ; to make it possible to step through our jtag debugger +;build_type = debug ; to make it possible to step through our jtag debugger extends = esp32_base board = heltec_wifi_lora_32_V2 -build_flags = +platform_packages = + platformio/framework-arduinoespressif32 @ https://github.com/m1nl/arduino-esp32/archive/refs/tags/2.0.17+7b9546f7.tar.gz ; optimize IRAM usage for ESP32 platform +build_flags = ${esp32_base.build_flags} -D HELTEC_V2_1 -I variants/esp32/heltec_v2.1 From b2b5bc1a6da717e1dfa376c373d12ca918281fbd Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 26 Aug 2025 23:07:59 +0200 Subject: [PATCH 23/24] switch heltec_v2.1 variant to esp-idf sdk with power management enabled --- variants/esp32/heltec_v2.1/variant.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/variants/esp32/heltec_v2.1/variant.h b/variants/esp32/heltec_v2.1/variant.h index 8ebccc54f..d0ad39579 100644 --- a/variants/esp32/heltec_v2.1/variant.h +++ b/variants/esp32/heltec_v2.1/variant.h @@ -32,6 +32,8 @@ #define ADC_MULTIPLIER 3.2 // 220k + 100k (320k/100k=3.2) // #define ADC_WIDTH ADC_WIDTH_BIT_10 +#define HAS_32768HZ 1 + #define BATTERY_PIN 37 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_CHANNEL ADC1_GPIO37_CHANNEL -#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. \ No newline at end of file +#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. From 0fbf7f7e4f0f007af7cb86895fe1da6c213d658e Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 26 Aug 2025 18:15:25 +0200 Subject: [PATCH 24/24] do not use ifdef for HAS_XYZ capability flags --- src/PowerFSM.cpp | 10 +++++----- src/PowerFSM.h | 2 +- src/platform/esp32/architecture.h | 2 +- src/sleep.cpp | 16 ++++++++-------- variants/esp32s3/heltec_v3/variant.h | 4 +--- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 5d9add8af..ef6931d5e 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -85,7 +85,7 @@ static void lsEnter() sleepStart = -1; sleepTime = 0; -#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if HAS_ESP32_DYNAMIC_LIGHT_SLEEP if (!doPreflightSleep()) { LOG_DEBUG("Transition to LS state aborted because of tasks pending"); powerFSM.trigger(EVENT_WAKE_TIMER); @@ -102,7 +102,7 @@ static void lsEnter() static void lsIdle() { if (!doPreflightSleep()) { -#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if HAS_ESP32_DYNAMIC_LIGHT_SLEEP powerFSM.trigger(EVENT_WAKE_TIMER); #endif return; @@ -119,7 +119,7 @@ static void lsIdle() } #ifdef ARCH_ESP32 -#ifndef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if !HAS_ESP32_DYNAMIC_LIGHT_SLEEP doLightSleep(sleepLeft); #endif @@ -158,7 +158,7 @@ static void lsIdle() static void lsExit() { -#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if HAS_ESP32_DYNAMIC_LIGHT_SLEEP doLightSleep(LIGHT_SLEEP_ABORT); #endif @@ -333,7 +333,7 @@ void PowerFSM_setup() powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); -#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if HAS_ESP32_DYNAMIC_LIGHT_SLEEP // it's better to exit dynamic light sleep when packet is received to ensure routing is properly handled powerFSM.add_transition(&stateLS, &stateDARK, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_RADIO_INTERRUPT, NULL, "Radio interrupt"); diff --git a/src/PowerFSM.h b/src/PowerFSM.h index 362c1af86..cb91d1ef5 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -24,7 +24,7 @@ #define EVENT_RADIO_INTERRUPT 18 #define EVENT_WEB_REQUEST 19 -#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if HAS_ESP32_DYNAMIC_LIGHT_SLEEP #define WAKE_TIME_MS 500 #else #define WAKE_TIME_MS (Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs)) diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index be6113632..ab7de0935 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -227,6 +227,6 @@ #endif // Setup flag, which indicates if our device supports dynamic light sleep -#if defined(HAS_ESP32_PM_SUPPORT) && defined(CONFIG_FREERTOS_USE_TICKLESS_IDLE) +#if HAS_ESP32_PM_SUPPORT && defined(CONFIG_FREERTOS_USE_TICKLESS_IDLE) #define HAS_ESP32_DYNAMIC_LIGHT_SLEEP 1 #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 70d431798..bf9dba99f 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -18,7 +18,7 @@ #include "target_specific.h" #ifdef ARCH_ESP32 -#ifdef HAS_ESP32_PM_SUPPORT +#if HAS_ESP32_PM_SUPPORT #include "esp32/pm.h" #include "esp_pm.h" #endif @@ -54,7 +54,7 @@ Observable notifyLightSleep; /// Called to tell observers that light sleep has just ended, and why it ended Observable notifyLightSleepEnd; -#ifdef HAS_ESP32_PM_SUPPORT +#if HAS_ESP32_PM_SUPPORT esp_pm_lock_handle_t pmLightSleepLock; #endif @@ -318,7 +318,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN } #ifdef ARCH_ESP32 -#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if HAS_ESP32_DYNAMIC_LIGHT_SLEEP static bool pmLightSleepLockAcquired; #endif static concurrency::Lock *lightSleepConcurrencyLock; @@ -335,7 +335,7 @@ void doLightSleep(uint32_t sleepMsec) assert(lightSleepConcurrencyLock); lightSleepConcurrencyLock->lock(); -#ifndef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if !HAS_ESP32_DYNAMIC_LIGHT_SLEEP assert(sleepMsec != LIGHT_SLEEP_ABORT); assert(sleepMsec != LIGHT_SLEEP_DYNAMIC); #else @@ -419,7 +419,7 @@ void doLightSleep(uint32_t sleepMsec) notifyLightSleepEnd.notifyObservers(wakeCause); } else { -#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if HAS_ESP32_DYNAMIC_LIGHT_SLEEP res = esp_pm_lock_release(pmLightSleepLock); assert(res == ESP_OK); pmLightSleepLockAcquired = false; @@ -441,7 +441,7 @@ void initLightSleep() } #endif -#ifdef HAS_ESP32_PM_SUPPORT +#if HAS_ESP32_PM_SUPPORT res = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "meshtastic", &pmLightSleepLock); assert(res == ESP_OK); @@ -451,7 +451,7 @@ void initLightSleep() esp_pm_config_esp32_t pm_config; pm_config.max_freq_mhz = 80; pm_config.min_freq_mhz = dfsSupported ? 20 : pm_config.max_freq_mhz; -#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if HAS_ESP32_DYNAMIC_LIGHT_SLEEP pm_config.light_sleep_enable = true; #else pm_config.light_sleep_enable = false; @@ -466,7 +466,7 @@ void initLightSleep() lightSleepConcurrencyLock = new concurrency::Lock(); -#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +#if HAS_ESP32_DYNAMIC_LIGHT_SLEEP pmLightSleepLockAcquired = true; #endif } diff --git a/variants/esp32s3/heltec_v3/variant.h b/variants/esp32s3/heltec_v3/variant.h index c888f56f0..13f41411a 100644 --- a/variants/esp32s3/heltec_v3/variant.h +++ b/variants/esp32s3/heltec_v3/variant.h @@ -1,7 +1,5 @@ #define LED_PIN LED -#define HAS_32768HZ - #define USE_SSD1306 // Heltec_v3 has a SSD1306 display #define RESET_OLED RST_OLED @@ -43,4 +41,4 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#define HAS_32768HZ 1 \ No newline at end of file +#define HAS_32768HZ 1