Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add deepsleep module for battery application #4456

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion usermods/deep_sleep/readme.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 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.***
During deep sleep the ESP is shut down completely: no WiFi, no CPU, no outputs. The only way to wake it up is by using an external signal, a button, or an automation timer set to the nearest preset time. Once it wakes from deep sleep it reboots so ***make sure to use a boot-up preset.***

# A word of warning

Expand Down Expand Up @@ -61,6 +61,7 @@ To override the default settings, place the `#define` in wled.h or add `-D DEEPS
* `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
* `DEEPSLEEP_WAKEUP_TOUCH_PIN` - specify GPIO pin used for touch-based wakeup

example for env build flags:
`-D USERMOD_DEEP_SLEEP`
Expand Down
152 changes: 146 additions & 6 deletions usermods/deep_sleep/usermod_deep_sleep.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include "wled.h"
#include "driver/rtc_io.h"
#ifndef CONFIG_IDF_TARGET_ESP32C3
#include "soc/touch_sensor_periph.h"
#endif

#ifdef ESP8266
#error The "Deep Sleep" usermod does not support ESP8266
Expand All @@ -23,6 +26,9 @@
#define DEEPSLEEP_DELAY 1
#endif

#ifndef DEEPSLEEP_WAKEUP_TOUCH_PIN
#define DEEPSLEEP_WAKEUP_TOUCH_PIN -1
#endif // DEEPSLEEP_WAKEUP_TOUCH_PIN
RTC_DATA_ATTR bool powerup = true; // variable in RTC data persists on a reboot

