From 77740a1044970c727a62649324078dcd7df528a6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 21:48:53 -0500 Subject: [PATCH] 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};