#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 #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 #ifndef DEEPSLEEP_WAKEUP_TOUCH_PIN #define DEEPSLEEP_WAKEUP_TOUCH_PIN 1 #endif RTC_DATA_ATTR bool powerup = true; // this is first boot after power cycle. note: variable in RTC data persists on a reboot RTC_DATA_ATTR uint8_t wakeupPreset = 0; // preset to apply after deep sleep wakeup (0 = none), set to timer macro preset class DeepSleepUsermod : public Usermod { private: bool enabled = false; // do not enable by default 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 bool enableTouchWakeup = false; uint8_t touchPin = DEEPSLEEP_WAKEUP_TOUCH_PIN; 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 = 10; // delay deep sleep at bootup until preset settings are applied, force wake up if offmode persists after bootup 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. note: input-only GPIOs 34-39 do not have internal pull resistors 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; } // functions to calculate time difference between now and next scheduled timer event 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() { if (toki.getTimeSource() == TOKI_TS_NONE) { DEBUG_PRINTLN("DeepSleep: local time not yet synchronized, skipping timer check."); return -1; } int currentHour = hour(localTime); int currentMinute = minute(localTime); int currentWeekday = weekdayMondayFirst(); // 1=Monday ... 7=Sunday int minDifference = INT_MAX; for (uint8_t i = 0; i < 8; i++) { // check if timer is enabled and date is in range, also wakes up if no macro is used if ((timerWeekday[i] & 0x01) && isTodayInDateRange(((timerMonth[i] >> 4) & 0x0F), timerDay[i], timerMonth[i] & 0x0F, timerDayEnd[i])) { // if timer is enabled (bit0 of timerWeekday) and date is in range, check all weekdays it is set for for (int dayOffset = 0; dayOffset < 7; dayOffset++) { int checkWeekday = ((currentWeekday + dayOffset) % 7); // 1-7, check all weekdays starting from today if (checkWeekday == 0) { checkWeekday = 7; // sunday is 7 not 0 } int targetHour = timerHours[i]; int targetMinute = timerMinutes[i]; if ((timerWeekday[i] >> (checkWeekday)) & 0x01) { if (dayOffset == 0 && (targetHour < currentHour || (targetHour == currentHour && targetMinute <= currentMinute))) continue; // skip if time has already passed today int timeDifference = calculateTimeDifference(currentHour, currentMinute, targetHour + (dayOffset * 24), targetMinute); if (timeDifference < minDifference) { minDifference = timeDifference; wakeupPreset = timerMacro[i]; } } } } } return minDifference; } 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); #ifdef WLED_DEBUG DEBUG_PRINTF("sleep wakeup cause: %d\n", esp_sleep_get_wakeup_cause()); #endif if (esp_sleep_get_wakeup_cause() != ESP_SLEEP_WAKEUP_TIMER) wakeupPreset = 0; // not a timed wakeup, don't apply preset initDone = true; } void loop() { if (!enabled) return; if (!offMode) { // LEDs are on lastLoopTime = 0; // reset timer if (delaycounter) delaycounter--; // decrease delay counter if LEDs are on (they are always turned on after a wake-up, see below) else if (wakeupPreset) applyPreset(wakeupPreset); // apply preset if set, this ensures macro is applied even if we missed the wake-up time return; } if (sleepDelay > 0) { powerup = false; // disable "safety" powerup sleep if delay is set if (lastLoopTime == 0) lastLoopTime = millis(); // initialize if (millis() - lastLoopTime < sleepDelay * 1000) return; // wait until delay is over } else if (powerup && delaycounter) { delaycounter--; // on first boot without sleepDelay set, do not force-turn on delay(1000); // just in case: give user a short ~10s window to turn LEDs on in UI (delaycounter is 10 by default) return; } 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 == 1 && 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 and set low brightness to avoid automatic turn off 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) uint32_t wakeupAfterSec = 0; if (presetWake) { int nextInterval = findNextTimerInterval(); if (nextInterval > 1 && nextInterval < INT_MAX) wakeupAfterSec = (nextInterval - 1) * 60; // wakeup before next preset } if (wakeupAfter > 0) { // user-defined interval if (wakeupAfterSec == 0 || (uint32_t)wakeupAfter < wakeupAfterSec) { wakeupAfterSec = wakeupAfter; } } if (wakeupAfterSec > 0) { esp_sleep_enable_timer_wakeup(wakeupAfterSec * (uint64_t)1e6); DEBUG_PRINTF("wakeup after %d seconds\n", wakeupAfterSec); } #if defined(CONFIG_IDF_TARGET_ESP32C3) // ESP32 C3 gpio_hold_dis((gpio_num_t)wakeupPin); // disable hold and configure pin if (wakeWhenHigh) halerror = esp_deep_sleep_enable_gpio_wakeup(1<= 0) { oappend(SET_F("addOption(dd,'")); oappend(String(touch_sensor_channel_io_map[touchchannel]).c_str()); oappend(SET_F("',")); oappend(String(touch_sensor_channel_io_map[touchchannel]).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 (0 = never)');")); oappend(SET_F("addInfo('DeepSleep:presetWake',1,'(wake up before next preset timer)');")); oappend(SET_F("addInfo('DeepSleep:delaySleep',1,'seconds (0 = sleep at powerup)');")); // 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"; static DeepSleepUsermod deep_sleep; REGISTER_USERMOD(deep_sleep);