mirror of
https://github.com/meshtastic/firmware.git
synced 2025-07-31 02:45:41 +00:00
refactor sleep and PowerFSM to allow dynamic light sleep
This commit is contained in:
parent
cf574c71d8
commit
035dfaf602
247
src/PowerFSM.cpp
247
src/PowerFSM.cpp
@ -19,13 +19,19 @@
|
|||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "target_specific.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"
|
#include "mesh/wifi/WiFiAPClient.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef SLEEP_TIME
|
#ifndef SLEEP_TIME
|
||||||
#define SLEEP_TIME 30
|
#define SLEEP_TIME 30
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if MESHTASTIC_EXCLUDE_POWER_FSM
|
#if MESHTASTIC_EXCLUDE_POWER_FSM
|
||||||
FakeFsm powerFSM;
|
FakeFsm powerFSM;
|
||||||
void PowerFSM_setup(){};
|
void PowerFSM_setup(){};
|
||||||
@ -77,85 +83,104 @@ static void shutdownEnter()
|
|||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
|
|
||||||
static uint32_t secsSlept;
|
uint32_t sleepStart;
|
||||||
|
uint32_t sleepTime;
|
||||||
|
|
||||||
static void lsEnter()
|
static void lsEnter()
|
||||||
{
|
{
|
||||||
LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs);
|
LOG_DEBUG("State: LS");
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setOn(false);
|
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()
|
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
|
#ifdef ARCH_ESP32
|
||||||
|
esp_sleep_source_t cause = esp_sleep_get_wakeup_cause();
|
||||||
|
|
||||||
// Do we have more sleeping to do?
|
switch (cause) {
|
||||||
if (secsSlept < config.power.ls_secs) {
|
case ESP_SLEEP_WAKEUP_UART:
|
||||||
// If some other service would stall sleep, don't let sleep happen yet
|
LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_UART");
|
||||||
if (doPreflightSleep()) {
|
powerFSM.trigger(EVENT_INPUT);
|
||||||
// Briefly come out of sleep long enough to blink the led once every few seconds
|
return;
|
||||||
uint32_t sleepTime = SLEEP_TIME;
|
|
||||||
|
|
||||||
powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep);
|
case ESP_SLEEP_WAKEUP_EXT0:
|
||||||
ledBlink.set(false); // Never leave led on while in light sleep
|
LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_EXT0");
|
||||||
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
|
powerFSM.trigger(EVENT_LORA_INTERRUPT);
|
||||||
powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep);
|
return;
|
||||||
|
|
||||||
switch (wakeCause2) {
|
case ESP_SLEEP_WAKEUP_EXT1:
|
||||||
case ESP_SLEEP_WAKEUP_TIMER:
|
LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_EXT1");
|
||||||
// Normal case: timer expired, we should just go back to sleep ASAP
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
|
return;
|
||||||
|
|
||||||
ledBlink.set(true); // briefly turn on led
|
case ESP_SLEEP_WAKEUP_GPIO:
|
||||||
wakeCause2 = doLightSleep(100); // leave led on for 1ms
|
LOG_DEBUG("Wake cause ESP_SLEEP_WAKEUP_GPIO");
|
||||||
|
|
||||||
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()");
|
|
||||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lsExit()
|
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()
|
static void nbEnter()
|
||||||
@ -167,12 +192,11 @@ static void nbEnter()
|
|||||||
// Only ESP32 should turn off bluetooth
|
// Only ESP32 should turn off bluetooth
|
||||||
setBluetoothEnable(false);
|
setBluetoothEnable(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// FIXME - check if we already have packets for phone and immediately trigger EVENT_PACKETS_FOR_PHONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void darkEnter()
|
static void darkEnter()
|
||||||
{
|
{
|
||||||
|
// LOG_DEBUG("State: DARK");
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setOn(false);
|
screen->setOn(false);
|
||||||
@ -195,11 +219,12 @@ static void serialExit()
|
|||||||
|
|
||||||
static void powerEnter()
|
static void powerEnter()
|
||||||
{
|
{
|
||||||
// LOG_DEBUG("State: POWER");
|
LOG_DEBUG("State: POWER");
|
||||||
if (!isPowered()) {
|
if (!isPowered()) {
|
||||||
// If we got here, we are in the wrong state - we should be in powered, let that state handle things
|
// 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");
|
LOG_INFO("Loss of power in Powered");
|
||||||
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setOn(true);
|
screen->setOn(true);
|
||||||
@ -261,23 +286,16 @@ void PowerFSM_setup()
|
|||||||
{
|
{
|
||||||
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
|
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
|
||||||
bool hasPower = isPowered();
|
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
|
#ifdef ARCH_ESP32
|
||||||
powerFSM.add_transition(&stateLS, isRouter ? &stateNB : &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer");
|
stateIDLE = isRouter ? &stateNB : &stateDARK;
|
||||||
#else // Don't go into a no-bluetooth state on low power platforms
|
#else
|
||||||
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, NULL, "Wake timer");
|
stateIDLE = &stateDARK;
|
||||||
#endif
|
#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
|
powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout");
|
||||||
// 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
|
// Handle press events - note: we ignore button presses when in API mode
|
||||||
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
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(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
|
||||||
powerFSM.add_transition(&stateON, &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 (!isRouter) {
|
||||||
// if any packet destined for phone arrives, turn on bluetooth at least
|
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
||||||
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for 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
|
// 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(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update");
|
||||||
// powerFSM.add_transition(&stateDARK, &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");
|
// 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(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text");
|
||||||
powerFSM.add_transition(&stateDARK, &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
|
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
|
// 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(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API");
|
||||||
powerFSM.add_transition(&stateNB, &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(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API");
|
||||||
powerFSM.add_transition(&stateON, &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(&statePOWER, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "Serial API");
|
||||||
|
|
||||||
// If we get power connected, go to the power connect state
|
// 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(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect");
|
||||||
powerFSM.add_transition(&stateNB, &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(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "Power connect");
|
||||||
powerFSM.add_transition(&stateON, &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(&statePOWER, &stateON, EVENT_POWER_DISCONNECTED, NULL, "Power disconnected");
|
||||||
// powerFSM.add_transition(&stateSERIAL, &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)
|
// 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)
|
// 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
|
#ifdef USE_EINK
|
||||||
// Allow E-Ink devices to suppress the screensaver, if screen timeout set to 0
|
// 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)
|
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
// See: https://github.com/meshtastic/firmware/issues/1071
|
if (config.power.is_power_saving) {
|
||||||
// Don't add power saving transitions if we are a power saving tracker or sensor or have Wifi enabled. Sleep will be initiated
|
powerFSM.add_timed_transition(stateIDLE, &stateLS, WAKE_TIME_MS, NULL, "Min wake timeout");
|
||||||
// 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");
|
|
||||||
}
|
}
|
||||||
#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
|
#endif
|
||||||
|
|
||||||
powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state
|
powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state
|
||||||
|
@ -21,6 +21,16 @@
|
|||||||
#define EVENT_FIRMWARE_UPDATE 15 // We just received a new firmware update packet from the phone
|
#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_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_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
|
#if MESHTASTIC_EXCLUDE_POWER_FSM
|
||||||
class FakeFsm
|
class FakeFsm
|
||||||
@ -45,7 +55,7 @@ void PowerFSM_setup();
|
|||||||
#else
|
#else
|
||||||
#include <Fsm.h>
|
#include <Fsm.h>
|
||||||
extern Fsm powerFSM;
|
extern Fsm powerFSM;
|
||||||
extern State stateON, statePOWER, stateSERIAL, stateDARK;
|
extern State stateON, statePOWER, stateSERIAL, stateDARK, stateNB, stateLS;
|
||||||
|
|
||||||
void PowerFSM_setup();
|
void PowerFSM_setup();
|
||||||
#endif
|
#endif
|
@ -254,7 +254,7 @@ static int32_t ledBlinker()
|
|||||||
ledBlink.set(ledOn);
|
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
|
// 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;
|
uint32_t timeLastPowered = 0;
|
||||||
@ -1410,6 +1410,12 @@ void setup()
|
|||||||
1000);
|
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
|
// 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
|
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();
|
powerFSMthread = new PowerFSMThread();
|
||||||
|
@ -162,8 +162,6 @@ void esp32Setup()
|
|||||||
WiFiOTA::initialize();
|
WiFiOTA::initialize();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// enableModemSleep();
|
|
||||||
|
|
||||||
// Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any
|
// 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.
|
// false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now.
|
||||||
// #define APP_WATCHDOG_SECS 45
|
// #define APP_WATCHDOG_SECS 45
|
||||||
|
461
src/sleep.cpp
461
src/sleep.cpp
@ -10,6 +10,7 @@
|
|||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerMon.h"
|
#include "PowerMon.h"
|
||||||
|
#include "concurrency/Lock.h"
|
||||||
#include "detect/LoRaRadioType.h"
|
#include "detect/LoRaRadioType.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
@ -17,18 +18,16 @@
|
|||||||
#include "target_specific.h"
|
#include "target_specific.h"
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#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 "esp32/pm.h"
|
||||||
#include "esp_pm.h"
|
#include "esp_pm.h"
|
||||||
|
#endif
|
||||||
#if HAS_WIFI
|
#if HAS_WIFI
|
||||||
#include "mesh/wifi/WiFiAPClient.h"
|
#include "mesh/wifi/WiFiAPClient.h"
|
||||||
#endif
|
#endif
|
||||||
#include "rom/rtc.h"
|
#include "rom/rtc.h"
|
||||||
#include <RadioLib.h>
|
|
||||||
#include <driver/rtc_io.h>
|
#include <driver/rtc_io.h>
|
||||||
#include <driver/uart.h>
|
#include <driver/uart.h>
|
||||||
|
|
||||||
esp_sleep_source_t wakeCause; // the reason we booted this time
|
|
||||||
#endif
|
#endif
|
||||||
#include "Throttle.h"
|
#include "Throttle.h"
|
||||||
|
|
||||||
@ -46,11 +45,25 @@ Observable<void *> notifyDeepSleep;
|
|||||||
Observable<void *> notifyReboot;
|
Observable<void *> notifyReboot;
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#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
|
/// Called to tell observers that light sleep is about to begin
|
||||||
Observable<void *> notifyLightSleep;
|
Observable<void *> 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
|
||||||
Observable<esp_sleep_wakeup_cause_t> notifyLightSleepEnd;
|
Observable<esp_sleep_wakeup_cause_t> 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
|
#endif
|
||||||
|
|
||||||
// deep sleep support
|
// deep sleep support
|
||||||
@ -68,30 +81,21 @@ RTC_DATA_ATTR int bootCount = 0;
|
|||||||
*/
|
*/
|
||||||
void setCPUFast(bool on)
|
void setCPUFast(bool on)
|
||||||
{
|
{
|
||||||
#if defined(ARCH_ESP32) && HAS_WIFI && !HAS_TFT
|
#if defined(ARCH_ESP32) && !HAS_TFT
|
||||||
|
#ifdef HAS_WIFI
|
||||||
if (isWifiAvailable()) {
|
if (isWifiAvailable()) {
|
||||||
/*
|
#if !defined(CONFIG_IDF_TARGET_ESP32C3) && defined(WIFI_MAX_PERFORMANCE)
|
||||||
*
|
|
||||||
* 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
|
|
||||||
LOG_DEBUG("Set CPU to 240MHz because WiFi is in use");
|
LOG_DEBUG("Set CPU to 240MHz because WiFi is in use");
|
||||||
setCpuFrequencyMhz(240);
|
setCpuFrequencyMhz(240);
|
||||||
#endif
|
|
||||||
return;
|
return;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// The Heltec LORA32 V1 runs at 26 MHz base frequency and doesn't react well to switching to 80 MHz...
|
// 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)
|
#if !defined(ARDUINO_HELTEC_WIFI_LORA_32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
setCpuFrequencyMhz(on ? 240 : 80);
|
setCpuFrequencyMhz(on ? 240 : 80);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +104,7 @@ void initDeepSleep()
|
|||||||
{
|
{
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
bootCount++;
|
bootCount++;
|
||||||
|
|
||||||
const char *reason;
|
const char *reason;
|
||||||
wakeCause = esp_sleep_get_wakeup_cause();
|
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);
|
LOG_INFO("Booted, wake cause %d (boot count %d), reset_reason=%s", wakeCause, bootCount, reason);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SOC_RTCIO_HOLD_SUPPORTED
|
#ifdef ARCH_ESP32
|
||||||
// If waking from sleep, release any and all RTC GPIOs
|
|
||||||
if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) {
|
if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||||
LOG_DEBUG("Disable any holds on RTC IO pads");
|
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);
|
|
||||||
|
|
||||||
// ESP32 (original)
|
|
||||||
else if (GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)i))
|
|
||||||
gpio_hold_dis((gpio_num_t)i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool doPreflightSleep()
|
bool doPreflightSleep()
|
||||||
{
|
{
|
||||||
if (preflightSleep.notifyObservers(NULL) != 0)
|
return preflightSleep.notifyObservers(NULL) == 0;
|
||||||
return false; // vetoed
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell devices we are going to sleep and wait for them to handle things
|
/// 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)) {
|
if (INCLUDE_vTaskSuspend && (msecToWake == portMAX_DELAY)) {
|
||||||
LOG_INFO("Enter deep sleep forever");
|
LOG_INFO("Enter deep sleep forever");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
LOG_INFO("Enter deep sleep for %u seconds", msecToWake / 1000);
|
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)) {
|
if (shouldLoraWake(msecToWake)) {
|
||||||
enableLoraInterrupt();
|
enableLoraInterrupt();
|
||||||
}
|
}
|
||||||
#ifdef BUTTON_PIN
|
enableButtonInterrupt();
|
||||||
// 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
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_PMU
|
#ifdef HAS_PMU
|
||||||
@ -340,166 +308,256 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
|
|||||||
pinMode(I2C_SCL, ANALOG);
|
pinMode(I2C_SCL, ANALOG);
|
||||||
#endif
|
#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();
|
console->flush();
|
||||||
cpuDeepSleep(msecToWake);
|
cpuDeepSleep(msecToWake);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
|
bool pmLockAcquired;
|
||||||
|
concurrency::Lock *pmLightSleepLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* enter light sleep (preserves ram but stops everything about CPU).
|
* 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
|
* 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
|
assert(pmLightSleepLock);
|
||||||
// LightSleep.
|
pmLightSleepLock->lock();
|
||||||
#if defined(SENSECAP_INDICATOR)
|
|
||||||
return ESP_SLEEP_WAKEUP_TIMER;
|
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
|
#endif
|
||||||
|
|
||||||
waitEnterSleep(false);
|
pmLightSleepLock->unlock();
|
||||||
notifyLightSleep.notifyObservers(NULL); // Button interrupts are detached here
|
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();
|
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
|
#ifdef PMU_IRQ
|
||||||
// wake due to PMU can happen repeatedly if there is no battery installed or the battery fills
|
// wake due to PMU can happen repeatedly if there is no battery installed or the battery fills
|
||||||
if (pmu_found)
|
if (pmu_found) {
|
||||||
gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq
|
res = gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq
|
||||||
|
assert(res == ESP_OK);
|
||||||
|
}
|
||||||
#endif
|
#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();
|
#if defined(VEXT_ENABLE)
|
||||||
res = esp_light_sleep_start();
|
gpio_hold_en((gpio_num_t)VEXT_ENABLE);
|
||||||
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
|
#endif
|
||||||
|
|
||||||
|
#if defined(RESET_OLED)
|
||||||
|
gpio_hold_en((gpio_num_t)RESET_OLED);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(INPUTDRIVER_ENCODER_BTN)
|
#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
|
#endif
|
||||||
#if defined(WAKE_ON_TOUCH)
|
#if defined(WAKE_ON_TOUCH)
|
||||||
gpio_wakeup_disable((gpio_num_t)SCREEN_TOUCH_INT);
|
res = gpio_wakeup_enable((gpio_num_t)SCREEN_TOUCH_INT, GPIO_INTR_LOW_LEVEL);
|
||||||
#endif
|
assert(res == ESP_OK);
|
||||||
#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
|
#endif
|
||||||
|
|
||||||
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
|
res = esp_sleep_enable_gpio_wakeup();
|
||||||
notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here
|
assert(res == ESP_OK);
|
||||||
|
|
||||||
#ifdef BUTTON_PIN
|
notifyLightSleep.notifyObservers(NULL);
|
||||||
if (cause == ESP_SLEEP_WAKEUP_GPIO) {
|
|
||||||
LOG_INFO("Exit light sleep gpio: btn=%d",
|
console->flush();
|
||||||
!digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN));
|
|
||||||
} else
|
#ifdef CONFIG_PM_ENABLE
|
||||||
|
res = esp_pm_lock_release(pmHandle);
|
||||||
|
assert(res == ESP_OK);
|
||||||
#endif
|
#endif
|
||||||
{
|
pmLockAcquired = false;
|
||||||
LOG_INFO("Exit light sleep cause: %d", cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cause;
|
#ifndef CONFIG_FREERTOS_USE_TICKLESS_IDLE
|
||||||
|
esp_light_sleep_start();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pmLightSleepLock->unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// not legal on the stock android ESP build
|
// Initialize power management settings to allow light sleep
|
||||||
|
void initLightSleep()
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
{
|
{
|
||||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
|
esp_err_t res;
|
||||||
static esp_pm_config_t esp32_config; // filled with zeros because bss
|
|
||||||
|
#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
|
#else
|
||||||
static esp_pm_config_esp32_t esp32_config; // filled with zeros because bss
|
pm_config.light_sleep_enable = false;
|
||||||
#endif
|
#endif
|
||||||
#if CONFIG_IDF_TARGET_ESP32S3
|
|
||||||
esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ;
|
res = esp_pm_configure(&pm_config);
|
||||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
assert(res == ESP_OK);
|
||||||
esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ;
|
|
||||||
#elif CONFIG_IDF_TARGET_ESP32C6
|
LOG_INFO("PM config enabled - min_freq_mhz=%d, max_freq_mhz=%d, light_sleep_enable=%d", pm_config.min_freq_mhz,
|
||||||
esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ;
|
pm_config.max_freq_mhz, pm_config.light_sleep_enable);
|
||||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
#endif
|
||||||
esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ;
|
|
||||||
|
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
|
#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
|
#endif
|
||||||
esp32_config.min_freq_mhz = 20; // 10Mhz is minimum recommended
|
res = gpio_hold_en((gpio_num_t)pin);
|
||||||
esp32_config.light_sleep_enable = false;
|
assert(res == ESP_OK);
|
||||||
int rv = esp_pm_configure(&esp32_config);
|
res = gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL);
|
||||||
LOG_DEBUG("Sleep request result %x", rv);
|
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)
|
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 ||
|
return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
|
||||||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER);
|
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
|
#endif
|
||||||
|
45
src/sleep.h
45
src/sleep.h
@ -4,52 +4,47 @@
|
|||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "configuration.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
|
#ifdef HAS_PMU
|
||||||
#include "XPowersLibInterface.hpp"
|
#include "XPowersLibInterface.hpp"
|
||||||
extern XPowersLibInterface *PMU;
|
extern XPowersLibInterface *PMU;
|
||||||
#endif
|
#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 initDeepSleep();
|
||||||
|
void doDeepSleep(uint32_t msecToWake, bool skipPreflight, bool skipSaveNodeDb), cpuDeepSleep(uint32_t msecToWake);
|
||||||
|
|
||||||
void setCPUFast(bool on);
|
void setCPUFast(bool on);
|
||||||
|
|
||||||
/** return true if sleep is allowed right now */
|
// returns true if sleep is allowed right now
|
||||||
bool doPreflightSleep();
|
bool doPreflightSleep();
|
||||||
|
|
||||||
extern int bootCount;
|
extern int bootCount;
|
||||||
|
|
||||||
// is bluetooth sw currently running?
|
// called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen
|
||||||
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
|
|
||||||
extern Observable<void *> preflightSleep;
|
extern Observable<void *> 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<void *> notifyDeepSleep;
|
extern Observable<void *> 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<void *> notifyReboot;
|
extern Observable<void *> notifyReboot;
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#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<void *> notifyLightSleep;
|
extern Observable<void *> 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<esp_sleep_wakeup_cause_t> notifyLightSleepEnd;
|
extern Observable<esp_sleep_wakeup_cause_t> notifyLightSleepEnd;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void enableModemSleep();
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
void enableLoraInterrupt();
|
|
||||||
bool shouldLoraWake(uint32_t msecToWake);
|
|
||||||
#endif
|
|
Loading…
Reference in New Issue
Block a user