From d0f47efae5a32e3241e2ab0ef76c62ca2f5d10d1 Mon Sep 17 00:00:00 2001 From: stefanbode Date: Tue, 28 Nov 2023 11:38:46 +0100 Subject: [PATCH] Integrate TIMERS into Deepsleep (#20117) * Integrate Timers into Deepsleep Added new functionality to controll the Deepsleep Wakeup process with TIMERS. * added 60sec initial runtime --- .../tasmota_xdrv_driver/xdrv_29_deepsleep.ino | 119 +++++++++++++++--- 1 file changed, 101 insertions(+), 18 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_29_deepsleep.ino b/tasmota/tasmota_xdrv_driver/xdrv_29_deepsleep.ino index 1867b2c41..7a4ee37d4 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_29_deepsleep.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_29_deepsleep.ino @@ -38,7 +38,7 @@ const uint32_t DEEPSLEEP_MAX = 10 * 366 * 24 * 60 * 60; // Allow max 10 years sleep const uint32_t DEEPSLEEP_MAX_CYCLE = 60 * 60; // Maximum time for a deepsleep as defined by chip hardware -const uint32_t DEEPSLEEP_MIN_TIME = 5; // Allow 5 seconds skew +const uint32_t DEEPSLEEP_MIN_TIME = 15; // Allow 15 seconds skew const uint32_t DEEPSLEEP_START_COUNTDOWN = 4; // Allow 4 seconds to update web console before deepsleep const char kDeepsleepCommands[] PROGMEM = D_PRFX_DEEPSLEEP "|" @@ -53,7 +53,7 @@ uint8_t deepsleep_flag = 0; bool DeepSleepEnabled(void) { if ((Settings->deepsleep < 10) || (Settings->deepsleep > DEEPSLEEP_MAX)) { - Settings->deepsleep = 0; // Issue #6961 + Settings->deepsleep = 0; // Issue #6961 return false; // Disabled } @@ -89,6 +89,87 @@ void DeepSleepReInit(void) RtcSettings.ultradeepsleep = 0; } +// Function to find the next relevant weekday +int8_t getNextWeekday(uint8_t currentDay, uint32_t day_bitarray, bool sameDayEventPassed) +{ + for (uint8_t daysChecked = 0; daysChecked < 7; ++daysChecked) { + uint8_t nextDay = (currentDay + daysChecked) % 7; + if ((day_bitarray & (1 << nextDay)) && (daysChecked > 0 || !sameDayEventPassed)) { + return nextDay; + } + } + return 0; +} + +// Funktion zur Berechnung der Sekunden bis zum nächsten ausgewählten Wochentag und Zeit +uint32_t calculateSecondsToTarget(uint32_t targetMinutesAfterMidnight, uint32_t day_bitarray) +{ + int32_t currentMinutesAfterMidnight = (RtcTime.hour *60) + RtcTime.minute; + + // samedayevent is a bit tricky. is wakeup is 10% before deepsleeptime this is tagged as "wakeup at time". + // e.g. deepsleep 3600 -> 360s (min) before the hour is fine and no other wakeup e.g in 6min. In this case the next + // wakeup is in 60+6min. A wakeup after the proposed time is no problem at all. + bool sameDayEventPassed = currentMinutesAfterMidnight + 1 + (RtcSettings.nextwakeup < LocalTime()?0:(Settings->deepsleep / 600)) >= targetMinutesAfterMidnight; + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DSL: passed: %d, currMin: %d, tagetMin: %d + error: %d->%d"), sameDayEventPassed, currentMinutesAfterMidnight, targetMinutesAfterMidnight, Settings->deepsleep, (RtcSettings.nextwakeup < LocalTime()?0:(Settings->deepsleep / 600))); + int8_t nextWeekday = getNextWeekday(RtcTime.day_of_week - 1, day_bitarray, sameDayEventPassed); + uint32_t secondsUntilTarget = 0; + int8_t daysUntilNextWeekday = (nextWeekday + 7 - (RtcTime.day_of_week - 1)) % 7; + + // Wenn das Zielereignis am selben Tag und bereits vorbei ist, wird der nächste mögliche Tag in der nächsten Woche berechnet + if (daysUntilNextWeekday == 0 && sameDayEventPassed) { + daysUntilNextWeekday = 7; + } + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DSL: nextDay %d, Duration %d"), + nextWeekday, daysUntilNextWeekday); + + secondsUntilTarget = daysUntilNextWeekday * 24 * 60 * 60 + (targetMinutesAfterMidnight - currentMinutesAfterMidnight) * 60; + return secondsUntilTarget; +} + +void DeepSleepCalculate() +{ + uint32_t secondsToTarget = 604800; // one week is the maximum + // Settings->deepsleep will be calculated by timers if timer is set to rule and rule is "Wakeup" + + // default add to the next wakeup. May be overwritten by "Wakeup" rule + RtcSettings.nextwakeup += Settings->deepsleep; +#if defined(USE_RULES) || defined(USE_SCRIPT) + if (RtcTime.valid) { + if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second) { TimerSetRandomWindows(); } // Midnight + if (Settings->flag3.timers_enable) { + int32_t time = (RtcTime.hour *60) + RtcTime.minute; + for (uint32_t i = 0; i < MAX_TIMERS; i++) { + Timer xtimer = Settings->timer[i]; + uint32_t day_bitarray = xtimer.days; + // day_bitarray>0 otherwhise no weekday selected + // rule keyword "Wakeup" + // Timer action: rule + if (xtimer.arm && day_bitarray && GetRule(0) == "Wakeup" && bitRead(Settings->rule_enabled, 0) && POWER_BLINK == xtimer.power) { +#ifdef USE_SUNRISE + if ((1 == xtimer.mode) || (2 == xtimer.mode)) { // Sunrise or Sunset + ApplyTimerOffsets(&xtimer); + if (xtimer.time>=2046) { continue; } + } +#endif + Settings->timer[i].arm = xtimer.repeat; + uint32_t secondsToTargetTemp = calculateSecondsToTarget(xtimer.time, day_bitarray); + secondsToTarget = tmin(secondsToTarget, secondsToTargetTemp); + } + } // loop over timers + // we found a timer 604800 == seconds in one week + if (secondsToTarget < 604800) { + Settings->deepsleep = secondsToTarget; + SettingsSaveAll(); + AddLog(LOG_LEVEL_DEBUG, PSTR("DSL: Wakeup: %ld [s]"), secondsToTarget); + RtcSettings.nextwakeup = LocalTime() + secondsToTarget - (LocalTime() % 60); + } + } + } //if (RtcTime.valid) +#endif // USE_RULES +} + + + void DeepSleepStart(void) { char stopic[TOPSZ]; @@ -104,7 +185,7 @@ void DeepSleepStart(void) RtcSettings.nextwakeup = 0; RtcSettings.deepsleep_slip = 10000; } - + // Timeslip in 0.1 seconds between the real wakeup and the calculated wakeup // Because deepsleep is in second and timeslip in 0.1 sec the compare always check if the slip is in the 10% range int32_t timeslip = (int32_t)(RtcSettings.nextwakeup + millis() / 1000 - LocalTime()) * 10; @@ -112,21 +193,21 @@ void DeepSleepStart(void) // Allow 10% of deepsleep error to count as valid deepsleep; expecting 3-4% // if more then 10% timeslip = 0 == non valid wakeup; maybe manual timeslip = (timeslip < -(int32_t)Settings->deepsleep) ? 0 : (timeslip > (int32_t)Settings->deepsleep) ? 0 : 1; + DeepSleepCalculate(); if (timeslip) { - RtcSettings.nextwakeup += Settings->deepsleep; RtcSettings.deepsleep_slip = (RtcSettings.nextwakeup - LocalTime()) * RtcSettings.deepsleep_slip / tmax((Settings->deepsleep - (millis() / 1000)),5); // Avoid crazy numbers. Again maximum 10% deviation. RtcSettings.deepsleep_slip = tmin(tmax(RtcSettings.deepsleep_slip, 9000), 11000); } -// AddLog(LOG_LEVEL_DEBUG, PSTR("DSL: Time %ld, next %ld, slip %ld"), timeslip, RtcSettings.nextwakeup, RtcSettings.deepsleep_slip ); + AddLog(LOG_LEVEL_DEBUG, PSTR("DSL: Time %ld, next %ld, slip %ld"), timeslip, RtcSettings.nextwakeup, RtcSettings.deepsleep_slip ); // It may happen that wakeup in just <5 seconds in future // In this case also add deepsleep to nextwakeup if (RtcSettings.nextwakeup <= (LocalTime() + DEEPSLEEP_MIN_TIME)) { // ensure nextwakeup is at least in the future, and add 5% RtcSettings.nextwakeup += (((LocalTime() + DEEPSLEEP_MIN_TIME - RtcSettings.nextwakeup) / Settings->deepsleep) + 1) * Settings->deepsleep; - RtcSettings.nextwakeup += Settings->deepsleep * 0.05; -// AddLog(LOG_LEVEL_DEBUG, PSTR("DSL: Time too short: time %ld, next %ld, slip %ld"), timeslip, RtcSettings.nextwakeup, RtcSettings.deepsleep_slip); + //RtcSettings.nextwakeup += Settings->deepsleep * 0.05; + //AddLog(LOG_LEVEL_DEBUG, PSTR("DSL: Time too short: time %ld, next %ld, slip %ld"), timeslip, RtcSettings.nextwakeup, RtcSettings.deepsleep_slip); } String dt = GetDT(RtcSettings.nextwakeup); // 2017-03-07T11:08:02 @@ -141,12 +222,12 @@ void DeepSleepStart(void) Response_P(PSTR("{\"" D_PRFX_DEEPSLEEP "\":{\"" D_JSON_TIME "\":\"%s\",\"" D_PRFX_DEEPSLEEP "\":%d,\"Wakeup\":%d}}"), (char*)dt.c_str(), LocalTime(), RtcSettings.nextwakeup); MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_PRFX_DEEPSLEEP), true); - WifiShutdown(); - RtcSettings.ultradeepsleep = RtcSettings.nextwakeup - LocalTime(); - RtcSettingsSave(); - RtcRebootReset(); + WifiShutdown(); + RtcSettings.ultradeepsleep = RtcSettings.nextwakeup - LocalTime(); + RtcSettingsSave(); + RtcRebootReset(); #ifdef ESP8266 - ESP.deepSleep(100 * RtcSettings.deepsleep_slip * deepsleep_sleeptime); + ESP.deepSleep(100 * RtcSettings.deepsleep_slip * deepsleep_sleeptime); #endif // ESP8266 #ifdef ESP32 esp_sleep_enable_timer_wakeup(100 * RtcSettings.deepsleep_slip * deepsleep_sleeptime); @@ -161,14 +242,15 @@ void DeepSleepEverySecond(void) { //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("Wifi Info: up %d, wifidown %d, wifistatus %d, flag %d"),TasmotaGlobal.uptime, TasmotaGlobal.global_state.wifi_down, Wifi.status , deepsleep_flag); if (DEEPSLEEP_NETWORK_TIMEOUT && TasmotaGlobal.uptime > DEEPSLEEP_NETWORK_TIMEOUT && Wifi.status != WL_CONNECTED && !deepsleep_flag && DeepSleepEnabled()) { - AddLog(LOG_LEVEL_ERROR, PSTR("Error Wifi could not connect %d seconds. Deepsleep"), DEEPSLEEP_NETWORK_TIMEOUT); + AddLog(LOG_LEVEL_ERROR, PSTR("Error Wifi no connect %d [s]. Deepsleep"), DEEPSLEEP_NETWORK_TIMEOUT); deepsleep_flag = DEEPSLEEP_START_COUNTDOWN; // Start deepsleep in 4 seconds } if (!deepsleep_flag) { return; } - + deepsleep_flag--; + AddLog(LOG_LEVEL_ERROR, PSTR("DSL: Countdown: %d"),deepsleep_flag); if (DeepSleepEnabled()) { - if (DEEPSLEEP_START_COUNTDOWN == deepsleep_flag) { + if (1 == deepsleep_flag) { SettingsSaveAll(); DeepSleepStart(); } @@ -187,7 +269,7 @@ void CmndDeepsleepTime(void) ((XdrvMailbox.payload > 10) && (XdrvMailbox.payload < DEEPSLEEP_MAX))) { Settings->deepsleep = XdrvMailbox.payload; RtcSettings.nextwakeup = 0; - deepsleep_flag = (0 == XdrvMailbox.payload) ? 0 : DEEPSLEEP_START_COUNTDOWN; + deepsleep_flag = (0 == XdrvMailbox.payload) ? 0 : DEEPSLEEP_START_COUNTDOWN + (ResetReason() != REASON_DEEP_SLEEP_AWAKE?60:0); if (deepsleep_flag) { if (!Settings->tele_period) { Settings->tele_period = TELE_PERIOD; // Need teleperiod to go back to sleep @@ -212,8 +294,9 @@ bool Xdrv29(uint32_t function) break; case FUNC_AFTER_TELEPERIOD: if (DeepSleepEnabled() && !deepsleep_flag && (Settings->tele_period == 10 || Settings->tele_period == 300 || millis() > 20000)) { - deepsleep_flag = DEEPSLEEP_START_COUNTDOWN; // Start deepsleep in 4 seconds - } + // on initial start the device will be 40 seconds awake. This allows to make changes + deepsleep_flag = DEEPSLEEP_START_COUNTDOWN + (ResetReason() != REASON_DEEP_SLEEP_AWAKE?60:0); // Start deepsleep in 4 seconds + } break; case FUNC_COMMAND: result = DecodeCommand(kDeepsleepCommands, DeepsleepCommand);