This commit is contained in:
m1nl 2025-07-20 23:21:57 +02:00 committed by GitHub
commit e67edba2d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 735 additions and 455 deletions

View File

@ -572,6 +572,19 @@ Power::Power() : OSThread("Power")
#ifdef DEBUG_HEAP
lastheap = memGet.getFreeHeap();
#endif
#ifdef ARCH_ESP32
lsObserver.observe(&notifyLightSleep);
lsEndObserver.observe(&notifyLightSleepEnd);
#endif
}
Power::~Power()
{
#ifdef ARCH_ESP32
lsObserver.unobserve(&notifyLightSleep);
lsEndObserver.unobserve(&notifyLightSleepEnd);
#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
*

View File

@ -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

View File

@ -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 <Fsm.h>
extern Fsm powerFSM;
extern State stateON, statePOWER, stateSERIAL, stateDARK;
extern State stateON, statePOWER, stateSERIAL, stateDARK, stateLS;
void PowerFSM_setup();
#endif
#endif

View File

@ -805,13 +805,15 @@ bool GPS::setup()
}
notifyDeepSleepObserver.observe(&notifyDeepSleep);
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(&notifyDeepSleep);
}
@ -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";

View File

@ -186,6 +186,7 @@ class GPS : private concurrency::OSThread
uint8_t numSatellites = 0;
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
CallbackObserver<GPS, void *> preflightSleepObserver = CallbackObserver<GPS, void *>(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);

View File

@ -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
}

View File

@ -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();

View File

@ -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();
}

View File

@ -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<PhoneAPI, void *> preflightSleepObserver =
CallbackObserver<PhoneAPI, void *>(this, &PhoneAPI::preflightSleepCb);
};

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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<Router, void *> preflightSleepObserver = CallbackObserver<Router, void *>(this, &Router::preflightSleepCb);
};
enum DecodeState { DECODE_SUCCESS, DECODE_FAILURE, DECODE_FATAL };

View File

@ -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);
}
}
}

View File

@ -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
#endif

View File

@ -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) {

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -109,6 +109,7 @@ class Power : private concurrency::OSThread
Observable<const meshtastic::PowerStatus *> 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<Power, void *> lsObserver = CallbackObserver<Power, void *>(this, &Power::beforeLightSleep);
CallbackObserver<Power, esp_sleep_wakeup_cause_t> lsEndObserver =
CallbackObserver<Power, esp_sleep_wakeup_cause_t>(this, &Power::afterLightSleep);
int beforeLightSleep(void *unused);
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
#endif
};
extern Power *power;

View File

@ -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 <RadioLib.h>
#include <driver/rtc_io.h>
#include <driver/uart.h>
esp_sleep_source_t wakeCause; // the reason we booted this time
#endif
#include "Throttle.h"
@ -46,11 +45,27 @@ Observable<void *> notifyDeepSleep;
Observable<void *> 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<void *> notifyLightSleep;
/// Called to tell observers that light sleep has just ended, and why it ended
Observable<esp_sleep_wakeup_cause_t> 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

View File

@ -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<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;
/// 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;
#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;
/// 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;
#endif
void enableModemSleep();
#ifdef ARCH_ESP32
void enableLoraInterrupt();
bool shouldLoraWake(uint32_t msecToWake);
#endif

View File

@ -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.

View File

@ -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

View File

@ -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
upload_speed = 921600

View File

@ -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

View File

@ -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.

View File

@ -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
#define SX126X_DIO3_TCXO_VOLTAGE 1.8

View File

@ -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
-DARDUINO_USB_MODE=0

View File

@ -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