DeepSleep usermod (#4190)

* DeepSleep Usermod

- sleep delay is now 1 by default, disabling sleep at powerup
- renamed bootup variable to powerup
- using delay counter for proper bootup
- changed power-up and bootup logic
- added fallback to always power-on at boot except at powerup
- fixed bug in settings page
This commit is contained in:
Damian Schneider 2025-01-04 08:07:11 +01:00 committed by GitHub
parent 6a1d3de75b
commit ae4de2782a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 321 additions and 0 deletions

View File

@ -0,0 +1,84 @@
# Deep Sleep usermod
This usermod unleashes the low power capabilities of th ESP: when you power off your LEDs (using the UI power button or a macro) the ESP will be put into deep sleep mode, reducing power consumption to a minimum.
During deep sleep the ESP is shut down completely: no WiFi, no CPU, no outputs. The only way to wake it up is to use an external signal or a button. Once it wakes from deep sleep it reboots so ***make sure to use a boot-up preset.***
# A word of warning
When you disable the WLED option 'Turn LEDs on after power up/reset' and 'DelaySleep' is set to zero the ESP will go into deep sleep directly after power-up and only start WLED after it has been woken up.
If the ESP can not be awoken from deep sleep due to a wrong configuration it has to be factory reset, disabling sleep at power-up. There is no other way to wake it up.
# Power Consumption in deep sleep
The current drawn by the ESP in deep sleep mode depends on the type and is in the range of 5uA-20uA (as in micro Amperes):
- ESP32: 10uA
- ESP32 S3: 8uA
- ESP32 S2: 20uA
- ESP32 C3: 5uA
- ESP8266: 20uA (not supported in this usermod)
However, there is usually additional components on a controller that increase the value:
- Power LED: the power LED on a ESP board draws 500uA - 1mA
- LDO: the voltage regulator also draws idle current. Depending on the type used this can be around 50uA up to 10mA (LM1117). Special low power LDOs with very low idle currents do exist
- Digital LEDs: WS2812 for example draw a current of about 1mA per LED. To make good use of this usermod it is required to power them off using MOSFETs or a Relay
For lowest power consumption, remove the Power LED and make sure your board does not use an LM1117. On a ESP32 C3 Supermini with the power LED removed (no other modifications) powered through the 5V pin I measured a current draw of 50uA in deep sleep.
# Useable GPIOs
The GPIOs that can be used to wake the ESP from deep sleep are limited. Only pins connected to the internal RTC unit can be used:
- ESP32: GPIO 0, 2, 4, 12-15, 25-39
- ESP32 S3: GPIO 0-21
- ESP32 S2: GPIO 0-21
- ESP32 C3: GPIO 0-5
- ESP8266 is not supported in this usermod
You can however use the selected wake-up pin normally in WLED, it only gets activated as a wake-up pin when your LEDs are powered down.
# Limitations
To keep this usermod simple and easy to use, it is a very basic implementation of the low-power capabilities provided by the ESP. If you need more advanced control you are welcome to implement your own version based on this usermod.
## Usermod installation
Use `#define USERMOD_DEEP_SLEEP` in wled.h or `-D USERMOD_DEEP_SLEEP` in your platformio.ini. Settings can be changed in the usermod config UI.
### Define Settings
There are five parameters you can set:
- GPIO: the pin to use for wake-up
- WakeWhen High/Low: the pin state that triggers the wake-up
- Pull-up/down disable: enable or disable the internal pullup resistors during sleep (does not affect normal use while running)
- Wake after: if set larger than 0, ESP will automatically wake-up after this many seconds (Turn LEDs on after power up/reset is overriden, it will always turn on)
- Delay sleep: if set larger than 0, ESP will not go to sleep for this many seconds after you power it off. Timer is reset when switched back on during this time.
To override the default settings, place the `#define` in wled.h or add `-D DEEPSLEEP_xxx` to your platformio_override.ini build flags
* `DEEPSLEEP_WAKEUPPIN x` - define the pin to be used for wake-up, see list of useable pins above. The pin can be used normally as a button pin in WLED.
* `DEEPSLEEP_WAKEWHENHIGH` - if defined, wakes up when pin goes high (default is low)
* `DEEPSLEEP_DISABLEPULL` - if defined, internal pullup/pulldown is disabled in deep sleep (default is ebnabled)
* `DEEPSLEEP_WAKEUPINTERVAL` - number of seconds after which a wake-up happens automatically, sooner if button is pressed. 0 = never. accuracy is about 2%
* `DEEPSLEEP_DELAY` - delay between power-off and sleep
example for env build flags:
`-D USERMOD_DEEP_SLEEP`
`-D DEEPSLEEP_WAKEUPPIN=4`
`-D DEEPSLEEP_DISABLEPULL=0` ;enable pull-up/down resistors by default
`-D DEEPSLEEP_WAKEUPINTERVAL=43200` ;wake up after 12 hours (or when button is pressed)
### Hardware Setup
To wake from deep-sleep an external trigger signal on the configured GPIO is required. When using timed-only wake-up, use a GPIO that has an on-board pull-up resistor (GPIO0 on most boards). When using push-buttons it is highly recommended to use an external pull-up resistor: not all IO's on all devices have properly working internal resistors.
Using sensors like PIR, IR, touch sensors or any other sensor with a digital output can be used instead of a button.
now go on and save some power
@dedehai
## Change log
2024-09
* Initial version
2024-10
* Changed from #define configuration to UI configuration

View File

@ -0,0 +1,227 @@
#pragma once
#include "wled.h"
#include "driver/rtc_io.h"
#ifdef ESP8266
#error The "Deep Sleep" usermod does not support ESP8266
#endif
#ifndef DEEPSLEEP_WAKEUPPIN
#define DEEPSLEEP_WAKEUPPIN 0
#endif
#ifndef DEEPSLEEP_WAKEWHENHIGH
#define DEEPSLEEP_WAKEWHENHIGH 0
#endif
#ifndef DEEPSLEEP_DISABLEPULL
#define DEEPSLEEP_DISABLEPULL 1
#endif
#ifndef DEEPSLEEP_WAKEUPINTERVAL
#define DEEPSLEEP_WAKEUPINTERVAL 0
#endif
#ifndef DEEPSLEEP_DELAY
#define DEEPSLEEP_DELAY 1
#endif
RTC_DATA_ATTR bool powerup = true; // variable in RTC data persists on a reboot
class DeepSleepUsermod : public Usermod {
private:
bool enabled = true;
bool initDone = false;
uint8_t wakeupPin = DEEPSLEEP_WAKEUPPIN;
uint8_t wakeWhenHigh = DEEPSLEEP_WAKEWHENHIGH; // wake up when pin goes high if 1, triggers on low if 0
bool noPull = true; // use pullup/pulldown resistor
int wakeupAfter = DEEPSLEEP_WAKEUPINTERVAL; // in seconds, <=0: button only
int sleepDelay = DEEPSLEEP_DELAY; // in seconds, 0 = immediate
int delaycounter = 5; // delay deep sleep at bootup until preset settings are applied
uint32_t lastLoopTime = 0;
// string that are used multiple time (this will save some flash memory)
static const char _name[];
static const char _enabled[];
bool pin_is_valid(uint8_t wakePin) {
#ifdef CONFIG_IDF_TARGET_ESP32 //ESP32: GPIOs 0,2,4, 12-15, 25-39 can be used for wake-up
if (wakePin == 0 || wakePin == 2 || wakePin == 4 || (wakePin >= 12 && wakePin <= 15) || (wakePin >= 25 && wakePin <= 27) || (wakePin >= 32 && wakePin <= 39)) {
return true;
}
#endif
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2) //ESP32 S3 & S3: GPIOs 0-21 can be used for wake-up
if (wakePin <= 21) {
return true;
}
#endif
#ifdef CONFIG_IDF_TARGET_ESP32C3 // ESP32 C3: GPIOs 0-5 can be used for wake-up
if (wakePin <= 5) {
return true;
}
#endif
DEBUG_PRINTLN(F("Error: unsupported deep sleep wake-up pin"));
return false;
}
public:
inline void enable(bool enable) { enabled = enable; } // Enable/Disable the usermod
inline bool isEnabled() { return enabled; } //Get usermod enabled/disabled state
// setup is called at boot (or in this case after every exit of sleep mode)
void setup() {
//TODO: if the de-init of RTC pins is required to do it could be done here
//rtc_gpio_deinit(wakeupPin);
initDone = true;
}
void loop() {
if (!enabled || !offMode) { // disabled or LEDs are on
lastLoopTime = 0; // reset timer
return;
}
if (sleepDelay > 0) {
if(lastLoopTime == 0) lastLoopTime = millis(); // initialize
if (millis() - lastLoopTime < sleepDelay * 1000) {
return; // wait until delay is over
}
}
if(powerup == false && delaycounter) { // delay sleep in case a preset is being loaded and turnOnAtBoot is disabled (handleIO() does enable offMode temporarily in this case)
delaycounter--;
if(delaycounter == 2 && offMode) { // force turn on, no matter the settings (device is bricked if user set sleepDelay=0, no bootup preset and turnOnAtBoot=false)
if (briS == 0) bri = 10; // turn on at low brightness
else bri = briS;
strip.setBrightness(bri); // needed to make handleIO() not turn off LEDs (really? does not help in bootup preset)
offMode = false;
applyPresetWithFallback(0, CALL_MODE_INIT, FX_MODE_STATIC, 0); // try to apply preset 0, fallback to static
if (rlyPin >= 0) {
digitalWrite(rlyPin, (rlyMde ? HIGH : LOW)); // turn relay on TODO: this should be done by wled, what function to call?
}
}
return;
}
DEBUG_PRINTLN(F("DeepSleep UM: entering deep sleep..."));
powerup = false; // turn leds on in all subsequent bootups (overrides Turn LEDs on after power up/reset' at reboot)
if(!pin_is_valid(wakeupPin)) return;
esp_err_t halerror = ESP_OK;
pinMode(wakeupPin, INPUT); // make sure GPIO is input with pullup/pulldown disabled
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); //disable all wake-up sources (just in case)
if(wakeupAfter)
esp_sleep_enable_timer_wakeup((uint64_t)wakeupAfter * (uint64_t)1e6); //sleep for x seconds
#if defined(CONFIG_IDF_TARGET_ESP32C3) // ESP32 C3
if(noPull)
gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_FLOATING);
else { // enable pullup/pulldown resistor
if(wakeWhenHigh)
gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_PULLDOWN_ONLY);
else
gpio_sleep_set_pull_mode((gpio_num_t)wakeupPin, GPIO_PULLUP_ONLY);
}
if(wakeWhenHigh)
halerror = esp_deep_sleep_enable_gpio_wakeup(1<<wakeupPin, ESP_GPIO_WAKEUP_GPIO_HIGH);
else
halerror = esp_deep_sleep_enable_gpio_wakeup(1<<wakeupPin, ESP_GPIO_WAKEUP_GPIO_LOW);
#else // ESP32, S2, S3
gpio_pulldown_dis((gpio_num_t)wakeupPin); // disable internal pull resistors for GPIO use
gpio_pullup_dis((gpio_num_t)wakeupPin);
if(noPull) {
rtc_gpio_pullup_dis((gpio_num_t)wakeupPin);
rtc_gpio_pulldown_dis((gpio_num_t)wakeupPin);
}
else { // enable pullup/pulldown resistor for RTC use
if(wakeWhenHigh)
rtc_gpio_pulldown_en((gpio_num_t)wakeupPin);
else
rtc_gpio_pullup_en((gpio_num_t)wakeupPin);
}
if(wakeWhenHigh)
halerror = esp_sleep_enable_ext0_wakeup((gpio_num_t)wakeupPin, HIGH); // only RTC pins can be used
else
halerror = esp_sleep_enable_ext0_wakeup((gpio_num_t)wakeupPin, LOW);
#endif
delay(1); // wait for pin to be ready
if(halerror == ESP_OK) esp_deep_sleep_start(); // go into deep sleep
else DEBUG_PRINTLN(F("sleep failed"));
}
//void connected() {} //unused, this is called every time the WiFi is (re)connected
void addToConfig(JsonObject& root) override
{
JsonObject top = root.createNestedObject(FPSTR(_name));
top[FPSTR(_enabled)] = enabled;
//save these vars persistently whenever settings are saved
top["gpio"] = wakeupPin;
top["wakeWhen"] = wakeWhenHigh;
top["pull"] = noPull;
top["wakeAfter"] = wakeupAfter;
top["delaySleep"] = sleepDelay;
}
bool readFromConfig(JsonObject& root) override
{
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
JsonObject top = root[FPSTR(_name)];
bool configComplete = !top.isNull();
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
configComplete &= getJsonValue(top["gpio"], wakeupPin, DEEPSLEEP_WAKEUPPIN);
if (!pin_is_valid(wakeupPin)) {
wakeupPin = 0; // set to 0 if invalid
configComplete = false; // Mark config as incomplete if pin is invalid
}
configComplete &= getJsonValue(top["wakeWhen"], wakeWhenHigh, DEEPSLEEP_WAKEWHENHIGH); // default to wake on low
configComplete &= getJsonValue(top["pull"], noPull, DEEPSLEEP_DISABLEPULL); // default to no pullup/pulldown
configComplete &= getJsonValue(top["wakeAfter"], wakeupAfter, DEEPSLEEP_WAKEUPINTERVAL);
configComplete &= getJsonValue(top["delaySleep"], sleepDelay, DEEPSLEEP_DELAY);
return configComplete;
}
/*
* appendConfigData() is called when user enters usermod settings page
* it may add additional metadata for certain entry fields (adding drop down is possible)
* be careful not to add too much as oappend() buffer is limited to 3k
*/
void appendConfigData() override
{
// dropdown for wakeupPin
oappend(SET_F("dd=addDropdown('DeepSleep','gpio');"));
for (int pin = 0; pin < 40; pin++) { // possible pins are in range 0-39
if (pin_is_valid(pin)) {
oappend(SET_F("addOption(dd,'"));
oappend(String(pin).c_str());
oappend(SET_F("',"));
oappend(String(pin).c_str());
oappend(SET_F(");"));
}
}
oappend(SET_F("dd=addDropdown('DeepSleep','wakeWhen');"));
oappend(SET_F("addOption(dd,'Low',0);"));
oappend(SET_F("addOption(dd,'High',1);"));
oappend(SET_F("addInfo('DeepSleep:pull',1,'','-up/down disable: ');")); // first string is suffix, second string is prefix
oappend(SET_F("addInfo('DeepSleep:wakeAfter',1,'seconds <i>(0 = never)<i>');"));
oappend(SET_F("addInfo('DeepSleep:delaySleep',1,'seconds <i>(0 = sleep at powerup)<i>');")); // first string is suffix, second string is prefix
}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() {
return USERMOD_ID_DEEP_SLEEP;
}
};
// add more strings here to reduce flash memory usage
const char DeepSleepUsermod::_name[] PROGMEM = "DeepSleep";
const char DeepSleepUsermod::_enabled[] PROGMEM = "enabled";

View File

@ -203,6 +203,7 @@
#define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h"
#define USERMOD_ID_POV_DISPLAY 53 //Usermod "usermod_pov_display.h"
#define USERMOD_ID_PIXELS_DICE_TRAY 54 //Usermod "pixels_dice_tray.h"
#define USERMOD_ID_DEEP_SLEEP 55 //Usermod "usermod_deep_sleep.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot

View File

@ -242,6 +242,11 @@
#include "../usermods/LD2410_v2/usermod_ld2410.h"
#endif
#ifdef USERMOD_DEEP_SLEEP
#include "../usermods/deep_sleep/usermod_deep_sleep.h"
#endif
void registerUsermods()
{
/*
@ -470,4 +475,8 @@ void registerUsermods()
#ifdef USERMOD_POV_DISPLAY
UsermodManager::add(new PovDisplayUsermod());
#endif
#ifdef USERMOD_DEEP_SLEEP
usermods.add(new DeepSleepUsermod());
#endif
}