mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Allow specifying deep sleep wakup clock time (#3312)
This commit is contained in:
		| @@ -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( | ||||
| DEEP_SLEEP_ENTER_SCHEMA = cv.All( | ||||
|     automation.maybe_simple_id( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DeepSleepComponent), | ||||
|         cv.Optional(CONF_SLEEP_DURATION): cv.templatable( | ||||
|             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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include "deep_sleep_component.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include <cinttypes> | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| #include <Esp.h> | ||||
| @@ -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(); | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,10 @@ | ||||
| #include <esp_sleep.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TIME | ||||
| #include "esphome/components/time/real_time_clock.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace deep_sleep { | ||||
|  | ||||
| @@ -116,15 +120,71 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> { | ||||
|   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<uint8_t> hour_; | ||||
|   optional<uint8_t> minute_; | ||||
|   optional<uint8_t> second_; | ||||
|  | ||||
|   time::RealTimeClock *time_; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class PreventDeepSleepAction : public Action<Ts...> { | ||||
|   | ||||
| @@ -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; } | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user