diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 24fc0cabb0..058358fa67 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -1,13 +1,18 @@ import esphome.codegen as cg +from esphome.components import time import esphome.config_validation as cv from esphome import pins, automation from esphome.const import ( + CONF_HOUR, CONF_ID, + CONF_MINUTE, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_DURATION, + CONF_SECOND, CONF_SLEEP_DURATION, + CONF_TIME_ID, CONF_WAKEUP_PIN, ) @@ -112,6 +117,7 @@ CONF_TOUCH_WAKEUP = "touch_wakeup" CONF_DEFAULT = "default" CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason" CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason" +CONF_UNTIL = "until" WAKEUP_CAUSES_SCHEMA = cv.Schema( { @@ -202,13 +208,19 @@ async def to_code(config): cg.add_define("USE_DEEP_SLEEP") -DEEP_SLEEP_ENTER_SCHEMA = automation.maybe_simple_id( - { - cv.GenerateID(): cv.use_id(DeepSleepComponent), - cv.Optional(CONF_SLEEP_DURATION): cv.templatable( - cv.positive_time_period_milliseconds - ), - } +DEEP_SLEEP_ENTER_SCHEMA = cv.All( + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(DeepSleepComponent), + cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable( + cv.positive_time_period_milliseconds + ), + # Only on ESP32 due to how long the RTC on ESP8266 can stay asleep + cv.Exclusive(CONF_UNTIL, "time"): cv.All(cv.only_on_esp32, cv.time_of_day), + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + } + ), + cv.has_none_or_all_keys(CONF_UNTIL, CONF_TIME_ID), ) @@ -228,6 +240,14 @@ async def deep_sleep_enter_to_code(config, action_id, template_arg, args): if CONF_SLEEP_DURATION in config: template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32) cg.add(var.set_sleep_duration(template_)) + + if CONF_UNTIL in config: + until = config[CONF_UNTIL] + cg.add(var.set_until(until[CONF_HOUR], until[CONF_MINUTE], until[CONF_SECOND])) + + time_ = await cg.get_variable(config[CONF_TIME_ID]) + cg.add(var.set_time(time_)) + return var diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 82751b538b..1bb70e0d7e 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -1,6 +1,7 @@ #include "deep_sleep_component.h" -#include "esphome/core/log.h" +#include #include "esphome/core/application.h" +#include "esphome/core/log.h" #ifdef USE_ESP8266 #include @@ -101,6 +102,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { #endif ESP_LOGI(TAG, "Beginning Deep Sleep"); + if (this->sleep_duration_.has_value()) + ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_); App.run_safe_shutdown_hooks(); diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 057d992427..5e90d4b89d 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -9,6 +9,10 @@ #include #endif +#ifdef USE_TIME +#include "esphome/components/time/real_time_clock.h" +#endif + namespace esphome { namespace deep_sleep { @@ -116,15 +120,71 @@ template class EnterDeepSleepAction : public Action { EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {} TEMPLATABLE_VALUE(uint32_t, sleep_duration); +#ifdef USE_TIME + void set_until(uint8_t hour, uint8_t minute, uint8_t second) { + this->hour_ = hour; + this->minute_ = minute; + this->second_ = second; + } + + void set_time(time::RealTimeClock *time) { this->time_ = time; } +#endif + void play(Ts... x) override { if (this->sleep_duration_.has_value()) { this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...)); } +#ifdef USE_TIME + + if (this->hour_.has_value()) { + auto time = this->time_->now(); + const uint32_t timestamp_now = time.timestamp; + + bool after_time = false; + if (time.hour > this->hour_) { + after_time = true; + } else { + if (time.hour == this->hour_) { + if (time.minute > this->minute_) { + after_time = true; + } else { + if (time.minute == this->minute_) { + if (time.second > this->second_) { + after_time = true; + } + } + } + } + } + + time.hour = *this->hour_; + time.minute = *this->minute_; + time.second = *this->second_; + time.recalc_timestamp_utc(); + + time_t timestamp = time.timestamp; // timestamp in local time zone + + if (after_time) + timestamp += 60 * 60 * 24; + + int32_t offset = time::ESPTime::timezone_offset(); + timestamp -= offset; // Change timestamp to utc + const uint32_t ms_left = (timestamp - timestamp_now) * 1000; + this->deep_sleep_->set_sleep_duration(ms_left); + } +#endif this->deep_sleep_->begin_sleep(true); } protected: DeepSleepComponent *deep_sleep_; +#ifdef USE_TIME + optional hour_; + optional minute_; + optional second_; + + time::RealTimeClock *time_; +#endif }; template class PreventDeepSleepAction : public Action { diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 0469ba2c37..36c5f4161d 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -176,6 +176,31 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { res += this->second; this->timestamp = res; } + +int32_t ESPTime::timezone_offset() { + int32_t offset = 0; + time_t now = ::time(nullptr); + auto local = ESPTime::from_epoch_local(now); + auto utc = ESPTime::from_epoch_utc(now); + bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year; + + if (utc.minute > local.minute) { + local.minute += 60; + local.hour -= 1; + } + offset += (local.minute - utc.minute) * 60; + + if (negative) { + offset -= (utc.hour - local.hour) * 3600; + } else { + if (utc.hour > local.hour) { + local.hour += 24; + } + offset += (local.hour - utc.hour) * 3600; + } + return offset; +} + bool ESPTime::operator<(ESPTime other) { return this->timestamp < other.timestamp; } bool ESPTime::operator<=(ESPTime other) { return this->timestamp <= other.timestamp; } bool ESPTime::operator==(ESPTime other) { return this->timestamp == other.timestamp; } diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index c45deb0be5..b22c6f04d7 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -88,6 +88,8 @@ struct ESPTime { /// Convert this ESPTime instance back to a tm struct. struct tm to_c_tm(); + static int32_t timezone_offset(); + /// Increment this clock instance by one second. void increment_second(); /// Increment this clock instance by one day.