class DeepSleepUsermod : public Usermod {
Expand All @@ -34,10 +40,15 @@ class DeepSleepUsermod : public Usermod {
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
bool enableTouchWakeup = false;
uint8_t touchPin = DEEPSLEEP_WAKEUP_TOUCH_PIN;
elanworld marked this conversation as resolved.
Show resolved Hide resolved
int wakeupAfter = DEEPSLEEP_WAKEUPINTERVAL; // in seconds, <=0: button only
bool presetWake = true; // wakeup timer for preset
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;
bool sleepNextLoop = false; // tag for next starting deep sleep

// string that are used multiple time (this will save some flash memory)
static const char _name[];
static const char _enabled[];
Expand All @@ -62,6 +73,86 @@ class DeepSleepUsermod : public Usermod {
return false;
}

#ifdef WLED_DEBUG
const char* phase_wakeup_reason() {
elanworld marked this conversation as resolved.
Show resolved Hide resolved
static char reson[20];
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch (wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0:
strcpy(reson, "RTC_IO");
break;
case ESP_SLEEP_WAKEUP_EXT1:
strcpy(reson, "RTC_CNTL");
break;
case ESP_SLEEP_WAKEUP_TIMER:
strcpy(reson, "timer");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
strcpy(reson, "touchpad");
break;
case ESP_SLEEP_WAKEUP_ULP:
strcpy(reson, "ULP");
break;
case ESP_SLEEP_WAKEUP_UNDEFINED:
strcpy(reson, "RESET");
break;
default:
snprintf(reson, sizeof(reson), "%d", wakeup_reason);
break;
}
return reson;
}
#endif

int calculateTimeDifference(int hour1, int minute1, int hour2,
int minute2) {
int totalMinutes1 = hour1 * 60 + minute1;
int totalMinutes2 = hour2 * 60 + minute2;
if (totalMinutes2 < totalMinutes1) {
totalMinutes2 += 24 * 60;
}
return totalMinutes2 - totalMinutes1;
}

int findNextTimerInterval() {
int currentHour = hour(localTime), currentMinute = minute(localTime),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a check here if the time is actually valid, not sure WLED already has a function for that but if time is not valid, this function will return bogus values.

currentWeekday = weekdayMondayFirst();
int minDifference = INT_MAX;

for (uint8_t i = 0; i < 8; i++) {
if (!(timerMacro[i] != 0 && (timerWeekday[i] & 0x01))) {
continue;
}

for (int dayOffset = 0; dayOffset < 7; dayOffset++) {
int checkWeekday = ((currentWeekday + dayOffset) % 7); // 1-7
if (checkWeekday == 0) {
checkWeekday = 7;
}

if ((timerWeekday[i] >> (checkWeekday)) & 0x01) {
if (dayOffset == 0 && (timerHours[i] < currentHour ||
(timerHours[i] == currentHour &&
timerMinutes[i] <= currentMinute))) {
continue;
}

int targetHour = timerHours[i];
int targetMinute = timerMinutes[i];
int timeDifference = calculateTimeDifference(
currentHour, currentMinute, targetHour + (dayOffset * 24),
targetMinute);

if (timeDifference < minDifference) {
minDifference = timeDifference;
}
}
}
}
return minDifference;
}

public:

inline void enable(bool enable) { enabled = enable; } // Enable/Disable the usermod
Expand All @@ -71,6 +162,9 @@ class DeepSleepUsermod : public Usermod {
void setup() {
//TODO: if the de-init of RTC pins is required to do it could be done here
//rtc_gpio_deinit(wakeupPin);
#ifdef WLED_DEBUG
DEBUG_PRINTF("boot type: %s\n", phase_wakeup_reason());
#endif
initDone = true;
}

Expand Down Expand Up @@ -109,8 +203,21 @@ class DeepSleepUsermod : public Usermod {
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
int nextWakeupMin = 0;
if (presetWake) {
nextWakeupMin = findNextTimerInterval() - 1; // wakeup before next preset
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sleep is not that accurate, if you sleep for 2 days, this can miss the preset. not sure what a good way to handle it would be. maybe wake up offset should depend on length of sleep, use datasheet accuracy values to determine the amount (I think RTC accuracy is ±2% but did not check).
also when this wake-up is used, the lights should not turn on until the preset time is reached. could also check how far off the time still is and go back to deep-deep sleep.

}
if (wakeupAfter) {
nextWakeupMin = nextWakeupMin < wakeupAfter / 60.0
? nextWakeupMin
: wakeupAfter / 60.0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a) dont use floats
b) use seconds and not minutes throughout the code: this breaks original code: if I set to 5s, it will turn to 0

}
if (nextWakeupMin > 0) {
esp_sleep_enable_timer_wakeup(nextWakeupMin * 60ULL *
(uint64_t)1e6);
DEBUG_PRINTF("wakeup after %d minites", nextWakeupMin);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

DEBUG_PRINTLN("");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just make the new line in the print above this line and remove.

}

#if defined(CONFIG_IDF_TARGET_ESP32C3) // ESP32 C3
if(noPull)
Expand Down Expand Up @@ -138,12 +245,20 @@ class DeepSleepUsermod : public Usermod {
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
if (wakeWhenHigh)
halerror = esp_sleep_enable_ext1_wakeup(1ULL << wakeupPin,
ESP_EXT1_WAKEUP_ANY_HIGH); // only RTC pins can be used
else
halerror = esp_sleep_enable_ext0_wakeup((gpio_num_t)wakeupPin, LOW);
halerror = esp_sleep_enable_ext1_wakeup(1ULL << wakeupPin,
ESP_EXT1_WAKEUP_ALL_LOW);
#endif

WiFi.disconnect();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not needed, it is shut down in deep sleep.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read that it is best practice to do this to avoid timeouts on connected clients, may be useful if sleep time is short, so keep it.

WiFi.mode(WIFI_OFF); // Completely shut down the Wi-Fi module
#ifndef CONFIG_IDF_TARGET_ESP32C3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not move this in the ifdef above?

if (enableTouchWakeup && touchPin) {
touchSleepWakeUpEnable(touchPin, touchThreshold);
}
#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"));
Expand All @@ -159,6 +274,11 @@ void addToConfig(JsonObject& root) override
top["gpio"] = wakeupPin;
top["wakeWhen"] = wakeWhenHigh;
top["pull"] = noPull;
#ifndef CONFIG_IDF_TARGET_ESP32C3
top["enableTouchWakeup"] = enableTouchWakeup;
top["touchPin"] = touchPin;
#endif
top["presetWake"] = presetWake;
top["wakeAfter"] = wakeupAfter;
top["delaySleep"] = sleepDelay;
}
Expand All @@ -178,6 +298,11 @@ void addToConfig(JsonObject& root) override
}
configComplete &= getJsonValue(top["wakeWhen"], wakeWhenHigh, DEEPSLEEP_WAKEWHENHIGH); // default to wake on low
configComplete &= getJsonValue(top["pull"], noPull, DEEPSLEEP_DISABLEPULL); // default to no pullup/pulldown
#ifndef CONFIG_IDF_TARGET_ESP32C3
configComplete &= getJsonValue(top["enableTouchWakeup"], enableTouchWakeup);
configComplete &= getJsonValue(top["touchPin"], touchPin, DEEPSLEEP_WAKEUP_TOUCH_PIN);
#endif
configComplete &= getJsonValue(top["presetWake"], presetWake);
configComplete &= getJsonValue(top["wakeAfter"], wakeupAfter, DEEPSLEEP_WAKEUPINTERVAL);
configComplete &= getJsonValue(top["delaySleep"], sleepDelay, DEEPSLEEP_DELAY);

Expand All @@ -202,13 +327,28 @@ void addToConfig(JsonObject& root) override
oappend(SET_F(");"));
}
}
#ifndef CONFIG_IDF_TARGET_ESP32C3
// dropdown for touch wakeupPin
touch_sensor_channel_io_map[SOC_TOUCH_SENSOR_NUM];
oappend(SET_F("dd=addDropdown('DeepSleep','touchPin');"));
oappend(SET_F("addOption(dd,'Unused',-1);"));
for (int pin = 0; pin < SOC_TOUCH_SENSOR_NUM; pin++) {
oappend(SET_F("addOption(dd,'"));
oappend(String(touch_sensor_channel_io_map[pin]).c_str());
oappend(SET_F("',"));
oappend(String(touch_sensor_channel_io_map[pin]).c_str());
oappend(SET_F(");"));
}
#endif

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:presetWake',1,'<i>(wake up before next preset timer)<i>');"));
oappend(SET_F("addInfo('DeepSleep:voltageCheckInterval',1,'seconds');"));
oappend(SET_F("addInfo('DeepSleep:delaySleep',1,'seconds <i>(0 = sleep at powerup)<i>');")); // first string is suffix, second string is prefix
}

Expand Down
3 changes: 2 additions & 1 deletion wled00/pin_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ enum struct PinOwner : uint8_t {
UM_LDR_DUSK_DAWN = USERMOD_ID_LDR_DUSK_DAWN, // 0x2B // Usermod "usermod_LDR_Dusk_Dawn_v2.h"
UM_MAX17048 = USERMOD_ID_MAX17048, // 0x2F // Usermod "usermod_max17048.h"
UM_BME68X = USERMOD_ID_BME68X, // 0x31 // Usermod "usermod_bme68x.h -- Uses "standard" HW_I2C pins
UM_PIXELS_DICE_TRAY = USERMOD_ID_PIXELS_DICE_TRAY // 0x35 // Usermod "pixels_dice_tray.h" -- Needs compile time specified 6 pins for display including SPI.
UM_PIXELS_DICE_TRAY = USERMOD_ID_PIXELS_DICE_TRAY, // 0x36 // Usermod "pixels_dice_tray.h" -- Needs compile time specified 6 pins for display including SPI.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert unnecessary changes

UM_DEEP_SLEEP = USERMOD_ID_DEEP_SLEEP // 0x37 // Usermod "usermod_deep_sleep.h" -- Needs pins to control pmos, nmos, other devices
};
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");

Expand Down