From 049c7e00cafd260fe9f2f38e11f688a00aa3b8c0 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sat, 14 Jun 2025 18:23:52 -0500 Subject: [PATCH 1/8] Move some consts to ``const.py`` (#9084) --- esphome/components/ld2410/number/__init__.py | 4 ++-- esphome/components/ld2410/sensor.py | 2 +- esphome/components/ld2410/switch/__init__.py | 2 +- esphome/components/ld2420/number/__init__.py | 4 ++-- esphome/components/ld2420/sensor/__init__.py | 8 ++++++-- esphome/components/ld2450/switch/__init__.py | 2 +- esphome/const.py | 4 ++++ 7 files changed, 17 insertions(+), 9 deletions(-) diff --git a/esphome/components/ld2410/number/__init__.py b/esphome/components/ld2410/number/__init__.py index 1f9c50db1f..ffa4e7e146 100644 --- a/esphome/components/ld2410/number/__init__.py +++ b/esphome/components/ld2410/number/__init__.py @@ -3,6 +3,8 @@ from esphome.components import number import esphome.config_validation as cv from esphome.const import ( CONF_ID, + CONF_MOVE_THRESHOLD, + CONF_STILL_THRESHOLD, CONF_TIMEOUT, DEVICE_CLASS_DISTANCE, DEVICE_CLASS_ILLUMINANCE, @@ -24,8 +26,6 @@ MaxDistanceTimeoutNumber = ld2410_ns.class_("MaxDistanceTimeoutNumber", number.N CONF_MAX_MOVE_DISTANCE_GATE = "max_move_distance_gate" CONF_MAX_STILL_DISTANCE_GATE = "max_still_distance_gate" CONF_LIGHT_THRESHOLD = "light_threshold" -CONF_STILL_THRESHOLD = "still_threshold" -CONF_MOVE_THRESHOLD = "move_threshold" TIMEOUT_GROUP = "timeout" diff --git a/esphome/components/ld2410/sensor.py b/esphome/components/ld2410/sensor.py index 38de1799cc..92245ea9a6 100644 --- a/esphome/components/ld2410/sensor.py +++ b/esphome/components/ld2410/sensor.py @@ -3,6 +3,7 @@ from esphome.components import sensor import esphome.config_validation as cv from esphome.const import ( CONF_LIGHT, + CONF_MOVING_DISTANCE, DEVICE_CLASS_DISTANCE, DEVICE_CLASS_ILLUMINANCE, ENTITY_CATEGORY_DIAGNOSTIC, @@ -17,7 +18,6 @@ from esphome.const import ( from . import CONF_LD2410_ID, LD2410Component DEPENDENCIES = ["ld2410"] -CONF_MOVING_DISTANCE = "moving_distance" CONF_STILL_DISTANCE = "still_distance" CONF_MOVING_ENERGY = "moving_energy" CONF_STILL_ENERGY = "still_energy" diff --git a/esphome/components/ld2410/switch/__init__.py b/esphome/components/ld2410/switch/__init__.py index aecad606be..71b8a40a29 100644 --- a/esphome/components/ld2410/switch/__init__.py +++ b/esphome/components/ld2410/switch/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import switch import esphome.config_validation as cv from esphome.const import ( + CONF_BLUETOOTH, DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG, ICON_BLUETOOTH, @@ -14,7 +15,6 @@ BluetoothSwitch = ld2410_ns.class_("BluetoothSwitch", switch.Switch) EngineeringModeSwitch = ld2410_ns.class_("EngineeringModeSwitch", switch.Switch) CONF_ENGINEERING_MODE = "engineering_mode" -CONF_BLUETOOTH = "bluetooth" CONFIG_SCHEMA = { cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), diff --git a/esphome/components/ld2420/number/__init__.py b/esphome/components/ld2420/number/__init__.py index 1558243cc2..a2637b7b06 100644 --- a/esphome/components/ld2420/number/__init__.py +++ b/esphome/components/ld2420/number/__init__.py @@ -3,6 +3,8 @@ from esphome.components import number import esphome.config_validation as cv from esphome.const import ( CONF_ID, + CONF_MOVE_THRESHOLD, + CONF_STILL_THRESHOLD, DEVICE_CLASS_DISTANCE, ENTITY_CATEGORY_CONFIG, ICON_MOTION_SENSOR, @@ -31,8 +33,6 @@ LD2420StillThresholdNumbers = ld2420_ns.class_( ) CONF_MIN_GATE_DISTANCE = "min_gate_distance" CONF_MAX_GATE_DISTANCE = "max_gate_distance" -CONF_STILL_THRESHOLD = "still_threshold" -CONF_MOVE_THRESHOLD = "move_threshold" CONF_GATE_MOVE_SENSITIVITY = "gate_move_sensitivity" CONF_GATE_STILL_SENSITIVITY = "gate_still_sensitivity" CONF_GATE_SELECT = "gate_select" diff --git a/esphome/components/ld2420/sensor/__init__.py b/esphome/components/ld2420/sensor/__init__.py index e39ca99ae1..6dde35753a 100644 --- a/esphome/components/ld2420/sensor/__init__.py +++ b/esphome/components/ld2420/sensor/__init__.py @@ -1,13 +1,17 @@ import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv -from esphome.const import CONF_ID, DEVICE_CLASS_DISTANCE, UNIT_CENTIMETER +from esphome.const import ( + CONF_ID, + CONF_MOVING_DISTANCE, + DEVICE_CLASS_DISTANCE, + UNIT_CENTIMETER, +) from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns LD2420Sensor = ld2420_ns.class_("LD2420Sensor", sensor.Sensor, cg.Component) -CONF_MOVING_DISTANCE = "moving_distance" CONF_GATE_ENERGY = "gate_energy" CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/ld2450/switch/__init__.py b/esphome/components/ld2450/switch/__init__.py index fb3969cf50..2d76b75781 100644 --- a/esphome/components/ld2450/switch/__init__.py +++ b/esphome/components/ld2450/switch/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import switch import esphome.config_validation as cv from esphome.const import ( + CONF_BLUETOOTH, DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG, ICON_BLUETOOTH, @@ -13,7 +14,6 @@ from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns BluetoothSwitch = ld2450_ns.class_("BluetoothSwitch", switch.Switch) MultiTargetSwitch = ld2450_ns.class_("MultiTargetSwitch", switch.Switch) -CONF_BLUETOOTH = "bluetooth" CONF_MULTI_TARGET = "multi_target" CONFIG_SCHEMA = { diff --git a/esphome/const.py b/esphome/const.py index c01f30c3ff..69d75c81ce 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -89,6 +89,7 @@ CONF_BIT_DEPTH = "bit_depth" CONF_BITS_PER_SAMPLE = "bits_per_sample" CONF_BLOCK = "block" CONF_BLUE = "blue" +CONF_BLUETOOTH = "bluetooth" CONF_BOARD = "board" CONF_BOARD_FLASH_MODE = "board_flash_mode" CONF_BORDER = "border" @@ -527,7 +528,9 @@ CONF_MONTH = "month" CONF_MONTHS = "months" CONF_MOSI_PIN = "mosi_pin" CONF_MOTION = "motion" +CONF_MOVE_THRESHOLD = "move_threshold" CONF_MOVEMENT_COUNTER = "movement_counter" +CONF_MOVING_DISTANCE = "moving_distance" CONF_MQTT = "mqtt" CONF_MQTT_ID = "mqtt_id" CONF_MULTIPLE = "multiple" @@ -835,6 +838,7 @@ CONF_STEP = "step" CONF_STEP_DELAY = "step_delay" CONF_STEP_MODE = "step_mode" CONF_STEP_PIN = "step_pin" +CONF_STILL_THRESHOLD = "still_threshold" CONF_STOP = "stop" CONF_STOP_ACTION = "stop_action" CONF_STORE_BASELINE = "store_baseline" From dcfe7af9d3a1d11606f6538e1181d9f6b48fbd14 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 21:44:45 -0500 Subject: [PATCH 2/8] Make ParseOnOffState enum uint8_t (#9083) --- esphome/core/helpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 7d25e7d261..477f260bf0 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -438,7 +438,7 @@ template::value, int> = 0> std::stri } /// Return values for parse_on_off(). -enum ParseOnOffState { +enum ParseOnOffState : uint8_t { PARSE_NONE = 0, PARSE_ON, PARSE_OFF, From 374c33e8dc1ac29a2960e45d144e7124df51b4f4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 21:48:53 -0500 Subject: [PATCH 3/8] Optimize Component and Application state storage from uint32_t to uint8_t (#9082) --- .../components/bme280_base/bme280_base.cpp | 5 +-- esphome/components/kmeteriso/kmeteriso.cpp | 5 +-- .../status_led/light/status_led_light.cpp | 4 +- .../status_led/light/status_led_light.h | 2 +- esphome/components/weikai/weikai.cpp | 2 +- esphome/core/application.cpp | 4 +- esphome/core/application.h | 4 +- esphome/core/component.cpp | 36 +++++++++++------ esphome/core/component.h | 39 +++++++++++++------ 9 files changed, 65 insertions(+), 36 deletions(-) diff --git a/esphome/components/bme280_base/bme280_base.cpp b/esphome/components/bme280_base/bme280_base.cpp index 142a03fe1c..d2524e5aac 100644 --- a/esphome/components/bme280_base/bme280_base.cpp +++ b/esphome/components/bme280_base/bme280_base.cpp @@ -93,9 +93,8 @@ void BME280Component::setup() { // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component. - if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { - this->component_state_ &= ~COMPONENT_STATE_MASK; - this->component_state_ |= COMPONENT_STATE_CONSTRUCTION; + if (this->is_failed()) { + this->reset_to_construction_state(); } if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) { diff --git a/esphome/components/kmeteriso/kmeteriso.cpp b/esphome/components/kmeteriso/kmeteriso.cpp index b3fbc31fe6..714df0b538 100644 --- a/esphome/components/kmeteriso/kmeteriso.cpp +++ b/esphome/components/kmeteriso/kmeteriso.cpp @@ -19,9 +19,8 @@ void KMeterISOComponent::setup() { // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component. - if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { - this->component_state_ &= ~COMPONENT_STATE_MASK; - this->component_state_ |= COMPONENT_STATE_CONSTRUCTION; + if (this->is_failed()) { + this->reset_to_construction_state(); } auto err = this->bus_->writev(this->address_, nullptr, 0); diff --git a/esphome/components/status_led/light/status_led_light.cpp b/esphome/components/status_led/light/status_led_light.cpp index 6d38833ebd..dc4820f6da 100644 --- a/esphome/components/status_led/light/status_led_light.cpp +++ b/esphome/components/status_led/light/status_led_light.cpp @@ -9,10 +9,10 @@ namespace status_led { static const char *const TAG = "status_led"; void StatusLEDLightOutput::loop() { - uint32_t new_state = App.get_app_state() & STATUS_LED_MASK; + uint8_t new_state = App.get_app_state() & STATUS_LED_MASK; if (new_state != this->last_app_state_) { - ESP_LOGV(TAG, "New app state 0x%08" PRIX32, new_state); + ESP_LOGV(TAG, "New app state 0x%02X", new_state); } if ((new_state & STATUS_LED_ERROR) != 0u) { diff --git a/esphome/components/status_led/light/status_led_light.h b/esphome/components/status_led/light/status_led_light.h index e711a2e749..bfa144526a 100644 --- a/esphome/components/status_led/light/status_led_light.h +++ b/esphome/components/status_led/light/status_led_light.h @@ -36,7 +36,7 @@ class StatusLEDLightOutput : public light::LightOutput, public Component { GPIOPin *pin_{nullptr}; output::BinaryOutput *output_{nullptr}; light::LightState *lightstate_{}; - uint32_t last_app_state_{0xFFFF}; + uint8_t last_app_state_{0xFF}; void output_state_(bool state); }; diff --git a/esphome/components/weikai/weikai.cpp b/esphome/components/weikai/weikai.cpp index 2211fc77d5..ebe987cc65 100644 --- a/esphome/components/weikai/weikai.cpp +++ b/esphome/components/weikai/weikai.cpp @@ -102,7 +102,7 @@ WeikaiRegister &WeikaiRegister::operator|=(uint8_t value) { // The WeikaiComponent methods /////////////////////////////////////////////////////////////////////////////// void WeikaiComponent::loop() { - if ((this->component_state_ & COMPONENT_STATE_MASK) != COMPONENT_STATE_LOOP) + if (!this->is_in_loop_state()) return; // If there are some bytes in the receive FIFO we transfers them to the ring buffers diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 87e6f33e04..4ed96f7300 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -66,7 +66,7 @@ void Application::setup() { [](Component *a, Component *b) { return a->get_loop_priority() > b->get_loop_priority(); }); do { - uint32_t new_app_state = STATUS_LED_WARNING; + uint8_t new_app_state = STATUS_LED_WARNING; this->scheduler.call(); this->feed_wdt(); for (uint32_t j = 0; j <= i; j++) { @@ -87,7 +87,7 @@ void Application::setup() { this->calculate_looping_components_(); } void Application::loop() { - uint32_t new_app_state = 0; + uint8_t new_app_state = 0; this->scheduler.call(); diff --git a/esphome/core/application.h b/esphome/core/application.h index 6c09b25590..d9ef4fe036 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -332,7 +332,7 @@ class Application { */ void teardown_components(uint32_t timeout_ms); - uint32_t get_app_state() const { return this->app_state_; } + uint8_t get_app_state() const { return this->app_state_; } #ifdef USE_BINARY_SENSOR const std::vector &get_binary_sensors() { return this->binary_sensors_; } @@ -653,7 +653,7 @@ class Application { uint32_t last_loop_{0}; uint32_t loop_interval_{16}; size_t dump_config_at_{SIZE_MAX}; - uint32_t app_state_{0}; + uint8_t app_state_{0}; Component *current_component_{nullptr}; uint32_t loop_component_start_time_{0}; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 1141e4067d..dae99a0d22 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -29,15 +29,17 @@ const float LATE = -100.0f; } // namespace setup_priority -const uint32_t COMPONENT_STATE_MASK = 0xFF; -const uint32_t COMPONENT_STATE_CONSTRUCTION = 0x00; -const uint32_t COMPONENT_STATE_SETUP = 0x01; -const uint32_t COMPONENT_STATE_LOOP = 0x02; -const uint32_t COMPONENT_STATE_FAILED = 0x03; -const uint32_t STATUS_LED_MASK = 0xFF00; -const uint32_t STATUS_LED_OK = 0x0000; -const uint32_t STATUS_LED_WARNING = 0x0100; -const uint32_t STATUS_LED_ERROR = 0x0200; +// Component state uses bits 0-1 (4 states) +const uint8_t COMPONENT_STATE_MASK = 0x03; +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 STATUS_LED_OK = 0x00; +const uint8_t STATUS_LED_WARNING = 0x04; // Bit 2 +const uint8_t STATUS_LED_ERROR = 0x08; // Bit 3 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 @@ -86,9 +88,9 @@ void Component::call_dump_config() { } } -uint32_t Component::get_component_state() const { return this->component_state_; } +uint8_t Component::get_component_state() const { return this->component_state_; } void Component::call() { - uint32_t state = this->component_state_ & COMPONENT_STATE_MASK; + uint8_t state = this->component_state_ & COMPONENT_STATE_MASK; switch (state) { case COMPONENT_STATE_CONSTRUCTION: // State Construction: Call setup and set state to setup @@ -131,6 +133,18 @@ void Component::mark_failed() { this->component_state_ |= COMPONENT_STATE_FAILED; this->status_set_error(); } +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()); + this->component_state_ &= ~COMPONENT_STATE_MASK; + this->component_state_ |= COMPONENT_STATE_CONSTRUCTION; + // Clear error status when resetting + this->status_clear_error(); + } +} +bool Component::is_in_loop_state() const { + return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP; +} void Component::defer(std::function &&f) { // NOLINT App.scheduler.set_timeout(this, "", 0, std::move(f)); } diff --git a/esphome/core/component.h b/esphome/core/component.h index ce9f0289d0..7ad4a5e496 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -53,15 +53,15 @@ static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL; ESP_LOGCONFIG(TAG, " Update Interval: %.1fs", this->get_update_interval() / 1000.0f); \ } -extern const uint32_t COMPONENT_STATE_MASK; -extern const uint32_t COMPONENT_STATE_CONSTRUCTION; -extern const uint32_t COMPONENT_STATE_SETUP; -extern const uint32_t COMPONENT_STATE_LOOP; -extern const uint32_t COMPONENT_STATE_FAILED; -extern const uint32_t STATUS_LED_MASK; -extern const uint32_t STATUS_LED_OK; -extern const uint32_t STATUS_LED_WARNING; -extern const uint32_t STATUS_LED_ERROR; +extern const uint8_t COMPONENT_STATE_MASK; +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 STATUS_LED_MASK; +extern const uint8_t STATUS_LED_OK; +extern const uint8_t STATUS_LED_WARNING; +extern const uint8_t STATUS_LED_ERROR; enum class RetryResult { DONE, RETRY }; @@ -123,7 +123,19 @@ class Component { */ virtual void on_powerdown() {} - uint32_t get_component_state() const; + uint8_t get_component_state() const; + + /** Reset this component back to the construction state to allow setup to run again. + * + * This can be used by components that have recoverable failures to attempt setup again. + */ + void reset_to_construction_state(); + + /** Check if this component has completed setup and is in the loop state. + * + * @return True if in loop state, false otherwise. + */ + bool is_in_loop_state() const; /** Mark this component as failed. Any future timeouts/intervals/setup/loop will no longer be called. * @@ -298,7 +310,12 @@ class Component { /// Cancel a defer callback using the specified name, name must not be empty. bool cancel_defer(const std::string &name); // NOLINT - uint32_t component_state_{0x0000}; ///< State of this component. + /// State of this component - each bit has a purpose: + /// Bits 0-1: Component state (0x00=CONSTRUCTION, 0x01=SETUP, 0x02=LOOP, 0x03=FAILED) + /// Bit 2: STATUS_LED_WARNING + /// Bit 3: STATUS_LED_ERROR + /// Bits 4-7: Unused - reserved for future expansion (50% of the bits are free) + uint8_t component_state_{0x00}; float setup_priority_override_{NAN}; const char *component_source_{nullptr}; uint32_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_MS}; From a1e4143600a86ff6fd60633b0e8c43beae7e5d21 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 21:55:03 -0500 Subject: [PATCH 4/8] Small optimizations to api buffer helper (#9071) --- esphome/components/api/api_connection.h | 40 +++++++++++-------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 34c7dcd880..13e6066788 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -240,8 +240,8 @@ class APIConnection : public APIServerConnection { // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext) // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext) shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size()); - // Insert header padding bytes so message encoding starts at the correct position - shared_buf.insert(shared_buf.begin(), header_padding, 0); + // Resize to add header padding so message encoding starts at the correct position + shared_buf.resize(header_padding); return {&shared_buf}; } @@ -249,32 +249,26 @@ class APIConnection : public APIServerConnection { ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) { // Get reference to shared buffer (it maintains state between batch messages) std::vector &shared_buf = this->parent_->get_shared_buffer_ref(); - size_t current_size = shared_buf.size(); if (is_first_message) { - // For first message, initialize buffer with header padding - uint8_t header_padding = this->helper_->frame_header_padding(); shared_buf.clear(); - shared_buf.reserve(message_size + header_padding); - shared_buf.resize(header_padding); - // Fill header padding with zeros - std::fill(shared_buf.begin(), shared_buf.end(), 0); - } else { - // For subsequent messages, add footer space for previous message and header for this message - uint8_t footer_size = this->helper_->frame_footer_size(); - uint8_t header_padding = this->helper_->frame_header_padding(); - - // Reserve additional space for everything - shared_buf.reserve(current_size + footer_size + header_padding + message_size); - - // Single resize to add both footer and header padding - size_t new_size = current_size + footer_size + header_padding; - shared_buf.resize(new_size); - - // Fill the newly added bytes with zeros (footer + header padding) - std::fill(shared_buf.begin() + current_size, shared_buf.end(), 0); } + size_t current_size = shared_buf.size(); + + // Calculate padding to add: + // - First message: just header padding + // - Subsequent messages: footer for previous message + header padding for this message + size_t padding_to_add = is_first_message + ? this->helper_->frame_header_padding() + : this->helper_->frame_header_padding() + this->helper_->frame_footer_size(); + + // Reserve space for padding + message + shared_buf.reserve(current_size + padding_to_add + message_size); + + // Resize to add the padding bytes + shared_buf.resize(current_size + padding_to_add); + return {&shared_buf}; } From 4f29039b41e4dbb930ef78c1524de4a087ed0a07 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 21:58:18 -0500 Subject: [PATCH 5/8] 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; From 183dd74f3e5449aab96c3ef8e9c02ff2e02fb4ca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 22:04:33 -0500 Subject: [PATCH 6/8] one more --- esphome/components/preferences/syncer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/preferences/syncer.h b/esphome/components/preferences/syncer.h index 8976a1fe15..93a8cff371 100644 --- a/esphome/components/preferences/syncer.h +++ b/esphome/components/preferences/syncer.h @@ -12,6 +12,8 @@ class IntervalSyncer : public Component { void setup() override { if (this->write_interval_ != 0) { set_interval(this->write_interval_, []() { global_preferences->sync(); }); + // When using interval-based syncing, we don't need the loop + this->mark_loop_done(); } } void loop() override { From 8fb385666554f5e30b37db7f3ab6fbce5ccdae1d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 22:09:19 -0500 Subject: [PATCH 7/8] small fix --- esphome/components/sntp/sntp_component.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 72ce972b1e..ab02720dd9 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -68,8 +68,11 @@ void SNTPComponent::loop() { this->time_sync_callback_.call(); this->has_time_ = true; +#ifdef USE_ESP_IDF + // On ESP-IDF, time sync is permanent and update() doesn't force resync // Time is now synchronized, no need to check anymore this->mark_loop_done(); +#endif } } // namespace sntp From 7ddf51bb5190a35da339855d9661c40269eec54d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 22:36:29 -0500 Subject: [PATCH 8/8] fix --- esphome/core/application.cpp | 5 +++++ esphome/core/scheduler.cpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 4ed96f7300..9dda32f0e6 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -98,6 +98,11 @@ void Application::loop() { this->feed_wdt(last_op_end_time); for (Component *component : this->looping_components_) { + // Skip components that are done or failed + if (component->should_skip_loop()) { + continue; + } + // Update the cached time before each component runs this->loop_component_start_time_ = last_op_end_time; diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 7d91241c72..eed222c974 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 or loop-done components - if (item->component != nullptr && item->component->should_skip_loop()) { + // Don't run on failed components + if (item->component != nullptr && item->component->is_failed()) { LockGuard guard{this->lock_}; this->pop_raw_(); continue;