@ -50,12 +50,23 @@ PROGMEM_STRING(None, "none");
PROGMEM_STRING ( Once , " once " ) ;
PROGMEM_STRING ( Repeat , " repeat " ) ;
static constexpr espurna : : settings : : options : : Enumeration < heartbeat : : Mode > HeartbeatModeOptions [ ] PROGMEM {
template < typename T >
using Enumeration = espurna : : settings : : options : : Enumeration < T > ;
static constexpr Enumeration < heartbeat : : Mode > HeartbeatModeOptions [ ] PROGMEM {
{ heartbeat : : Mode : : None , None } ,
{ heartbeat : : Mode : : Once , Once } ,
{ heartbeat : : Mode : : Repeat , Repeat } ,
} ;
PROGMEM_STRING ( Low , " low " ) ;
PROGMEM_STRING ( High , " high " ) ;
static constexpr Enumeration < sleep : : Interrupt > SleepInterruptOptions [ ] PROGMEM {
{ sleep : : Interrupt : : Low , Low } ,
{ sleep : : Interrupt : : High , High } ,
} ;
} // namespace options
namespace keys {
@ -73,6 +84,15 @@ PROGMEM_STRING(Password, "adminPass");
namespace settings {
namespace internal {
template < >
espurna : : sleep : : Interrupt convert ( const String & value ) {
return convert ( system : : settings : : options : : SleepInterruptOptions , value , sleep : : Interrupt : : Low ) ;
}
String serialize ( espurna : : sleep : : Interrupt value ) {
return serialize ( system : : settings : : options : : SleepInterruptOptions , value ) ;
}
template < >
espurna : : heartbeat : : Mode convert ( const String & value ) {
return convert ( system : : settings : : options : : HeartbeatModeOptions , value , heartbeat : : Mode : : Repeat ) ;
@ -96,9 +116,14 @@ std::chrono::duration<float> convert(const String& value) {
return std : : chrono : : duration < float > ( convert < float > ( value ) ) ;
}
template < typename T >
T duration_convert ( const String & value ) {
return T { convert < typename T : : rep > ( value ) } ;
}
template < >
duration : : Milliseconds convert ( const String & value ) {
return duration : : Milliseconds ( convert < duration : : Milliseconds : : rep > ( value ) ) ;
return duration_ convert < duration : : Milliseconds > ( value ) ;
}
String serialize ( espurna : : duration : : Milliseconds value ) {
@ -112,6 +137,280 @@ String serialize(espurna::duration::ClockCycles value) {
} // namespace internal
} // namespace settings
// TODO: implement 'before' and 'after' callbacks executed outside of normal loop
// TODO: flush HW UART before light sleep?
namespace sleep {
namespace {
constexpr auto DeepSleepWakeupPin = uint8_t { 16 } ;
namespace build {
static constexpr auto DefaultInterrupt = espurna : : sleep : : Interrupt : : Low ;
static constexpr auto DefaultPin = uint8_t { GPIO_NONE } ;
} // namespace build
namespace settings {
namespace keys {
PROGMEM_STRING ( Pin , " sleepPin " ) ;
PROGMEM_STRING ( Interrupt , " sleepIntr " ) ;
} // namespace keys
uint8_t pin ( ) {
return getSetting ( keys : : Pin , build : : DefaultPin ) ;
}
espurna : : sleep : : Interrupt interrupt ( ) {
return getSetting ( keys : : Interrupt , build : : DefaultInterrupt ) ;
}
} // namespace settings
// 0xFFFFFFF is a magic number per the NONOS API reference, 3.7.5 wifi_fpm_do_sleep:
// > If sleep_time_in_us is 0xFFFFFFF, the ESP8266 will sleep till be woke up as below:
// > • If wifi_fpm_set_sleep_type is set to be LIGHT_SLEEP_T, ESP8266 can wake up by GPIO.
// > • If wifi_fpm_set_sleep_type is set to be MODEM_SLEEP_T, ESP8266 can wake up by wifi_fpm_do_wakeup.
//
// In our case, both sleep modes are indefinite when OFF action is executed.
// Light sleep timed mode *can* be enabled, but effectively forces all SDK timers to be expunged.
//
// Null mode turns off radio, no need for extra code to enable MODEM sleep after switching to NULL mode.
extern " C " bool fpm_is_open ( void ) ;
extern " C " bool fpm_rf_is_closed ( void ) ;
// We have to wait for certain {F,}PM changes that happen in SDK system idle task
template < typename T >
bool wait_for_fpm ( duration : : Milliseconds timeout , T & & condition ) {
time : : blockingDelay (
timeout , duration : : Milliseconds { 1 } , condition ) ;
return condition ( ) ;
}
template < typename Condition , typename Action >
bool wait_for_fpm ( duration : : Milliseconds timeout , Condition & & condition , Action & & action ) {
if ( condition ( ) ) {
action ( ) ;
return wait_for_fpm ( timeout , std : : forward < Condition > ( condition ) ) ;
}
return false ;
}
bool forced_wakeup ( ) {
// Per API spec, we have to disable current forced PM mode to switch
// it to something else. Wait for a bit until both conditions are true
// and no idle task is attached to the system loop.
constexpr auto Timeout = duration : : Seconds { 1 } ;
const auto rf_closed = wait_for_fpm (
Timeout , fpm_rf_is_closed , wifi_fpm_do_wakeup ) ;
if ( rf_closed ) {
return false ;
}
const auto fpm_opened = wait_for_fpm (
Timeout , fpm_is_open , wifi_fpm_close ) ;
if ( fpm_opened ) {
return false ;
}
return true ;
}
// Generic PM mode that disables RF peripheral.
// We can still execute user code while it is active.
bool forced_modem_sleep ( ) {
if ( ! wifiDisabled ( ) ) {
return false ;
}
if ( ! forced_wakeup ( ) ) {
return false ;
}
wifi_fpm_set_sleep_type ( MODEM_SLEEP_T ) ;
wifi_fpm_open ( ) ;
const auto result = wifi_fpm_do_sleep ( FpmSleepIndefinite . count ( ) ) ;
if ( result ! = 0 ) {
wifi_fpm_close ( ) ;
return false ;
}
// Change *would* occur regardless, but we still notify that expected t/o happened
return wait_for_fpm (
duration : : Milliseconds { 1000 } ,
[ ] ( ) {
return ! fpm_rf_is_closed ( ) ;
} ) ;
}
// CPU + RF power saving mode.
// SDK enters IDLE task with minimal amount of operations.
// User code would not be executed while the device is asleep.
// On wakeup, execution resumes from this point.
struct FpmLightSleep {
FpmLightSleep ( ) ;
~ FpmLightSleep ( ) ;
explicit operator bool ( ) const {
return _ok ;
}
private :
bool _ok { false } ;
} ;
FpmLightSleep : : ~ FpmLightSleep ( ) {
if ( _ok ) {
wifi_fpm_close ( ) ;
}
wifi_fpm_auto_sleep_set_in_null_mode ( 1 ) ;
espurnaReload ( ) ;
forced_modem_sleep ( ) ;
}
FpmLightSleep : : FpmLightSleep ( ) {
// Unlike deep sleep option, we have to manually go over everything related
// to WiFi state management; both for our internals and SDK ones
wifi_fpm_auto_sleep_set_in_null_mode ( 0 ) ;
// Since both sleep variants are going to block user
// task, WiFi actions would be delayed until woken up
wifiDisconnect ( ) ;
wifiDisable ( ) ;
if ( ! forced_wakeup ( ) ) {
return ;
}
// ...before FPM type can be changed yet again
wifi_fpm_set_sleep_type ( LIGHT_SLEEP_T ) ;
wifi_fpm_open ( ) ;
_ok = true ;
}
bool forced_light_sleep ( sleep : : Microseconds time ) {
// Can't really do what deep sleep does without EXT wakeup source
if ( ( time < = FpmSleepMin ) | | ( time > = FpmSleepIndefinite ) ) {
return false ;
}
// Common before and after actions
FpmLightSleep sleep ;
if ( ! sleep ) {
return false ;
}
// NONOS has a quirky implementation - while RF peripheral is stopped,
// neither system or sdk tasks are. Timers are still processed,
// interrupts are happening, background tasks may still execute.
//
// Instead of doing a (fairly common) workaround that temporarily hides
// every available timer from SDK, just pretend this works as intended and
// block with software timer loop.
//
// Also ref. `ets_set_idle_cb` processing at 0x3fffdab0 (func) and 0x3fffdab4 (arg)
// (in case RF + CPU sleep and RTC peripheral communication can be replicated here)
static bool block ;
block = true ;
wifi_fpm_set_wakeup_cb ( [ ] ( ) {
block = false ;
} ) ;
const auto result = wifi_fpm_do_sleep ( time . count ( ) ) ;
if ( result = = 0 ) {
const auto Wait = std : : chrono : : duration_cast < duration : : Milliseconds > ( time ) ;
espurna : : time : : blockingDelay (
Wait ,
Wait ,
[ ] ( ) {
return block ;
} ) ;
}
wifi_fpm_close ( ) ;
return result = = 0 ;
}
bool forced_light_sleep ( uint8_t pin , Interrupt interrupt ) {
if ( pin = = DeepSleepWakeupPin ) {
return false ;
}
const auto & hardware = hardwareGpio ( ) ;
if ( ! hardware . valid ( pin ) ) {
return false ;
}
// Common before and after actions
FpmLightSleep sleep ;
if ( ! sleep ) {
return false ;
}
// TODO: does pin mode apply interrupt mask for wakeup properly?
// TODO: check whether pin is already in use?
const auto pin_mode = espurna : : gpio : : pin_mode ( pin ) ;
pinMode ( pin ,
( interrupt = = Interrupt : : Low )
? INPUT
: INPUT_PULLUP ) ;
wifi_enable_gpio_wakeup ( pin ,
( interrupt = = Interrupt : : Low )
? GPIO_PIN_INTR_LOLEVEL
: GPIO_PIN_INTR_HILEVEL ) ;
// User task is suspended for the duration of the sleep,
// delay is just a context switch so idle task does its job
const auto result = wifi_fpm_do_sleep ( FpmSleepIndefinite . count ( ) ) ;
delay ( 10 ) ;
// Restore everything back as it was before
wifi_disable_gpio_wakeup ( ) ;
pinMode ( pin , pin_mode . value ) ;
return result = = 0 ;
}
bool forced_light_sleep ( ) {
return forced_light_sleep ( settings : : pin ( ) , settings : : interrupt ( ) ) ;
}
// Extended sleep variant that disables everything but RTC memory.
// Unlike LIGHT sleep, device would be reset via RST pin to wake up.
bool deep_sleep ( sleep : : Microseconds time ) {
const auto & hardware = hardwareGpio ( ) ;
if ( hardware . lock ( DeepSleepWakeupPin ) ) {
return false ;
}
system_deep_sleep_set_option ( RF_DEFAULT ) ;
if ( system_deep_sleep ( time . count ( ) ) ) {
yield ( ) ;
return true ;
}
return false ;
}
// Force WiFi RF peripheral to power down when NULL opmode is selected
void init ( ) {
wifi_fpm_auto_sleep_set_in_null_mode ( 1 ) ;
}
} // namespace
} // namespace sleep
// -----------------------------------------------------------------------------
namespace system {
@ -272,10 +571,23 @@ bool password_equals(StringView other) {
namespace settings {
namespace query {
static constexpr std : : array < espurna : : settings : : query : : Setting , 3 > Settings PROGMEM { {
# define EXACT_VALUE(NAME, FUNC)\
String NAME ( ) { \
return espurna : : settings : : internal : : serialize ( FUNC ( ) ) ; \
}
EXACT_VALUE ( sleepPin , sleep : : settings : : pin ) ;
EXACT_VALUE ( sleepInterrupt , sleep : : settings : : interrupt ) ;
# undef EXACT_VALUE
static constexpr std : : array < espurna : : settings : : query : : Setting , 5 > Settings PROGMEM { {
{ keys : : Description , system : : description } ,
{ keys : : Hostname , system : : hostname } ,
{ keys : : Password , system : : password } ,
{ sleep : : settings : : keys : : Pin , query : : sleepPin } ,
{ sleep : : settings : : keys : : Interrupt , query : : sleepInterrupt } ,
} } ;
bool checkExact ( StringView key ) {
@ -1106,8 +1418,12 @@ void onConnected(JsonObject& root) {
}
bool onKeyCheck ( StringView key , const JsonVariant & ) {
return espurna : : settings : : query : : samePrefix ( key , STRING_VIEW ( " sys " ) )
| | espurna : : settings : : query : : samePrefix ( key , STRING_VIEW ( " hb " ) ) ;
return ( key = = system : : settings : : keys : : Description )
| | ( key = = system : : settings : : keys : : Hostname )
| | ( key = = system : : settings : : keys : : Password )
| | key . startsWith ( STRING_VIEW ( " hb " ) )
| | key . startsWith ( STRING_VIEW ( " sleep " ) )
| | key . startsWith ( STRING_VIEW ( " sys " ) ) ;
}
void init ( ) {
@ -1197,6 +1513,8 @@ void setup() {
boot : : hardware ( ) ;
boot : : customReason ( ) ;
sleep : : init ( ) ;
# if SYSTEM_CHECK_ENABLED
boot : : stability : : init ( ) ;
# endif
@ -1301,6 +1619,30 @@ String customResetReasonToPayload(CustomResetReason reason) {
return espurna : : boot : : serialize ( reason ) ;
}
bool prepareModemForcedSleep ( ) {
return espurna : : sleep : : forced_modem_sleep ( ) ;
}
bool wakeupModemForcedSleep ( ) {
return espurna : : sleep : : forced_wakeup ( ) ;
}
bool instantLightSleep ( ) {
return espurna : : sleep : : forced_light_sleep ( ) ;
}
bool instantLightSleep ( espurna : : sleep : : Microseconds time ) {
return espurna : : sleep : : forced_light_sleep ( time ) ;
}
bool instantLightSleep ( uint8_t pin , espurna : : sleep : : Interrupt interrupt ) {
return espurna : : sleep : : forced_light_sleep ( pin , interrupt ) ;
}
bool instantDeepSleep ( espurna : : sleep : : Microseconds time ) {
return espurna : : sleep : : deep_sleep ( time ) ;
}
# if SYSTEM_CHECK_ENABLED
uint8_t systemStabilityCounter ( ) {
return espurna : : boot : : internal : : persistent_data . counter ( ) ;