From 4f29039b41e4dbb930ef78c1524de4a087ed0a07 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 21:58:18 -0500 Subject: [PATCH] mark_loop_done --- esphome/components/anova/anova.cpp | 6 ++++- esphome/components/bedjet/bedjet_hub.cpp | 6 ++++- .../bedjet/climate/bedjet_climate.cpp | 6 ++++- .../ble_client/sensor/ble_rssi_sensor.cpp | 6 ++++- .../ble_client/sensor/ble_sensor.cpp | 6 ++++- .../text_sensor/ble_text_sensor.cpp | 6 ++++- .../esp32_improv/esp32_improv_component.cpp | 2 ++ esphome/components/safe_mode/safe_mode.cpp | 2 ++ esphome/components/sntp/sntp_component.cpp | 3 +++ esphome/core/component.cpp | 25 ++++++++++++++----- esphome/core/component.h | 14 +++++++++++ esphome/core/scheduler.cpp | 4 +-- 12 files changed, 72 insertions(+), 14 deletions(-) diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index ebf6c1d037..c8d0d27b07 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -17,7 +17,11 @@ void Anova::setup() { this->current_request_ = 0; } -void Anova::loop() {} +void Anova::loop() { + // This component uses polling via update() and BLE callbacks + // Empty loop not needed, mark as done to save CPU cycles + this->mark_loop_done(); +} void Anova::control(const ClimateCall &call) { if (call.get_mode().has_value()) { diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index 7ebed2e78d..f9b330ccc9 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -480,7 +480,11 @@ void BedJetHub::set_clock(uint8_t hour, uint8_t minute) { /* Internal */ -void BedJetHub::loop() {} +void BedJetHub::loop() { + // This component uses polling via update() and BLE callbacks + // Empty loop not needed, mark as done to save CPU cycles + this->mark_loop_done(); +} void BedJetHub::update() { this->dispatch_status_(); } void BedJetHub::dump_config() { diff --git a/esphome/components/bedjet/climate/bedjet_climate.cpp b/esphome/components/bedjet/climate/bedjet_climate.cpp index 854129f816..31880fe3ae 100644 --- a/esphome/components/bedjet/climate/bedjet_climate.cpp +++ b/esphome/components/bedjet/climate/bedjet_climate.cpp @@ -83,7 +83,11 @@ void BedJetClimate::reset_state_() { this->publish_state(); } -void BedJetClimate::loop() {} +void BedJetClimate::loop() { + // This component is controlled via the parent BedJetHub + // Empty loop not needed, mark as done to save CPU cycles + this->mark_loop_done(); +} void BedJetClimate::control(const ClimateCall &call) { ESP_LOGD(TAG, "Received BedJetClimate::control"); diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp index 81d244ce6d..8511437a4a 100644 --- a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp @@ -11,7 +11,11 @@ namespace ble_client { static const char *const TAG = "ble_rssi_sensor"; -void BLEClientRSSISensor::loop() {} +void BLEClientRSSISensor::loop() { + // This component uses polling via update() and BLE GAP callbacks + // Empty loop not needed, mark as done to save CPU cycles + this->mark_loop_done(); +} void BLEClientRSSISensor::dump_config() { LOG_SENSOR("", "BLE Client RSSI Sensor", this); diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index f91b07fee2..4bf3154e04 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -11,7 +11,11 @@ namespace ble_client { static const char *const TAG = "ble_sensor"; -void BLESensor::loop() {} +void BLESensor::loop() { + // This component uses polling via update() and BLE callbacks + // Empty loop not needed, mark as done to save CPU cycles + this->mark_loop_done(); +} void BLESensor::dump_config() { LOG_SENSOR("", "BLE Sensor", this); diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index 5083e235c6..24b8ad486a 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -14,7 +14,11 @@ static const char *const TAG = "ble_text_sensor"; static const std::string EMPTY = ""; -void BLETextSensor::loop() {} +void BLETextSensor::loop() { + // This component uses polling via update() and BLE callbacks + // Empty loop not needed, mark as done to save CPU cycles + this->mark_loop_done(); +} void BLETextSensor::dump_config() { LOG_TEXT_SENSOR("", "BLE Text Sensor", this); diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 9d84d38968..57fc1b5797 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -168,6 +168,8 @@ void ESP32ImprovComponent::loop() { case improv::STATE_PROVISIONED: { this->incoming_data_.clear(); this->set_status_indicator_state_(false); + // Provisioning complete, no further loop execution needed + this->mark_loop_done(); break; } } diff --git a/esphome/components/safe_mode/safe_mode.cpp b/esphome/components/safe_mode/safe_mode.cpp index 89c9242357..88f34beafa 100644 --- a/esphome/components/safe_mode/safe_mode.cpp +++ b/esphome/components/safe_mode/safe_mode.cpp @@ -42,6 +42,8 @@ void SafeModeComponent::loop() { ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter"); this->clean_rtc(); this->boot_successful_ = true; + // Mark loop as done since we no longer need to check + this->mark_loop_done(); } } diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index f9a9981c52..72ce972b1e 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -67,6 +67,9 @@ void SNTPComponent::loop() { time.minute, time.second); this->time_sync_callback_.call(); this->has_time_ = true; + + // Time is now synchronized, no need to check anymore + this->mark_loop_done(); } } // namespace sntp diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index dae99a0d22..84fc86609c 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -29,17 +29,18 @@ const float LATE = -100.0f; } // namespace setup_priority -// Component state uses bits 0-1 (4 states) -const uint8_t COMPONENT_STATE_MASK = 0x03; +// Component state uses bits 0-2 (8 states, 5 used) +const uint8_t COMPONENT_STATE_MASK = 0x07; const uint8_t COMPONENT_STATE_CONSTRUCTION = 0x00; const uint8_t COMPONENT_STATE_SETUP = 0x01; const uint8_t COMPONENT_STATE_LOOP = 0x02; const uint8_t COMPONENT_STATE_FAILED = 0x03; -// Status LED uses bits 2-3 -const uint8_t STATUS_LED_MASK = 0x0C; +const uint8_t COMPONENT_STATE_LOOP_DONE = 0x04; +// Status LED uses bits 3-4 +const uint8_t STATUS_LED_MASK = 0x18; const uint8_t STATUS_LED_OK = 0x00; -const uint8_t STATUS_LED_WARNING = 0x04; // Bit 2 -const uint8_t STATUS_LED_ERROR = 0x08; // Bit 3 +const uint8_t STATUS_LED_WARNING = 0x08; // Bit 3 +const uint8_t STATUS_LED_ERROR = 0x10; // Bit 4 const uint32_t WARN_IF_BLOCKING_OVER_MS = 50U; ///< Initial blocking time allowed without warning const uint32_t WARN_IF_BLOCKING_INCREMENT_MS = 10U; ///< How long the blocking time must be larger to warn again @@ -111,6 +112,9 @@ void Component::call() { case COMPONENT_STATE_FAILED: // NOLINT(bugprone-branch-clone) // State failed: Do nothing break; + case COMPONENT_STATE_LOOP_DONE: // NOLINT(bugprone-branch-clone) + // State loop done: Do nothing, component has finished its work + break; default: break; } @@ -133,6 +137,11 @@ void Component::mark_failed() { this->component_state_ |= COMPONENT_STATE_FAILED; this->status_set_error(); } +void Component::mark_loop_done() { + ESP_LOGD(TAG, "Component %s loop marked as done.", this->get_component_source()); + this->component_state_ &= ~COMPONENT_STATE_MASK; + this->component_state_ |= COMPONENT_STATE_LOOP_DONE; +} void Component::reset_to_construction_state() { if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { ESP_LOGI(TAG, "Component %s is being reset to construction state.", this->get_component_source()); @@ -169,6 +178,10 @@ bool Component::is_ready() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP || (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP; } +bool Component::should_skip_loop() const { + uint8_t state = this->component_state_ & COMPONENT_STATE_MASK; + return state == COMPONENT_STATE_FAILED || state == COMPONENT_STATE_LOOP_DONE; +} bool Component::can_proceed() { return true; } bool Component::status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; } bool Component::status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; } diff --git a/esphome/core/component.h b/esphome/core/component.h index 7ad4a5e496..123ec92814 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -58,6 +58,7 @@ extern const uint8_t COMPONENT_STATE_CONSTRUCTION; extern const uint8_t COMPONENT_STATE_SETUP; extern const uint8_t COMPONENT_STATE_LOOP; extern const uint8_t COMPONENT_STATE_FAILED; +extern const uint8_t COMPONENT_STATE_LOOP_DONE; extern const uint8_t STATUS_LED_MASK; extern const uint8_t STATUS_LED_OK; extern const uint8_t STATUS_LED_WARNING; @@ -150,10 +151,23 @@ class Component { this->mark_failed(); } + /** Mark this component's loop as done. The loop will no longer be called. + * + * This is useful for components that only need to run for a certain period of time + * and then no longer need their loop() method called, saving CPU cycles. + */ + void mark_loop_done(); + bool is_failed() const; bool is_ready() const; + /** Check if this component should skip its loop execution. + * + * @return True if the component is in FAILED or LOOP_DONE state + */ + bool should_skip_loop() const; + virtual bool can_proceed(); bool status_has_warning() const; diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index eed222c974..7d91241c72 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -211,8 +211,8 @@ void HOT Scheduler::call() { // Not reached timeout yet, done for this call break; } - // Don't run on failed components - if (item->component != nullptr && item->component->is_failed()) { + // Don't run on failed or loop-done components + if (item->component != nullptr && item->component->should_skip_loop()) { LockGuard guard{this->lock_}; this->pop_raw_(); continue;