diff --git a/src/Power.cpp b/src/Power.cpp index 9c67977bd..abe44d9e5 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,37 @@ 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) +{ + // 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; +} +#endif + /** * Init the power manager chip * diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 3b3f8080d..239f035dc 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -16,20 +16,27 @@ #include "configuration.h" #include "graphics/Screen.h" #include "main.h" +#include "power.h" #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(){}; #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() { @@ -55,19 +62,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() { @@ -75,105 +75,106 @@ static void shutdownEnter() power->shutdown(); } -#include "error.h" - -static uint32_t secsSlept; - 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"); + 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() { - // LOG_INFO("lsIdle begin ls_secs=%u", getPref_ls_secs()); + if (!doPreflightSleep()) { +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP + powerFSM.trigger(EVENT_WAKE_TIMER); +#endif + return; + } + + if (sleepStart == -1) { + sleepStart = millis(); + } + + sleepTime = millis() - sleepStart; + sleepLeft = config.power.ls_secs * 1000LL - sleepTime; + if (sleepLeft > SLEEP_TIME_QUANTUM_S * 1000LL) { + sleepLeft = SLEEP_TIME_QUANTUM_S * 1000LL; + } #ifdef ARCH_ESP32 - - // 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; - - 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); - - switch (wakeCause2) { - case ESP_SLEEP_WAKEUP_TIMER: - // Normal case: timer expired, we should just go back to sleep ASAP - - 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; +#ifndef HAS_ESP32_DYNAMIC_LIGHT_SLEEP + doLightSleep(sleepLeft); #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); + + esp_sleep_source_t cause = esp_sleep_get_wakeup_cause(); + + switch (cause) { + case ESP_SLEEP_WAKEUP_UART: + LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_UART"); + 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); + return; + + case ESP_SLEEP_WAKEUP_GPIO: + LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_GPIO"); + // FSM events should be triggered by interrupt handlers + return; + + default: + if (sleepTime > config.power.ls_secs * 1000LL) { + powerFSM.trigger(EVENT_WAKE_TIMER); + return; } - } else { - // Time to stop sleeping! - ledBlink.set(false); - LOG_INFO("Reached ls_secs, service loop()"); - powerFSM.trigger(EVENT_WAKE_TIMER); + break; } #endif } static void lsExit() { - LOG_INFO("Exit state: LS"); -} - -static void nbEnter() -{ - LOG_DEBUG("State: NB"); - if (screen) - screen->setOn(false); -#ifdef ARCH_ESP32 - // Only ESP32 should turn off bluetooth - setBluetoothEnable(false); +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP + doLightSleep(LIGHT_SLEEP_ABORT); #endif - // FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE + if (sleepStart != -1) { + sleepTime = millis() - sleepStart; + sleepStart = 0; + + powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); + + LOG_DEBUG("Exit state: LS, stayed %d ms in light-sleep state", sleepTime); + } } static void darkEnter() { - setBluetoothEnable(true); + // LOG_DEBUG("State: DARK"); i if (screen) screen->setOn(false); } @@ -181,29 +182,22 @@ 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"); + 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); - setBluetoothEnable(true); // within enter() the function getState() returns the state we came from } } @@ -221,7 +215,6 @@ static void powerExit() { if (screen) screen->setOn(true); - setBluetoothEnable(true); } static void onEnter() @@ -229,7 +222,6 @@ static void onEnter() LOG_DEBUG("State: ON"); if (screen) screen->setOn(true); - setBluetoothEnable(true); } static void onIdle() @@ -246,12 +238,10 @@ 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 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"); @@ -262,26 +252,13 @@ 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", 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"); - // 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"); -#endif - - // 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"); - // 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 @@ -291,7 +268,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"); @@ -299,59 +275,69 @@ 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 + 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"); - // 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"); // Removed 2.7: we don't show the nodes individually for every node on the screen anymore - // powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); + // powerFSM.add_transition(&stateLS, &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 } // 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(&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(&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(&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, &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer"); + powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer"); + + powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); + powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_WEB_REQUEST, NULL, "Web request"); + +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP + // 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 @@ -366,41 +352,11 @@ 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 - // 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(&stateDARK, &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..362c1af86 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -21,6 +21,18 @@ #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_RADIO_INTERRUPT 18 +#define EVENT_WEB_REQUEST 19 + +#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 @@ -41,11 +53,10 @@ class FakeFsm }; extern FakeFsm powerFSM; void PowerFSM_setup(); - #else #include extern Fsm powerFSM; -extern State stateON, statePOWER, stateSERIAL, stateDARK; +extern State stateON, statePOWER, stateSERIAL, stateDARK, stateLS; void PowerFSM_setup(); -#endif \ No newline at end of file +#endif 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); 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 } diff --git a/src/main.cpp b/src/main.cpp index c37001307..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() ? 1000 : (ledOn ? 1 : 1000); + return (powerStatus->getIsCharging() && !isSleeping) ? 1000 : (ledOn ? 1 : 1000); } uint32_t timeLastPowered = 0; @@ -1369,12 +1376,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(); @@ -1410,6 +1421,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/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..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 @@ -172,4 +173,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() && (millis() - lastContactMsec) < WAKE_SESSION_TIMEOUT_MS; } + + CallbackObserver preflightSleepObserver = + CallbackObserver(this, &PhoneAPI::preflightSleepCb); }; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index e3ef58f14..ef0f4fc93 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_RADIO_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..c50aefe8e 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); 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..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; } } @@ -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/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) { 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; 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 diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index baefbc4eb..048579b9d 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_ESP32_DYNAMIC_LIGHT_SLEEP +#endif 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/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; diff --git a/src/sleep.cpp b/src/sleep.cpp index 09484f46e..a51a0aed0 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 HAS_ESP32_PM_SUPPORT #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,27 @@ Observable notifyDeepSleep; Observable notifyReboot; #ifdef ARCH_ESP32 +// Wake cause when returning from 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 HAS_ESP32_PM_SUPPORT +esp_pm_lock_handle_t pmLightSleepLock; +#endif + +// 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 // deep sleep support @@ -68,30 +83,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 +106,7 @@ void initDeepSleep() { #ifdef ARCH_ESP32 bootCount++; + const char *reason; wakeCause = esp_sleep_get_wakeup_cause(); @@ -147,30 +154,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); - } + gpioReset(); } #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 +193,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 +264,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 +310,361 @@ 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 +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP +static bool pmLightSleepLockAcquired; +#endif +static concurrency::Lock *lightSleepConcurrencyLock; + /** * 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(lightSleepConcurrencyLock); + lightSleepConcurrencyLock->lock(); + +#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; + } + + res = esp_pm_lock_acquire(pmLightSleepLock); + assert(res == ESP_OK); + + wakeCause = esp_sleep_get_wakeup_cause(); + + pmLightSleepLockAcquired = true; + + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); + gpioReset(); + + notifyLightSleepEnd.notifyObservers(wakeCause); + } + + if (sleepMsec == LIGHT_SLEEP_ABORT) { + lightSleepConcurrencyLock->unlock(); + return; + } #endif - waitEnterSleep(false); - notifyLightSleep.notifyObservers(NULL); // Button interrupts are detached here - - 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(); + + 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); + + res = esp_sleep_enable_uart_wakeup(UART_NUM_0); + assert(res == ESP_OK); + +#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 + +#ifdef INPUTDRIVER_ENCODER_BTN + res = gpio_wakeup_enable((gpio_num_t)INPUTDRIVER_ENCODER_BTN, GPIO_INTR_LOW_LEVEL); + assert(res == ESP_OK); +#endif +#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) - 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); - } + + res = esp_sleep_enable_gpio_wakeup(); assert(res == ESP_OK); + notifyLightSleep.notifyObservers(NULL); + 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); -#endif -#if defined(INPUTDRIVER_ENCODER_BTN) - gpio_wakeup_disable((gpio_num_t)INPUTDRIVER_ENCODER_BTN); -#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); - } -#endif + if (sleepMsec != LIGHT_SLEEP_DYNAMIC) { + esp_light_sleep_start(); - esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); - notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here + wakeCause = esp_sleep_get_wakeup_cause(); -#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 + 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 - { - LOG_INFO("Exit light sleep cause: %d", cause); } - return cause; + lightSleepConcurrencyLock->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 HAS_ESP32_PM_SUPPORT + res = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "meshtastic", &pmLightSleepLock); + assert(res == ESP_OK); + + 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_ESP32_DYNAMIC_LIGHT_SLEEP + 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 + + lightSleepConcurrencyLock = new concurrency::Lock(); + +#ifdef HAS_ESP32_DYNAMIC_LIGHT_SLEEP + pmLightSleepLockAcquired = true; +#endif +} + +void gpioReset() +{ + esp_err_t res; + + // 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); + } +#endif + 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 + } +} + +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 + 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); +#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); +#ifdef CONFIG_IDF_TARGET_ESP32 + res = esp_sleep_enable_ext1_wakeup(1ULL << pin, ESP_EXT1_WAKEUP_ALL_LOW); #else - esp32_config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ; + 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 + res = gpio_pullup_en(pin); + assert(res == ESP_OK); +#endif + 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 + res = gpio_pullup_en(pin); + assert(res == ESP_OK); +#endif + res = gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL); + 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 +} + +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 + + if (pin == GPIO_NUM_NC) { + return; + } + +#if defined(LORA_RESET) && (LORA_RESET != GPIO_NUM_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 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); + 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_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_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); + } #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); } bool shouldLoraWake(uint32_t msecToWake) @@ -507,39 +672,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..14ac890b2 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -4,52 +4,48 @@ #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 +#define LIGHT_SLEEP_DYNAMIC UINT32_MAX + +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 diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 4be96b019..04e6d7abd 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+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 -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 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 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 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