From 035dfaf60244b580d7d5abc56ec016549e0e6bdb Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:33:23 +0200 Subject: [PATCH 01/31] 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 3b3f8080d..afd4beaf1 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 c37001307..76f08cefb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -254,7 +254,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; @@ -1410,6 +1410,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 cdea53c9a..c13865500 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 09484f46e..313ce0ecf 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 0b5030b436c01b8520ad6f3c25239017a0a38952 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:33:48 +0200 Subject: [PATCH 02/31] ensure acquired SPI lock prevents light-sleep --- src/SPILock.cpp | 47 +++++++++++++++++++++++++++++++++++++++++- src/concurrency/Lock.h | 4 ++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/SPILock.cpp b/src/SPILock.cpp index 13fa556fc..9bb027ae8 100644 --- a/src/SPILock.cpp +++ b/src/SPILock.cpp @@ -1,12 +1,57 @@ #include "SPILock.h" +#include "PowerFSM.h" #include "configuration.h" +#include "sleep.h" #include #include concurrency::Lock *spiLock; +class SPILock : public concurrency::Lock +{ + public: + SPILock(); + ~SPILock(); + + void lock() override; + void unlock() override; + + private: + bool locked; + + int preflightSleepCb(void *unused = NULL) { return locked ? 1 : 0; } + + CallbackObserver preflightSleepObserver = + CallbackObserver(this, &SPILock::preflightSleepCb); +}; + +SPILock::SPILock() : Lock() +{ + locked = false; + preflightSleepObserver.observe(&preflightSleep); +} + +SPILock::~SPILock() +{ + preflightSleepObserver.unobserve(&preflightSleep); +} + +void SPILock::lock() +{ + powerFSM.trigger(EVENT_WAKE_TIMER); + + Lock::lock(); + locked = true; +} + +void SPILock::unlock() +{ + locked = false; + Lock::unlock(); +} + void initSPI() { assert(!spiLock); - spiLock = new concurrency::Lock(); + spiLock = new SPILock(); } \ No newline at end of file diff --git a/src/concurrency/Lock.h b/src/concurrency/Lock.h index 1b9ea20d5..a0ef84873 100644 --- a/src/concurrency/Lock.h +++ b/src/concurrency/Lock.h @@ -19,12 +19,12 @@ class Lock /// Locks the lock. // // Must not be called from an ISR. - void lock(); + virtual void lock(); // Unlocks the lock. // // Must not be called from an ISR. - void unlock(); + virtual void unlock(); private: #ifdef HAS_FREE_RTOS From 5d956d6bdcd7f012b8246d30eb98a047db54abe3 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:36:08 +0200 Subject: [PATCH 03/31] 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 287de38fa..cceac8dfe 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 @@ -35,10 +36,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 e3ef58f14..c6057231c 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(); @@ -531,4 +534,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 02968513c..15cf2424b 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 4a42e5197..6f309cc50 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -97,6 +97,8 @@ void StreamAPI::writeStream() 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; @@ -150,4 +152,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 d1b10fa82..f1158a1eb 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 a92013d01..b56064a71 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 f1c18084495a7f6932ff99205cb86363a13e1db6 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:36:47 +0200 Subject: [PATCH 04/31] 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 9c67977bd..a2e23a8ef 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -572,6 +572,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() @@ -708,6 +721,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; @@ -862,10 +877,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 c71f96c10..a3bd90229 100644 --- a/src/power.h +++ b/src/power.h @@ -109,6 +109,7 @@ class Power : private concurrency::OSThread Observable newStatus; Power(); + ~Power(); void shutdown(); void readPowerStatus(); @@ -133,6 +134,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 2d5b9297e3cba744ede6f877b2d5471fef60ce47 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:38:17 +0200 Subject: [PATCH 05/31] improve WiFi power saving configuration (stable in current sdk) --- src/mesh/wifi/WiFiAPClient.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 7a56c258b..dd29eb3c5 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -296,10 +296,12 @@ bool initWifi() #ifdef ARCH_ESP32 WiFi.onEvent(WiFiEvent); WiFi.setAutoReconnect(true); - WiFi.setSleep(false); - // This is needed to improve performance. - esp_wifi_set_ps(WIFI_PS_NONE); // Disable radio power saving +#ifdef WIFI_MAX_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); +#endif WiFi.onEvent( [](WiFiEvent_t event, WiFiEventInfo_t info) { From 5745c4ecf22aac1af28491528ed195dbbc33aa71 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 00:40:27 +0200 Subject: [PATCH 06/31] switch heltec_v3 variant to esp-idf sdk with power management enabled --- variants/heltec_v3/platformio.ini | 6 ++++-- variants/heltec_v3/variant.h | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 4be96b019..9bdb81301 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -1,8 +1,10 @@ -[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+1685ef30.tar.gz ; enable PM support in ESP-IDF board = heltec_wifi_lora_32_V3 board_check = true board_build.partitions = default_8MB.csv -build_flags = +build_flags = ${esp32s3_base.build_flags} -D HELTEC_V3 -I variants/heltec_v3 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/heltec_v3/variant.h b/variants/heltec_v3/variant.h index 4f1d91db8..710a7bfdb 100644 --- a/variants/heltec_v3/variant.h +++ b/variants/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 802c2fa7aea231980913d9abdb3b27bd07f54cf2 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 24 Apr 2025 16:52:24 +0200 Subject: [PATCH 07/31] 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 1d2a5c0692d7eea362f6edc903a3fff33c4235dc Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 25 Apr 2025 00:23:14 +0200 Subject: [PATCH 08/31] remove unnecessary lock --- src/SPILock.cpp | 47 +---------------------------------------------- 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/src/SPILock.cpp b/src/SPILock.cpp index 9bb027ae8..13fa556fc 100644 --- a/src/SPILock.cpp +++ b/src/SPILock.cpp @@ -1,57 +1,12 @@ #include "SPILock.h" -#include "PowerFSM.h" #include "configuration.h" -#include "sleep.h" #include #include concurrency::Lock *spiLock; -class SPILock : public concurrency::Lock -{ - public: - SPILock(); - ~SPILock(); - - void lock() override; - void unlock() override; - - private: - bool locked; - - int preflightSleepCb(void *unused = NULL) { return locked ? 1 : 0; } - - CallbackObserver preflightSleepObserver = - CallbackObserver(this, &SPILock::preflightSleepCb); -}; - -SPILock::SPILock() : Lock() -{ - locked = false; - preflightSleepObserver.observe(&preflightSleep); -} - -SPILock::~SPILock() -{ - preflightSleepObserver.unobserve(&preflightSleep); -} - -void SPILock::lock() -{ - powerFSM.trigger(EVENT_WAKE_TIMER); - - Lock::lock(); - locked = true; -} - -void SPILock::unlock() -{ - locked = false; - Lock::unlock(); -} - void initSPI() { assert(!spiLock); - spiLock = new SPILock(); + spiLock = new concurrency::Lock(); } \ No newline at end of file From 31c1d935f2d3138f58eddea037c8286082155765 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 25 Apr 2025 00:24:59 +0200 Subject: [PATCH 09/31] 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 afd4beaf1..6f7b76cf3 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 c6057231c..ef0f4fc93 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 be8a87c9b61ebb1d12e099dd09f7331e6c6de768 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 25 Apr 2025 09:37:45 +0200 Subject: [PATCH 10/31] reference new arduino-esp32 release --- variants/heltec_v3/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 9bdb81301..04e6d7abd 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -1,7 +1,7 @@ [env:heltec-v3] extends = esp32s3_base platform_packages = - platformio/framework-arduinoespressif32 @ https://github.com/m1nl/arduino-esp32/archive/refs/tags/2.0.17+1685ef30.tar.gz ; enable PM support in ESP-IDF + 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_check = true board_build.partitions = default_8MB.csv From a0af522ceb8c8a0abed1867501c25c6d1ad1d5b1 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 25 Apr 2025 10:18:03 +0200 Subject: [PATCH 11/31] revert change to Lock class --- src/concurrency/Lock.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/concurrency/Lock.h b/src/concurrency/Lock.h index a0ef84873..1b9ea20d5 100644 --- a/src/concurrency/Lock.h +++ b/src/concurrency/Lock.h @@ -19,12 +19,12 @@ class Lock /// Locks the lock. // // Must not be called from an ISR. - virtual void lock(); + void lock(); // Unlocks the lock. // // Must not be called from an ISR. - virtual void unlock(); + void unlock(); private: #ifdef HAS_FREE_RTOS From 850407beba59305249b2f56e06993f1e9d885381 Mon Sep 17 00:00:00 2001 From: m1nl Date: Thu, 1 May 2025 15:10:29 +0200 Subject: [PATCH 12/31] enable custom arduino framework for xiao s3 and t-beam s3 --- variants/seeed_xiao_s3/platformio.ini | 8 +++++--- variants/tbeam-s3-core/platformio.ini | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/variants/seeed_xiao_s3/platformio.ini b/variants/seeed_xiao_s3/platformio.ini index 9d935e2e0..83108e132 100644 --- a/variants/seeed_xiao_s3/platformio.ini +++ b/variants/seeed_xiao_s3/platformio.ini @@ -1,5 +1,7 @@ [env:seeed-xiao-s3] 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 = seeed-xiao-s3 board_check = true board_build.partitions = default_8MB.csv @@ -10,8 +12,8 @@ lib_deps = build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 -build_flags = +build_flags = ${esp32s3_base.build_flags} -DSEEED_XIAO_S3 -I variants/seeed_xiao_s3 - -DBOARD_HAS_PSRAM + -DBOARD_HAS_PSRAM - -DARDUINO_USB_MODE=0 \ No newline at end of file + -DARDUINO_USB_MODE=0 diff --git a/variants/tbeam-s3-core/platformio.ini b/variants/tbeam-s3-core/platformio.ini index a7bdf963f..96c2de4fc 100644 --- a/variants/tbeam-s3-core/platformio.ini +++ b/variants/tbeam-s3-core/platformio.ini @@ -1,6 +1,8 @@ -; The 1.0 release of the LilyGo TBEAM-S3-Core board +; The 1.0 release of the LilyGo TBEAM-S3-Core board [env:tbeam-s3-core] 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 = tbeam-s3-core board_build.partitions = default_8MB.csv board_check = true @@ -9,7 +11,7 @@ lib_deps = ${esp32s3_base.lib_deps} lewisxhe/PCF8563_Library@1.0.1 -build_flags = - ${esp32s3_base.build_flags} +build_flags = + ${esp32s3_base.build_flags} -Ivariants/tbeam-s3-core -DPCF8563_RTC=0x51 ;Putting definitions in variant.h does not compile correctly From 759b3a09e82ec26a78a1251991bebe38b5efcaa4 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 2 May 2025 21:06:13 +0200 Subject: [PATCH 13/31] 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 313ce0ecf..46b523370 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 067f9558c211c306504539714e57fef46a085d3f Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 2 May 2025 21:06:46 +0200 Subject: [PATCH 14/31] make log entry more consistent --- src/PowerFSM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 6f7b76cf3..d4f00f5c8 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); } } From 6e41f46df23fbb89b181d1eb1548067fd0bb36c8 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 13 May 2025 14:21:16 +0200 Subject: [PATCH 15/31] 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 091612827..06da66c39 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -585,14 +585,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 46f2a62186037998fc50ec3c6bfcb87096afd2e2 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 13 May 2025 14:23:50 +0200 Subject: [PATCH 16/31] improve logging --- src/PowerFSM.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index d4f00f5c8..8a24b7aa4 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -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"); From a267a645eb8e710f8e2dbb97c0efa9e16553ee30 Mon Sep 17 00:00:00 2001 From: m1nl Date: Sun, 18 May 2025 18:09:57 +0200 Subject: [PATCH 17/31] set custom framework for heltec_vision_master_e213 --- variants/heltec_vision_master_e213/platformio.ini | 12 ++++++++---- variants/heltec_vision_master_e213/variant.h | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/heltec_vision_master_e213/platformio.ini index 028caaeff..10f5e7700 100644 --- a/variants/heltec_vision_master_e213/platformio.ini +++ b/variants/heltec_vision_master_e213/platformio.ini @@ -2,9 +2,11 @@ extends = esp32s3_base board = heltec_vision_master_e213 board_build.partitions = default_8MB.csv -build_flags = +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 +build_flags = ${esp32s3_base.build_flags} - -Ivariants/heltec_vision_master_e213 + -Ivariants/heltec_vision_master_e213 -DHELTEC_VISION_MASTER_E213 -DUSE_EINK -DGXEPD2_DRIVER_0=GxEPD2_213_FC1 @@ -25,10 +27,12 @@ upload_speed = 115200 extends = esp32s3_base, inkhud board = heltec_vision_master_e213 board_build.partitions = default_8MB.csv +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 build_src_filter = ${esp32_base.build_src_filter} ${inkhud.build_src_filter} -build_flags = +build_flags = ${esp32s3_base.build_flags} ${inkhud.build_flags} -I variants/heltec_vision_master_e213 @@ -36,4 +40,4 @@ build_flags = lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${esp32s3_base.lib_deps} -upload_speed = 921600 \ No newline at end of file +upload_speed = 921600 diff --git a/variants/heltec_vision_master_e213/variant.h b/variants/heltec_vision_master_e213/variant.h index 60f4e00cc..cccd3e0cd 100644 --- a/variants/heltec_vision_master_e213/variant.h +++ b/variants/heltec_vision_master_e213/variant.h @@ -1,4 +1,7 @@ #define LED_PIN 45 // LED is not populated on earliest board variant + +#define HAS_32768HZ + #define BUTTON_PIN 0 #define PIN_BUTTON2 21 // Second built-in button #define ALT_BUTTON_PIN PIN_BUTTON2 // Send the up event From 774eb1a226c95980354bdc5e2ef1d1700eaf5d6e Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 3 Jun 2025 09:33:50 +0200 Subject: [PATCH 18/31] use framework with dynamic light sleep for heltec_wsl_v3 --- variants/heltec_wsl_v3/platformio.ini | 6 ++++-- variants/heltec_wsl_v3/variant.h | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/variants/heltec_wsl_v3/platformio.ini b/variants/heltec_wsl_v3/platformio.ini index bc3e6ada1..0bfd262f2 100644 --- a/variants/heltec_wsl_v3/platformio.ini +++ b/variants/heltec_wsl_v3/platformio.ini @@ -1,8 +1,10 @@ -[env:heltec-wsl-v3] +[env:heltec-wsl-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_build.partitions = default_8MB.csv # Temporary until espressif creates a release with this new target -build_flags = +build_flags = ${esp32s3_base.build_flags} -D HELTEC_WSL_V3 -I variants/heltec_wsl_v3 -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. diff --git a/variants/heltec_wsl_v3/variant.h b/variants/heltec_wsl_v3/variant.h index c103b9172..2a64f0bc6 100644 --- a/variants/heltec_wsl_v3/variant.h +++ b/variants/heltec_wsl_v3/variant.h @@ -1,3 +1,5 @@ +#define HAS_32768HZ + #define I2C_SCL SCL #define I2C_SDA SDA @@ -33,4 +35,4 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 \ No newline at end of file +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 From f22d2f46a108ef6e6c6fc6c90e37c557f7b57100 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 6 Jun 2025 21:17:56 +0200 Subject: [PATCH 19/31] fix logging --- src/sleep.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index 46b523370..04b1e0d00 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 47881d11a6369e3a8f9de1c523e63115942b43de Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 6 Jun 2025 21:20:20 +0200 Subject: [PATCH 20/31] 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 6f309cc50..ba949818a 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -66,12 +66,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 94a560b6781e4fef3f2e8b3db8975d6fcbdb893c Mon Sep 17 00:00:00 2001 From: m1nl Date: Mon, 23 Jun 2025 00:21:41 +0200 Subject: [PATCH 21/31] 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 ad667f003..70d1a3ece 100644 --- a/src/input/ButtonThread.cpp +++ b/src/input/ButtonThread.cpp @@ -300,7 +300,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 8cb9344ed77c6675331cf842c0a0f48925ad9f4c Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 02:13:59 +0200 Subject: [PATCH 22/31] remove stateNB, improve regular (non-dynamic) light-sleep handling --- src/PowerFSM.cpp | 117 ++++++++++++-------------------------------- src/PowerFSM.h | 5 +- src/main.cpp | 8 ++- src/mesh/Router.cpp | 1 - 4 files changed, 40 insertions(+), 91 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 8a24b7aa4..4f2108177 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..6eea31a9d 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 diff --git a/src/main.cpp b/src/main.cpp index 76f08cefb..ccf8e788f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1369,12 +1369,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 15cf2424b..c50aefe8e 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 07804e1efae224d8a2dfd8e62770d39b15059f4e Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 09:23:37 +0200 Subject: [PATCH 23/31] move feature flags --- src/PowerFSM.cpp | 4 ++-- src/PowerFSM.h | 5 +---- src/platform/esp32/architecture.h | 10 ++++++++++ src/sleep.cpp | 20 ++++++++++---------- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 4f2108177..df2e088cd 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 6eea31a9d..e79bc07ed 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 baefbc4eb..caf2585db 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -211,3 +211,13 @@ #endif #define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc. + +// Setup flag, which indicates if our device supports power management +#ifdef CONFIG_PM_ENABLE +#define HAS_ESP32_PM_SUPPORT +#endif + +// 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_DYNAMIC_LIGHT_SLEEP +#endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 04b1e0d00..ace271f46 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 4dddfa3cbbc4f76e22f4a1b724fffb90726861ed Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 09:56:37 +0200 Subject: [PATCH 24/31] 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 a2e23a8ef..abe44d9e5 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -889,12 +889,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 fda2b899ffa2aff4bd58d84bab6f600f262a5ec2 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 11:20:13 +0200 Subject: [PATCH 25/31] 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 df2e088cd..77ebee1dc 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() power->shutdown(); } -#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 ace271f46..683fb2bb2 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 91d25d2b4510ffffe5242f4e6cf21e2cffc480aa Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 20:40:13 +0200 Subject: [PATCH 26/31] another refactor to better handle variants without dynamic light sleep --- src/PowerFSM.cpp | 41 +++++++----- src/PowerFSM.h | 9 ++- src/platform/esp32/architecture.h | 2 +- src/sleep.cpp | 101 ++++++++++++++++-------------- src/sleep.h | 1 + 5 files changed, 89 insertions(+), 65 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 77ebee1dc..5d04b1215 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 e79bc07ed..eb630e79b 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, stateNB, stateLS; void PowerFSM_setup(); -#endif \ No newline at end of file +#endif diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index caf2585db..048579b9d 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -219,5 +219,5 @@ // 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_DYNAMIC_LIGHT_SLEEP +#define HAS_ESP32_DYNAMIC_LIGHT_SLEEP #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 683fb2bb2..f45417a12 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 0f74118ef415e17c575edd4c12d4ba4639a7c899 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 21:20:22 +0200 Subject: [PATCH 27/31] remvoe stateNB declaration --- src/PowerFSM.h | 2 +- src/sleep.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PowerFSM.h b/src/PowerFSM.h index eb630e79b..362c1af86 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -56,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 diff --git a/src/sleep.cpp b/src/sleep.cpp index f45417a12..165c82b1e 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -427,6 +427,7 @@ void doLightSleep(uint32_t sleepMsec) #ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP res = esp_pm_lock_release(pmLightSleepLock); assert(res == ESP_OK); + pmLightSleepLockAcquired = false; #endif } From c1c2731a197ecb9ba9f354645c81e3cf4fb5fa53 Mon Sep 17 00:00:00 2001 From: m1nl Date: Tue, 24 Jun 2025 22:11:32 +0200 Subject: [PATCH 28/31] 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 165c82b1e..a51a0aed0 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -476,11 +476,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 f725505f54eac0d834928e65870bb75dc71de63b Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 27 Jun 2025 18:26:52 +0200 Subject: [PATCH 29/31] 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 5d04b1215..239f035dc 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 fb4418b952948074dcce040f5dd2b593bf82ad20 Mon Sep 17 00:00:00 2001 From: m1nl Date: Fri, 27 Jun 2025 21:41:23 +0200 Subject: [PATCH 30/31] 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 ccf8e788f..8bcab635f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -253,8 +253,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 4223701d0ed874ae06124a065721bc5d3003bf65 Mon Sep 17 00:00:00 2001 From: m1nl Date: Wed, 2 Jul 2025 22:26:19 +0200 Subject: [PATCH 31/31] 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 3a6b19f64..8bc2a543d 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -805,13 +805,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); } @@ -1171,6 +1173,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);