From 7c2d2ef5a33d280ae08670340e01f1f6a2a7f67d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Jul 2025 15:53:12 -0500 Subject: [PATCH] deep_sleep: Replace polling loop with event-driven state machine --- .../deep_sleep/deep_sleep_component.cpp | 35 ++++++++++++------- .../deep_sleep/deep_sleep_component.h | 11 ++++-- .../deep_sleep/deep_sleep_esp32.cpp | 18 ++++++++-- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 84fc102b66..880db6c56b 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -36,26 +36,27 @@ void DeepSleepComponent::dump_config() { this->dump_config_platform_(); } -void DeepSleepComponent::loop() { - if (this->next_enter_deep_sleep_) - this->begin_sleep(); -} - -float DeepSleepComponent::get_loop_priority() const { - return -100.0f; // run after everything else is ready -} - void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; } void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; } void DeepSleepComponent::begin_sleep(bool manual) { - if (this->prevent_ && !manual) { - this->next_enter_deep_sleep_ = true; + if (this->sleep_state_ == SLEEP_STATE_ENTERING_SLEEP) { + // Already entering sleep, avoid re-entrance return; } + if (this->prevent_ && !manual) { + // Sleep was prevented + this->sleep_state_ = SLEEP_STATE_BLOCKED_BY_PREVENT; + ESP_LOGD(TAG, "Deep sleep blocked by prevent flag"); + return; + } + + this->sleep_state_ = SLEEP_STATE_ENTERING_SLEEP; + if (!this->prepare_to_sleep_()) { + // prepare_to_sleep_ will set appropriate blocked state return; } @@ -76,7 +77,17 @@ float DeepSleepComponent::get_setup_priority() const { return setup_priority::LA void DeepSleepComponent::prevent_deep_sleep() { this->prevent_ = true; } -void DeepSleepComponent::allow_deep_sleep() { this->prevent_ = false; } +void DeepSleepComponent::allow_deep_sleep() { + this->prevent_ = false; + // If sleep was blocked by prevent flag, try to sleep now + if (this->sleep_state_ == SLEEP_STATE_BLOCKED_BY_PREVENT) { + ESP_LOGD(TAG, "Deep sleep allowed, executing deferred sleep"); + this->sleep_state_ = SLEEP_STATE_IDLE; + // Schedule sleep for next loop iteration to avoid potential issues + // with calling begin_sleep during another component's execution + this->defer([this]() { this->begin_sleep(false); }); // false = automatic sleep (respects prevent flag) + } +} } // namespace deep_sleep } // namespace esphome diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 7a640b9ea5..c056820eed 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -93,8 +93,6 @@ class DeepSleepComponent : public Component { void setup() override; void dump_config() override; - void loop() override; - float get_loop_priority() const override; float get_setup_priority() const override; /// Helper to enter deep sleep mode @@ -124,9 +122,16 @@ class DeepSleepComponent : public Component { optional touch_wakeup_; optional wakeup_cause_to_run_duration_; #endif + enum SleepState : uint8_t { + SLEEP_STATE_IDLE, + SLEEP_STATE_BLOCKED_BY_PREVENT, + SLEEP_STATE_BLOCKED_BY_WAKEUP_PIN, + SLEEP_STATE_ENTERING_SLEEP, + }; + optional run_duration_; - bool next_enter_deep_sleep_{false}; bool prevent_{false}; + SleepState sleep_state_{SLEEP_STATE_IDLE}; }; extern bool global_has_deep_sleep; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/deep_sleep/deep_sleep_esp32.cpp b/esphome/components/deep_sleep/deep_sleep_esp32.cpp index 7965ab738a..4a15eb95f3 100644 --- a/esphome/components/deep_sleep/deep_sleep_esp32.cpp +++ b/esphome/components/deep_sleep/deep_sleep_esp32.cpp @@ -59,13 +59,27 @@ bool DeepSleepComponent::prepare_to_sleep_() { if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr && this->wakeup_pin_->digital_read()) { // Defer deep sleep until inactive - if (!this->next_enter_deep_sleep_) { + if (this->sleep_state_ != SLEEP_STATE_BLOCKED_BY_WAKEUP_PIN) { + this->sleep_state_ = SLEEP_STATE_BLOCKED_BY_WAKEUP_PIN; this->status_set_warning(); ESP_LOGW(TAG, "Waiting for wakeup pin state change"); + // Set up monitoring - check pin state every 100ms + this->set_interval("wakeup_pin_check", 100, [this]() { + if (!this->wakeup_pin_->digital_read()) { + ESP_LOGD(TAG, "Wakeup pin inactive, can now enter deep sleep"); + this->cancel_interval("wakeup_pin_check"); + this->sleep_state_ = SLEEP_STATE_IDLE; + this->begin_sleep(false); // false = automatic sleep (respects prevent flag) + } + }); } - this->next_enter_deep_sleep_ = true; return false; } + // If we were monitoring and now can sleep, clean up + if (this->sleep_state_ == SLEEP_STATE_BLOCKED_BY_WAKEUP_PIN) { + this->cancel_interval("wakeup_pin_check"); + this->sleep_state_ = SLEEP_STATE_ENTERING_SLEEP; + } return true; }