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 4 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
159 changes: 153 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,7 @@

#include "wled.h"
#include "driver/rtc_io.h"
#include "soc/touch_sensor_periph.h"
elanworld marked this conversation as resolved.
Show resolved Hide resolved

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

#ifndef DEEPSLEEP_WAKEUP_TOUCH_PIN
#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3
#define DEEPSLEEP_WAKEUP_TOUCH_PIN 5
elanworld marked this conversation as resolved.
Show resolved Hide resolved
#else
#define DEEPSLEEP_WAKEUP_TOUCH_PIN 15
elanworld marked this conversation as resolved.
Show resolved Hide resolved
#endif
#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 +42,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 +75,106 @@ class DeepSleepUsermod : public Usermod {
return false;
}

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

void startDeepSeelp(bool immediate) {
elanworld marked this conversation as resolved.
Show resolved Hide resolved
if (immediate) {
esp_err_t halerror = ESP_OK;
WiFi.disconnect();
WiFi.mode(WIFI_OFF); // Completely shut down the Wi-Fi module
if (enableTouchWakeup) {
touchSleepWakeUpEnable(touchPin, touchThreshold);
elanworld marked this conversation as resolved.
Show resolved Hide resolved
}
delay(2000); // wati gpio level and wifi module restore ...
if (halerror == ESP_OK)
esp_deep_sleep_start(); // go into deep sleep
else
DEBUG_PRINTLN(F("sleep failed"));
} else {
// Not to be used for now
sleepNextLoop = true;
briLast = bri;
bri = 0;
stateUpdated(CALL_MODE_DIRECT_CHANGE);
}
}

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),
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 +184,7 @@ 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);
DEBUG_PRINTF("boot type: %s\n", phase_wakeup_reason());
initDone = true;
}

Expand Down Expand Up @@ -109,8 +223,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
}
if (wakeupAfter) {
nextWakeupMin = nextWakeupMin < wakeupAfter / 60.0
? nextWakeupMin
: wakeupAfter / 60.0;
}
if (nextWakeupMin > 0) {
esp_sleep_enable_timer_wakeup(nextWakeupMin * 60ULL *
(uint64_t)1e6);
DEBUG_PRINTF("wakeup after %d minites", nextWakeupMin);
DEBUG_PRINTLN("");
}

#if defined(CONFIG_IDF_TARGET_ESP32C3) // ESP32 C3
if(noPull)
Expand Down Expand Up @@ -138,14 +265,16 @@ 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

delay(1); // wait for pin to be ready
if(halerror == ESP_OK) esp_deep_sleep_start(); // go into deep sleep
if(halerror == ESP_OK) startDeepSeelp(true); // go into deep sleep
else DEBUG_PRINTLN(F("sleep failed"));
}

Expand All @@ -159,6 +288,9 @@ void addToConfig(JsonObject& root) override
top["gpio"] = wakeupPin;
top["wakeWhen"] = wakeWhenHigh;
top["pull"] = noPull;
top["enableTouchWakeup"] = enableTouchWakeup;
top["touchPin"] = touchPin;
top["presetWake"] = presetWake;
top["wakeAfter"] = wakeupAfter;
top["delaySleep"] = sleepDelay;
}
Expand All @@ -178,6 +310,9 @@ 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
configComplete &= getJsonValue(top["enableTouchWakeup"], enableTouchWakeup);
configComplete &= getJsonValue(top["touchPin"], touchPin, DEEPSLEEP_WAKEUP_TOUCH_PIN);
configComplete &= getJsonValue(top["presetWake"], presetWake);
configComplete &= getJsonValue(top["wakeAfter"], wakeupAfter, DEEPSLEEP_WAKEUPINTERVAL);
configComplete &= getJsonValue(top["delaySleep"], sleepDelay, DEEPSLEEP_DELAY);

Expand All @@ -202,13 +337,25 @@ void addToConfig(JsonObject& root) override
oappend(SET_F(");"));
}
}
// dropdown for touch wakeupPin
touch_sensor_channel_io_map[SOC_TOUCH_SENSOR_NUM];
oappend(SET_F("dd=addDropdown('DeepSleep','touchPin');"));
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(");"));
}

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