From 7abb6d499870ef418b27bbd63e3281aff85eb11c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Nov 2025 17:34:08 -0600 Subject: [PATCH] [core] Implement Global Controller Registry to reduce RAM usage (#11772) --- .../alarm_control_panel.cpp | 9 +- esphome/components/api/__init__.py | 3 + esphome/components/api/api_server.cpp | 33 +-- esphome/components/api/api_server.h | 14 +- .../binary_sensor/binary_sensor.cpp | 5 + esphome/components/climate/climate.cpp | 5 + esphome/components/cover/cover.cpp | 7 + esphome/components/datetime/date_entity.cpp | 6 +- .../components/datetime/datetime_entity.cpp | 6 +- esphome/components/datetime/time_entity.cpp | 6 +- esphome/components/event/event.cpp | 6 +- esphome/components/fan/fan.cpp | 5 + esphome/components/light/light_state.cpp | 14 +- esphome/components/lock/lock.cpp | 5 + .../components/media_player/media_player.cpp | 10 +- esphome/components/number/number.cpp | 5 + esphome/components/select/select.cpp | 5 + esphome/components/sensor/sensor.cpp | 5 + esphome/components/switch/switch.cpp | 5 + esphome/components/text/text.cpp | 5 + .../components/text_sensor/text_sensor.cpp | 5 + esphome/components/update/update_entity.cpp | 6 +- esphome/components/valve/valve.cpp | 5 + esphome/components/web_server/__init__.py | 3 + esphome/components/web_server/web_server.cpp | 54 +++- esphome/components/web_server/web_server.h | 14 +- esphome/core/__init__.py | 8 + esphome/core/config.py | 17 +- esphome/core/controller.cpp | 134 ---------- esphome/core/controller.h | 15 +- esphome/core/controller_registry.cpp | 114 ++++++++ esphome/core/controller_registry.h | 245 ++++++++++++++++++ esphome/core/defines.h | 2 + 33 files changed, 583 insertions(+), 198 deletions(-) delete mode 100644 esphome/core/controller.cpp create mode 100644 esphome/core/controller_registry.cpp create mode 100644 esphome/core/controller_registry.h diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index 9f1485ee90..c29e02c8ef 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -1,6 +1,8 @@ -#include - #include "alarm_control_panel.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" + +#include #include "esphome/core/application.h" #include "esphome/core/helpers.h" @@ -34,6 +36,9 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) { LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state))); this->current_state_ = state; this->state_callback_.call(); +#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_alarm_control_panel_update(this); +#endif if (state == ACP_STATE_TRIGGERED) { this->triggered_callback_.call(); } else if (state == ACP_STATE_ARMING) { diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 7f69a9fda1..a9286c531f 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -245,6 +245,9 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + # Track controller registration for StaticVector sizing + CORE.register_controller() + cg.add(var.set_port(config[CONF_PORT])) if config[CONF_PASSWORD]: cg.add_define("USE_API_PASSWORD") diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index e5f0d9795e..18601d74ff 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -5,6 +5,7 @@ #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -34,7 +35,7 @@ APIServer::APIServer() { } void APIServer::setup() { - this->setup_controller(); + ControllerRegistry::register_controller(this); #ifdef USE_API_NOISE uint32_t hash = 88491486UL; @@ -269,7 +270,7 @@ bool APIServer::check_password(const uint8_t *password_data, size_t password_len void APIServer::handle_disconnect(APIConnection *conn) {} -// Macro for entities without extra parameters +// Macro for controller update dispatch #define API_DISPATCH_UPDATE(entity_type, entity_name) \ void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ if (obj->is_internal()) \ @@ -278,15 +279,6 @@ void APIServer::handle_disconnect(APIConnection *conn) {} c->send_##entity_name##_state(obj); \ } -// Macro for entities with extra parameters (but parameters not used in send) -#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \ - void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \ - if (obj->is_internal()) \ - return; \ - for (auto &c : this->clients_) \ - c->send_##entity_name##_state(obj); \ - } - #ifdef USE_BINARY_SENSOR API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor) #endif @@ -304,15 +296,15 @@ API_DISPATCH_UPDATE(light::LightState, light) #endif #ifdef USE_SENSOR -API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state) +API_DISPATCH_UPDATE(sensor::Sensor, sensor) #endif #ifdef USE_SWITCH -API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state) +API_DISPATCH_UPDATE(switch_::Switch, switch) #endif #ifdef USE_TEXT_SENSOR -API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state) +API_DISPATCH_UPDATE(text_sensor::TextSensor, text_sensor) #endif #ifdef USE_CLIMATE @@ -320,7 +312,7 @@ API_DISPATCH_UPDATE(climate::Climate, climate) #endif #ifdef USE_NUMBER -API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state) +API_DISPATCH_UPDATE(number::Number, number) #endif #ifdef USE_DATETIME_DATE @@ -336,11 +328,11 @@ API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime) #endif #ifdef USE_TEXT -API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state) +API_DISPATCH_UPDATE(text::Text, text) #endif #ifdef USE_SELECT -API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index) +API_DISPATCH_UPDATE(select::Select, select) #endif #ifdef USE_LOCK @@ -356,12 +348,13 @@ API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player) #endif #ifdef USE_EVENT -// Event is a special case - it's the only entity that passes extra parameters to the send method -void APIServer::on_event(event::Event *obj, const std::string &event_type) { +// Event is a special case - unlike other entities with simple state fields, +// events store their state in a member accessed via obj->get_last_event_type() +void APIServer::on_event(event::Event *obj) { if (obj->is_internal()) return; for (auto &c : this->clients_) - c->send_event(obj, event_type); + c->send_event(obj, obj->get_last_event_type()); } #endif diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index f1f44a266d..2d58063d6c 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -72,19 +72,19 @@ class APIServer : public Component, public Controller { void on_light_update(light::LightState *obj) override; #endif #ifdef USE_SENSOR - void on_sensor_update(sensor::Sensor *obj, float state) override; + void on_sensor_update(sensor::Sensor *obj) override; #endif #ifdef USE_SWITCH - void on_switch_update(switch_::Switch *obj, bool state) override; + void on_switch_update(switch_::Switch *obj) override; #endif #ifdef USE_TEXT_SENSOR - void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override; + void on_text_sensor_update(text_sensor::TextSensor *obj) override; #endif #ifdef USE_CLIMATE void on_climate_update(climate::Climate *obj) override; #endif #ifdef USE_NUMBER - void on_number_update(number::Number *obj, float state) override; + void on_number_update(number::Number *obj) override; #endif #ifdef USE_DATETIME_DATE void on_date_update(datetime::DateEntity *obj) override; @@ -96,10 +96,10 @@ class APIServer : public Component, public Controller { void on_datetime_update(datetime::DateTimeEntity *obj) override; #endif #ifdef USE_TEXT - void on_text_update(text::Text *obj, const std::string &state) override; + void on_text_update(text::Text *obj) override; #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj, const std::string &state, size_t index) override; + void on_select_update(select::Select *obj) override; #endif #ifdef USE_LOCK void on_lock_update(lock::Lock *obj) override; @@ -141,7 +141,7 @@ class APIServer : public Component, public Controller { void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override; #endif #ifdef USE_EVENT - void on_event(event::Event *obj, const std::string &event_type) override; + void on_event(event::Event *obj) override; #endif #ifdef USE_UPDATE void on_update(update::UpdateEntity *obj) override; diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 33b3de6d72..220ed685db 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -1,4 +1,6 @@ #include "binary_sensor.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -37,6 +39,9 @@ void BinarySensor::send_state_internal(bool new_state) { // Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed if (this->set_state_(new_state)) { ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(new_state)); +#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_binary_sensor_update(this); +#endif } } diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 7df38758dc..82b75660ba 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -1,4 +1,6 @@ #include "climate.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/macros.h" namespace esphome { @@ -463,6 +465,9 @@ void Climate::publish_state() { // Send state to frontend this->state_callback_.call(*this); +#if defined(USE_CLIMATE) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_climate_update(this); +#endif // Save state this->save_state_(); } diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 654bb956a5..3062dba28a 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -1,5 +1,9 @@ #include "cover.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" + #include + #include "esphome/core/log.h" namespace esphome { @@ -169,6 +173,9 @@ void Cover::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation)); this->state_callback_.call(); +#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_cover_update(this); +#endif if (save) { CoverRestoreState restore{}; diff --git a/esphome/components/datetime/date_entity.cpp b/esphome/components/datetime/date_entity.cpp index c164a98b2e..2c2775ecf4 100644 --- a/esphome/components/datetime/date_entity.cpp +++ b/esphome/components/datetime/date_entity.cpp @@ -1,5 +1,6 @@ #include "date_entity.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_DATE #include "esphome/core/log.h" @@ -32,6 +33,9 @@ void DateEntity::publish_state() { this->set_has_state(true); ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_); this->state_callback_.call(); +#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_date_update(this); +#endif } DateCall DateEntity::make_call() { return DateCall(this); } diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index 4e3b051eb3..8606a47fa7 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -1,5 +1,6 @@ #include "datetime_entity.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_DATETIME #include "esphome/core/log.h" @@ -48,6 +49,9 @@ void DateTimeEntity::publish_state() { ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, this->month_, this->day_, this->hour_, this->minute_, this->second_); this->state_callback_.call(); +#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_datetime_update(this); +#endif } DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); } diff --git a/esphome/components/datetime/time_entity.cpp b/esphome/components/datetime/time_entity.cpp index 9b05c2124f..469be077ea 100644 --- a/esphome/components/datetime/time_entity.cpp +++ b/esphome/components/datetime/time_entity.cpp @@ -1,5 +1,6 @@ #include "time_entity.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_TIME #include "esphome/core/log.h" @@ -29,6 +30,9 @@ void TimeEntity::publish_state() { ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, this->second_); this->state_callback_.call(); +#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_time_update(this); +#endif } TimeCall TimeEntity::make_call() { return TimeCall(this); } diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index a14afbd7f5..4c74a11388 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -1,5 +1,6 @@ #include "event.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -23,6 +24,9 @@ void Event::trigger(const std::string &event_type) { this->last_event_type_ = found; ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_); this->event_callback_.call(event_type); +#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_event(this); +#endif } void Event::set_event_types(const FixedVector &event_types) { diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 959572e9d9..d37825a651 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -1,4 +1,6 @@ #include "fan.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -181,6 +183,9 @@ void Fan::publish_state() { ESP_LOGD(TAG, " Preset Mode: %s", preset); } this->state_callback_.call(); +#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_fan_update(this); +#endif this->save_state_(); } diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 7b0a698bb8..4c253ec5a8 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -1,7 +1,8 @@ -#include "esphome/core/log.h" - -#include "light_output.h" #include "light_state.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" +#include "esphome/core/log.h" +#include "light_output.h" #include "transformers.h" namespace esphome { @@ -137,7 +138,12 @@ void LightState::loop() { float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } -void LightState::publish_state() { this->remote_values_callback_.call(); } +void LightState::publish_state() { + this->remote_values_callback_.call(); +#if defined(USE_LIGHT) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_light_update(this); +#endif +} LightOutput *LightState::get_output() const { return this->output_; } diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index ddc5445349..54fefe8745 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -1,4 +1,6 @@ #include "lock.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -53,6 +55,9 @@ void Lock::publish_state(LockState state) { this->rtc_.save(&this->state); ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state)); this->state_callback_.call(); +#if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_lock_update(this); +#endif } void Lock::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index 3f274bf73b..b46ec39d30 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -1,5 +1,6 @@ #include "media_player.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -148,7 +149,12 @@ void MediaPlayer::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -void MediaPlayer::publish_state() { this->state_callback_.call(); } +void MediaPlayer::publish_state() { + this->state_callback_.call(); +#if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_media_player_update(this); +#endif +} } // namespace media_player } // namespace esphome diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index da08faf655..f12e0e9e1e 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -1,4 +1,6 @@ #include "number.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -32,6 +34,9 @@ void Number::publish_state(float state) { this->state = state; ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state); this->state_callback_.call(state); +#if defined(USE_NUMBER) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_number_update(this); +#endif } void Number::add_on_state_callback(std::function &&callback) { diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 6bb01ba6e2..9fe7a52422 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -1,4 +1,6 @@ #include "select.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include @@ -33,6 +35,9 @@ void Select::publish_state(size_t index) { ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", this->get_name().c_str(), option, index); // Callback signature requires std::string, create temporary for compatibility this->state_callback_.call(std::string(option), index); +#if defined(USE_SELECT) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_select_update(this); +#endif } const char *Select::current_option() const { return this->has_state() ? this->option_at(this->active_index_) : ""; } diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 92da4345b7..df6bd644e8 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -1,4 +1,6 @@ #include "sensor.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -131,6 +133,9 @@ void Sensor::internal_send_state_to_frontend(float state) { ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state, this->get_unit_of_measurement_ref().c_str(), this->get_accuracy_decimals()); this->callback_.call(state); +#if defined(USE_SENSOR) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_sensor_update(this); +#endif } } // namespace sensor diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 02cee91a76..3c3a437ff3 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -1,4 +1,6 @@ #include "switch.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -62,6 +64,9 @@ void Switch::publish_state(bool state) { ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), ONOFF(this->state)); this->state_callback_.call(this->state); +#if defined(USE_SWITCH) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_switch_update(this); +#endif } bool Switch::assumed_state() { return false; } diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index 654893d4e4..933d82c85c 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -1,4 +1,6 @@ #include "text.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -16,6 +18,9 @@ void Text::publish_state(const std::string &state) { ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); } this->state_callback_.call(state); +#if defined(USE_TEXT) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_text_update(this); +#endif } void Text::add_on_state_callback(std::function &&callback) { diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 0294d65861..a7bcf19967 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -1,4 +1,6 @@ #include "text_sensor.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -84,6 +86,9 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) { this->set_has_state(true); ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); this->callback_.call(state); +#if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_text_sensor_update(this); +#endif } } // namespace text_sensor diff --git a/esphome/components/update/update_entity.cpp b/esphome/components/update/update_entity.cpp index ce97fb1b77..567fc9fc8e 100644 --- a/esphome/components/update/update_entity.cpp +++ b/esphome/components/update/update_entity.cpp @@ -1,5 +1,6 @@ #include "update_entity.h" - +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -32,6 +33,9 @@ void UpdateEntity::publish_state() { this->set_has_state(true); this->state_callback_.call(); +#if defined(USE_UPDATE) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_update(this); +#endif } } // namespace update diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index b041fe8449..381d9061de 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -1,4 +1,6 @@ #include "valve.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include @@ -147,6 +149,9 @@ void Valve::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", valve_operation_to_str(this->current_operation)); this->state_callback_.call(); +#if defined(USE_VALVE) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_valve_update(this); +#endif if (save) { ValveRestoreState restore{}; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index a7fdf30eef..17ad496f30 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -289,6 +289,9 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], paren) await cg.register_component(var, config) + # Track controller registration for StaticVector sizing + CORE.register_controller() + version = config[CONF_VERSION] cg.add(paren.set_port(config[CONF_PORT])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 91ca076474..5a8128ba43 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -3,6 +3,8 @@ #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" #include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -294,7 +296,7 @@ std::string WebServer::get_config_json() { } void WebServer::setup() { - this->setup_controller(this->include_internal_); + ControllerRegistry::register_controller(this); this->base_->init(); #ifdef USE_LOGGER @@ -430,7 +432,9 @@ static JsonDetail get_request_detail(AsyncWebServerRequest *request) { } #ifdef USE_SENSOR -void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { +void WebServer::on_sensor_update(sensor::Sensor *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", sensor_state_json_generator); } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -473,7 +477,9 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail #endif #ifdef USE_TEXT_SENSOR -void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { +void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", text_sensor_state_json_generator); } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -513,7 +519,9 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std: #endif #ifdef USE_SWITCH -void WebServer::on_switch_update(switch_::Switch *obj, bool state) { +void WebServer::on_switch_update(switch_::Switch *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", switch_state_json_generator); } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -625,6 +633,8 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", binary_sensor_state_json_generator); } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -664,6 +674,8 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool #ifdef USE_FAN void WebServer::on_fan_update(fan::Fan *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", fan_state_json_generator); } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -738,6 +750,8 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { #ifdef USE_LIGHT void WebServer::on_light_update(light::LightState *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", light_state_json_generator); } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -811,6 +825,8 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi #ifdef USE_COVER void WebServer::on_cover_update(cover::Cover *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", cover_state_json_generator); } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -895,7 +911,9 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { #endif #ifdef USE_NUMBER -void WebServer::on_number_update(number::Number *obj, float state) { +void WebServer::on_number_update(number::Number *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", number_state_json_generator); } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -961,6 +979,8 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail #ifdef USE_DATETIME_DATE void WebServer::on_date_update(datetime::DateEntity *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", date_state_json_generator); } void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1016,6 +1036,8 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con #ifdef USE_DATETIME_TIME void WebServer::on_time_update(datetime::TimeEntity *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", time_state_json_generator); } void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1070,6 +1092,8 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con #ifdef USE_DATETIME_DATETIME void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", datetime_state_json_generator); } void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1124,7 +1148,9 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s #endif // USE_DATETIME_DATETIME #ifdef USE_TEXT -void WebServer::on_text_update(text::Text *obj, const std::string &state) { +void WebServer::on_text_update(text::Text *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", text_state_json_generator); } void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1178,7 +1204,9 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json #endif #ifdef USE_SELECT -void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { +void WebServer::on_select_update(select::Select *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", select_state_json_generator); } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1237,6 +1265,8 @@ std::string WebServer::select_json(select::Select *obj, const char *value, JsonD #ifdef USE_CLIMATE void WebServer::on_climate_update(climate::Climate *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", climate_state_json_generator); } void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1378,6 +1408,8 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf #ifdef USE_LOCK void WebServer::on_lock_update(lock::Lock *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", lock_state_json_generator); } void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1449,6 +1481,8 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet #ifdef USE_VALVE void WebServer::on_valve_update(valve::Valve *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", valve_state_json_generator); } void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1530,6 +1564,8 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { #ifdef USE_ALARM_CONTROL_PANEL void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", alarm_control_panel_state_json_generator); } void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -1607,7 +1643,9 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro #endif #ifdef USE_EVENT -void WebServer::on_event(event::Event *obj, const std::string &event_type) { +void WebServer::on_event(event::Event *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; this->events_.deferrable_send_state(obj, "state", event_state_json_generator); } diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 328140cfae..7e1af88645 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -255,7 +255,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_SENSOR - void on_sensor_update(sensor::Sensor *obj, float state) override; + void on_sensor_update(sensor::Sensor *obj) override; /// Handle a sensor request under '/sensor/'. void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -266,7 +266,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_SWITCH - void on_switch_update(switch_::Switch *obj, bool state) override; + void on_switch_update(switch_::Switch *obj) override; /// Handle a switch request under '/switch//'. void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -324,7 +324,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_TEXT_SENSOR - void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override; + void on_text_sensor_update(text_sensor::TextSensor *obj) override; /// Handle a text sensor request under '/text_sensor/'. void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -348,7 +348,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_NUMBER - void on_number_update(number::Number *obj, float state) override; + void on_number_update(number::Number *obj) override; /// Handle a number request under '/number/'. void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -392,7 +392,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_TEXT - void on_text_update(text::Text *obj, const std::string &state) override; + void on_text_update(text::Text *obj) override; /// Handle a text input request under '/text/'. void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -403,7 +403,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj, const std::string &state, size_t index) override; + void on_select_update(select::Select *obj) override; /// Handle a select request under '/select/'. void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -462,7 +462,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_EVENT - void on_event(event::Event *obj, const std::string &event_type) override; + void on_event(event::Event *obj) override; static std::string event_state_json_generator(WebServer *web_server, void *source); static std::string event_all_json_generator(WebServer *web_server, void *source); diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index fed5265d6b..08753b0f2d 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -48,6 +48,9 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) +# Key for tracking controller count in CORE.data for ControllerRegistry StaticVector sizing +KEY_CONTROLLER_REGISTRY_COUNT = "controller_registry_count" + class EsphomeError(Exception): """General ESPHome exception occurred.""" @@ -910,6 +913,11 @@ class EsphomeCore: """ self.platform_counts[platform_name] += 1 + def register_controller(self) -> None: + """Track registration of a Controller for ControllerRegistry StaticVector sizing.""" + controller_count = self.data.setdefault(KEY_CONTROLLER_REGISTRY_COUNT, 0) + self.data[KEY_CONTROLLER_REGISTRY_COUNT] = controller_count + 1 + @property def cpp_main_section(self): from esphome.cpp_generator import statement diff --git a/esphome/core/config.py b/esphome/core/config.py index 2740453808..763f9ebd9f 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -40,7 +40,12 @@ from esphome.const import ( PlatformFramework, __version__ as ESPHOME_VERSION, ) -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import ( + CORE, + KEY_CONTROLLER_REGISTRY_COUNT, + CoroPriority, + coroutine_with_priority, +) from esphome.helpers import ( copy_file_if_changed, fnv1a_32bit_hash, @@ -462,6 +467,15 @@ async def _add_platform_defines() -> None: cg.add_define(f"USE_{platform_name.upper()}") +@coroutine_with_priority(CoroPriority.FINAL) +async def _add_controller_registry_define() -> None: + # Generate StaticVector size for ControllerRegistry + controller_count = CORE.data.get(KEY_CONTROLLER_REGISTRY_COUNT, 0) + if controller_count > 0: + cg.add_define("USE_CONTROLLER_REGISTRY") + cg.add_define("CONTROLLER_REGISTRY_MAX", controller_count) + + @coroutine_with_priority(CoroPriority.CORE) async def to_code(config: ConfigType) -> None: cg.add_global(cg.global_ns.namespace("esphome").using) @@ -483,6 +497,7 @@ async def to_code(config: ConfigType) -> None: cg.add_define("ESPHOME_COMPONENT_COUNT", len(CORE.component_ids)) CORE.add_job(_add_platform_defines) + CORE.add_job(_add_controller_registry_define) CORE.add_job(_add_automations, config) diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp deleted file mode 100644 index f7ff5a9734..0000000000 --- a/esphome/core/controller.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "controller.h" -#include "esphome/core/application.h" -#include "esphome/core/log.h" - -namespace esphome { - -void Controller::setup_controller(bool include_internal) { -#ifdef USE_BINARY_SENSOR - for (auto *obj : App.get_binary_sensors()) { - if (include_internal || !obj->is_internal()) { - obj->add_full_state_callback( - [this, obj](optional previous, optional state) { this->on_binary_sensor_update(obj); }); - } - } -#endif -#ifdef USE_FAN - for (auto *obj : App.get_fans()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_fan_update(obj); }); - } -#endif -#ifdef USE_LIGHT - for (auto *obj : App.get_lights()) { - if (include_internal || !obj->is_internal()) - obj->add_new_remote_values_callback([this, obj]() { this->on_light_update(obj); }); - } -#endif -#ifdef USE_SENSOR - for (auto *obj : App.get_sensors()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](float state) { this->on_sensor_update(obj, state); }); - } -#endif -#ifdef USE_SWITCH - for (auto *obj : App.get_switches()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](bool state) { this->on_switch_update(obj, state); }); - } -#endif -#ifdef USE_COVER - for (auto *obj : App.get_covers()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_cover_update(obj); }); - } -#endif -#ifdef USE_TEXT_SENSOR - for (auto *obj : App.get_text_sensors()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](const std::string &state) { this->on_text_sensor_update(obj, state); }); - } -#endif -#ifdef USE_CLIMATE - for (auto *obj : App.get_climates()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](climate::Climate & /*unused*/) { this->on_climate_update(obj); }); - } -#endif -#ifdef USE_NUMBER - for (auto *obj : App.get_numbers()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](float state) { this->on_number_update(obj, state); }); - } -#endif -#ifdef USE_DATETIME_DATE - for (auto *obj : App.get_dates()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_date_update(obj); }); - } -#endif -#ifdef USE_DATETIME_TIME - for (auto *obj : App.get_times()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_time_update(obj); }); - } -#endif -#ifdef USE_DATETIME_DATETIME - for (auto *obj : App.get_datetimes()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_datetime_update(obj); }); - } -#endif -#ifdef USE_TEXT - for (auto *obj : App.get_texts()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](const std::string &state) { this->on_text_update(obj, state); }); - } -#endif -#ifdef USE_SELECT - for (auto *obj : App.get_selects()) { - if (include_internal || !obj->is_internal()) { - obj->add_on_state_callback( - [this, obj](const std::string &state, size_t index) { this->on_select_update(obj, state, index); }); - } - } -#endif -#ifdef USE_LOCK - for (auto *obj : App.get_locks()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_lock_update(obj); }); - } -#endif -#ifdef USE_VALVE - for (auto *obj : App.get_valves()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_valve_update(obj); }); - } -#endif -#ifdef USE_MEDIA_PLAYER - for (auto *obj : App.get_media_players()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_media_player_update(obj); }); - } -#endif -#ifdef USE_ALARM_CONTROL_PANEL - for (auto *obj : App.get_alarm_control_panels()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_alarm_control_panel_update(obj); }); - } -#endif -#ifdef USE_EVENT - for (auto *obj : App.get_events()) { - if (include_internal || !obj->is_internal()) - obj->add_on_event_callback([this, obj](const std::string &event_type) { this->on_event(obj, event_type); }); - } -#endif -#ifdef USE_UPDATE - for (auto *obj : App.get_updates()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj]() { this->on_update(obj); }); - } -#endif -} - -} // namespace esphome diff --git a/esphome/core/controller.h b/esphome/core/controller.h index b475e326ee..697017217d 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -69,7 +69,6 @@ namespace esphome { class Controller { public: - void setup_controller(bool include_internal = false); #ifdef USE_BINARY_SENSOR virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj){}; #endif @@ -80,22 +79,22 @@ class Controller { virtual void on_light_update(light::LightState *obj){}; #endif #ifdef USE_SENSOR - virtual void on_sensor_update(sensor::Sensor *obj, float state){}; + virtual void on_sensor_update(sensor::Sensor *obj){}; #endif #ifdef USE_SWITCH - virtual void on_switch_update(switch_::Switch *obj, bool state){}; + virtual void on_switch_update(switch_::Switch *obj){}; #endif #ifdef USE_COVER virtual void on_cover_update(cover::Cover *obj){}; #endif #ifdef USE_TEXT_SENSOR - virtual void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state){}; + virtual void on_text_sensor_update(text_sensor::TextSensor *obj){}; #endif #ifdef USE_CLIMATE virtual void on_climate_update(climate::Climate *obj){}; #endif #ifdef USE_NUMBER - virtual void on_number_update(number::Number *obj, float state){}; + virtual void on_number_update(number::Number *obj){}; #endif #ifdef USE_DATETIME_DATE virtual void on_date_update(datetime::DateEntity *obj){}; @@ -107,10 +106,10 @@ class Controller { virtual void on_datetime_update(datetime::DateTimeEntity *obj){}; #endif #ifdef USE_TEXT - virtual void on_text_update(text::Text *obj, const std::string &state){}; + virtual void on_text_update(text::Text *obj){}; #endif #ifdef USE_SELECT - virtual void on_select_update(select::Select *obj, const std::string &state, size_t index){}; + virtual void on_select_update(select::Select *obj){}; #endif #ifdef USE_LOCK virtual void on_lock_update(lock::Lock *obj){}; @@ -125,7 +124,7 @@ class Controller { virtual void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj){}; #endif #ifdef USE_EVENT - virtual void on_event(event::Event *obj, const std::string &event_type){}; + virtual void on_event(event::Event *obj){}; #endif #ifdef USE_UPDATE virtual void on_update(update::UpdateEntity *obj){}; diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp new file mode 100644 index 0000000000..0a84bb0d0d --- /dev/null +++ b/esphome/core/controller_registry.cpp @@ -0,0 +1,114 @@ +#include "esphome/core/controller_registry.h" + +#ifdef USE_CONTROLLER_REGISTRY + +#include "esphome/core/controller.h" + +namespace esphome { + +StaticVector ControllerRegistry::controllers; + +void ControllerRegistry::register_controller(Controller *controller) { controllers.push_back(controller); } + +// Macro for standard registry notification dispatch - calls on__update() +#define CONTROLLER_REGISTRY_NOTIFY(entity_type, entity_name) \ + void ControllerRegistry::notify_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ + for (auto *controller : controllers) { \ + controller->on_##entity_name##_update(obj); \ + } \ + } + +// Macro for entities where controller method has no "_update" suffix (Event, Update) +#define CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX(entity_type, entity_name) \ + void ControllerRegistry::notify_##entity_name(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \ + for (auto *controller : controllers) { \ + controller->on_##entity_name(obj); \ + } \ + } + +#ifdef USE_BINARY_SENSOR +CONTROLLER_REGISTRY_NOTIFY(binary_sensor::BinarySensor, binary_sensor) +#endif + +#ifdef USE_FAN +CONTROLLER_REGISTRY_NOTIFY(fan::Fan, fan) +#endif + +#ifdef USE_LIGHT +CONTROLLER_REGISTRY_NOTIFY(light::LightState, light) +#endif + +#ifdef USE_SENSOR +CONTROLLER_REGISTRY_NOTIFY(sensor::Sensor, sensor) +#endif + +#ifdef USE_SWITCH +CONTROLLER_REGISTRY_NOTIFY(switch_::Switch, switch) +#endif + +#ifdef USE_COVER +CONTROLLER_REGISTRY_NOTIFY(cover::Cover, cover) +#endif + +#ifdef USE_TEXT_SENSOR +CONTROLLER_REGISTRY_NOTIFY(text_sensor::TextSensor, text_sensor) +#endif + +#ifdef USE_CLIMATE +CONTROLLER_REGISTRY_NOTIFY(climate::Climate, climate) +#endif + +#ifdef USE_NUMBER +CONTROLLER_REGISTRY_NOTIFY(number::Number, number) +#endif + +#ifdef USE_DATETIME_DATE +CONTROLLER_REGISTRY_NOTIFY(datetime::DateEntity, date) +#endif + +#ifdef USE_DATETIME_TIME +CONTROLLER_REGISTRY_NOTIFY(datetime::TimeEntity, time) +#endif + +#ifdef USE_DATETIME_DATETIME +CONTROLLER_REGISTRY_NOTIFY(datetime::DateTimeEntity, datetime) +#endif + +#ifdef USE_TEXT +CONTROLLER_REGISTRY_NOTIFY(text::Text, text) +#endif + +#ifdef USE_SELECT +CONTROLLER_REGISTRY_NOTIFY(select::Select, select) +#endif + +#ifdef USE_LOCK +CONTROLLER_REGISTRY_NOTIFY(lock::Lock, lock) +#endif + +#ifdef USE_VALVE +CONTROLLER_REGISTRY_NOTIFY(valve::Valve, valve) +#endif + +#ifdef USE_MEDIA_PLAYER +CONTROLLER_REGISTRY_NOTIFY(media_player::MediaPlayer, media_player) +#endif + +#ifdef USE_ALARM_CONTROL_PANEL +CONTROLLER_REGISTRY_NOTIFY(alarm_control_panel::AlarmControlPanel, alarm_control_panel) +#endif + +#ifdef USE_EVENT +CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX(event::Event, event) +#endif + +#ifdef USE_UPDATE +CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX(update::UpdateEntity, update) +#endif + +#undef CONTROLLER_REGISTRY_NOTIFY +#undef CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX + +} // namespace esphome + +#endif // USE_CONTROLLER_REGISTRY diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h new file mode 100644 index 0000000000..640a276a0a --- /dev/null +++ b/esphome/core/controller_registry.h @@ -0,0 +1,245 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_CONTROLLER_REGISTRY + +#include "esphome/core/helpers.h" + +// Forward declarations +namespace esphome { + +class Controller; + +#ifdef USE_BINARY_SENSOR +namespace binary_sensor { +class BinarySensor; +} +#endif + +#ifdef USE_FAN +namespace fan { +class Fan; +} +#endif + +#ifdef USE_LIGHT +namespace light { +class LightState; +} +#endif + +#ifdef USE_SENSOR +namespace sensor { +class Sensor; +} +#endif + +#ifdef USE_SWITCH +namespace switch_ { +class Switch; +} +#endif + +#ifdef USE_COVER +namespace cover { +class Cover; +} +#endif + +#ifdef USE_TEXT_SENSOR +namespace text_sensor { +class TextSensor; +} +#endif + +#ifdef USE_CLIMATE +namespace climate { +class Climate; +} +#endif + +#ifdef USE_NUMBER +namespace number { +class Number; +} +#endif + +#ifdef USE_DATETIME_DATE +namespace datetime { +class DateEntity; +} +#endif + +#ifdef USE_DATETIME_TIME +namespace datetime { +class TimeEntity; +} +#endif + +#ifdef USE_DATETIME_DATETIME +namespace datetime { +class DateTimeEntity; +} +#endif + +#ifdef USE_TEXT +namespace text { +class Text; +} +#endif + +#ifdef USE_SELECT +namespace select { +class Select; +} +#endif + +#ifdef USE_LOCK +namespace lock { +class Lock; +} +#endif + +#ifdef USE_VALVE +namespace valve { +class Valve; +} +#endif + +#ifdef USE_MEDIA_PLAYER +namespace media_player { +class MediaPlayer; +} +#endif + +#ifdef USE_ALARM_CONTROL_PANEL +namespace alarm_control_panel { +class AlarmControlPanel; +} +#endif + +#ifdef USE_EVENT +namespace event { +class Event; +} +#endif + +#ifdef USE_UPDATE +namespace update { +class UpdateEntity; +} +#endif + +/** Global registry for Controllers to receive entity state updates. + * + * This singleton registry allows Controllers (APIServer, WebServer) to receive + * entity state change notifications without storing per-entity callbacks. + * + * Instead of each entity maintaining controller callbacks (32 bytes overhead per entity), + * entities call ControllerRegistry::notify_*_update() which iterates the small list + * of registered controllers (typically 2: API and WebServer). + * + * Controllers read state directly from entities using existing accessors (obj->state, etc.) + * rather than receiving it as callback parameters that were being ignored anyway. + * + * Memory savings: 32 bytes per entity (2 controllers × 16 bytes std::function overhead) + * Typical config (25 entities): ~780 bytes saved + * Large config (80 entities): ~2,540 bytes saved + */ +class ControllerRegistry { + public: + /** Register a controller to receive entity state updates. + * + * Controllers should call this in their setup() method. + * Typically only APIServer and WebServer register. + */ + static void register_controller(Controller *controller); + +#ifdef USE_BINARY_SENSOR + static void notify_binary_sensor_update(binary_sensor::BinarySensor *obj); +#endif + +#ifdef USE_FAN + static void notify_fan_update(fan::Fan *obj); +#endif + +#ifdef USE_LIGHT + static void notify_light_update(light::LightState *obj); +#endif + +#ifdef USE_SENSOR + static void notify_sensor_update(sensor::Sensor *obj); +#endif + +#ifdef USE_SWITCH + static void notify_switch_update(switch_::Switch *obj); +#endif + +#ifdef USE_COVER + static void notify_cover_update(cover::Cover *obj); +#endif + +#ifdef USE_TEXT_SENSOR + static void notify_text_sensor_update(text_sensor::TextSensor *obj); +#endif + +#ifdef USE_CLIMATE + static void notify_climate_update(climate::Climate *obj); +#endif + +#ifdef USE_NUMBER + static void notify_number_update(number::Number *obj); +#endif + +#ifdef USE_DATETIME_DATE + static void notify_date_update(datetime::DateEntity *obj); +#endif + +#ifdef USE_DATETIME_TIME + static void notify_time_update(datetime::TimeEntity *obj); +#endif + +#ifdef USE_DATETIME_DATETIME + static void notify_datetime_update(datetime::DateTimeEntity *obj); +#endif + +#ifdef USE_TEXT + static void notify_text_update(text::Text *obj); +#endif + +#ifdef USE_SELECT + static void notify_select_update(select::Select *obj); +#endif + +#ifdef USE_LOCK + static void notify_lock_update(lock::Lock *obj); +#endif + +#ifdef USE_VALVE + static void notify_valve_update(valve::Valve *obj); +#endif + +#ifdef USE_MEDIA_PLAYER + static void notify_media_player_update(media_player::MediaPlayer *obj); +#endif + +#ifdef USE_ALARM_CONTROL_PANEL + static void notify_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj); +#endif + +#ifdef USE_EVENT + static void notify_event(event::Event *obj); +#endif + +#ifdef USE_UPDATE + static void notify_update(update::UpdateEntity *obj); +#endif + + protected: + static StaticVector controllers; +}; + +} // namespace esphome + +#endif // USE_CONTROLLER_REGISTRY diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 2be32058ea..8230518071 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -28,6 +28,7 @@ #define USE_BUTTON #define USE_CAMERA #define USE_CLIMATE +#define USE_CONTROLLER_REGISTRY #define USE_COVER #define USE_DATETIME #define USE_DATETIME_DATE @@ -296,6 +297,7 @@ #define USE_DASHBOARD_IMPORT // Default counts for static analysis +#define CONTROLLER_REGISTRY_MAX 2 #define ESPHOME_COMPONENT_COUNT 50 #define ESPHOME_DEVICE_COUNT 10 #define ESPHOME_AREA_COUNT 10