mirror of
https://github.com/esphome/esphome.git
synced 2026-09-29 16:00:23 +00:00
[core] Dispatch controller updates through generated direct calls
APIServer and WebServer inherited both Component and Controller, so each on_*_update override reached through the registry needed a non-virtual thunk to adjust this to the secondary base, and every publish iterated a StaticVector of Controller pointers with an indirect call. Drop the Controller base class and the runtime list. ControllerRegistry only declares notify_*; code generation defines each one in main.cpp as direct calls on the registered controllers through the entity_types.h X-macro. A ControllerContract concept with a generated static_assert per controller reports a missing callback clearly. On an ESP32 IDF config with api, web_server and four entity types this saves 256 B of flash and 24 B of RAM.
This commit is contained in:
@@ -64,7 +64,6 @@ from esphome.cpp_types import ( # noqa: F401
|
||||
Application,
|
||||
Component,
|
||||
ComponentPtr,
|
||||
Controller,
|
||||
EntityBase,
|
||||
EntityCategory,
|
||||
ESPTime,
|
||||
|
||||
@@ -81,7 +81,7 @@ def AUTO_LOAD(config: ConfigType) -> list[str]:
|
||||
|
||||
|
||||
api_ns = cg.esphome_ns.namespace("api")
|
||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||
APIServer = api_ns.class_("APIServer", cg.Component)
|
||||
HomeAssistantServiceCallAction = api_ns.class_(
|
||||
"HomeAssistantServiceCallAction", automation.Action
|
||||
)
|
||||
@@ -462,8 +462,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# Track controller registration for StaticVector sizing
|
||||
CORE.register_controller()
|
||||
CORE.register_controller(var)
|
||||
|
||||
# Request a log listener slot for API log streaming
|
||||
request_log_listener()
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "api_connection.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -61,8 +60,6 @@ void APIServer::socket_failed_(const LogString *msg) {
|
||||
}
|
||||
|
||||
void APIServer::setup() {
|
||||
ControllerRegistry::register_controller(this);
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
// Always reserve the slot: flash preferences are positional on esp8266, so
|
||||
// a yaml key build must keep the layout of a runtime key build
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#ifdef USE_PROVISIONING
|
||||
@@ -49,8 +48,7 @@ struct SavedNoisePsk {
|
||||
bool load_saved_noise_psk(noise::psk_t &out);
|
||||
#endif
|
||||
|
||||
class APIServer final : public Component,
|
||||
public Controller
|
||||
class APIServer final : public Component
|
||||
#ifdef USE_CAMERA
|
||||
,
|
||||
public camera::CameraListener
|
||||
@@ -94,58 +92,58 @@ class APIServer final : public Component,
|
||||
|
||||
void handle_disconnect(APIConnection *conn);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
void on_cover_update(cover::Cover *obj) override;
|
||||
void on_cover_update(cover::Cover *obj);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_update(fan::Fan *obj) override;
|
||||
void on_fan_update(fan::Fan *obj);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_update(light::LightState *obj) override;
|
||||
void on_light_update(light::LightState *obj);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
void on_sensor_update(sensor::Sensor *obj) override;
|
||||
void on_sensor_update(sensor::Sensor *obj);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_update(switch_::Switch *obj) override;
|
||||
void on_switch_update(switch_::Switch *obj);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_update(climate::Climate *obj) override;
|
||||
void on_climate_update(climate::Climate *obj);
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void on_number_update(number::Number *obj) override;
|
||||
void on_number_update(number::Number *obj);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void on_date_update(datetime::DateEntity *obj) override;
|
||||
void on_date_update(datetime::DateEntity *obj);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void on_time_update(datetime::TimeEntity *obj) override;
|
||||
void on_time_update(datetime::TimeEntity *obj);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
||||
void on_datetime_update(datetime::DateTimeEntity *obj);
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
void on_text_update(text::Text *obj) override;
|
||||
void on_text_update(text::Text *obj);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj) override;
|
||||
void on_select_update(select::Select *obj);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
void on_lock_update(lock::Lock *obj);
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
void on_valve_update(valve::Valve *obj) override;
|
||||
void on_valve_update(valve::Valve *obj);
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
||||
void on_media_player_update(media_player::MediaPlayer *obj);
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
void on_water_heater_update(water_heater::WaterHeater *obj) override;
|
||||
void on_water_heater_update(water_heater::WaterHeater *obj);
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
||||
@@ -188,13 +186,13 @@ class APIServer final : public Component,
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
|
||||
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj);
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
void on_event(event::Event *obj) override;
|
||||
void on_event(event::Event *obj);
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
void on_update(update::UpdateEntity *obj) override;
|
||||
void on_update(update::UpdateEntity *obj);
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_zwave_proxy_request(const ZWaveProxyRequest &msg);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#ifdef USE_API
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/component_iterator.h"
|
||||
#include "esphome/core/controller.h"
|
||||
namespace esphome::api {
|
||||
|
||||
class APIConnection;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
#include "esphome/components/web_server_base/web_server_base.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/entity_includes.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#ifdef USE_CLIMATE
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -62,7 +62,7 @@ DEFAULT_PORT = 80
|
||||
|
||||
|
||||
web_server_ns = cg.esphome_ns.namespace("web_server")
|
||||
WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
|
||||
WebServer = web_server_ns.class_("WebServer", cg.Component)
|
||||
|
||||
sorting_groups = {}
|
||||
|
||||
@@ -378,8 +378,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# Track controller registration for StaticVector sizing
|
||||
CORE.register_controller()
|
||||
CORE.register_controller(var)
|
||||
|
||||
version = config[CONF_VERSION]
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#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"
|
||||
@@ -365,7 +364,6 @@ json::SerializationBuffer<> WebServer::get_config_json() {
|
||||
}
|
||||
|
||||
void WebServer::setup() {
|
||||
ControllerRegistry::register_controller(this);
|
||||
this->base_->init();
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "esphome/components/web_server_base/web_server_base.h"
|
||||
#ifdef USE_WEBSERVER
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#ifdef USE_LOGGER
|
||||
@@ -187,7 +186,7 @@ class DeferredUpdateEventSourceList final : public std::list<DeferredUpdateEvent
|
||||
* under the '/light/...', '/sensor/...', ... URLs. A full documentation for this API
|
||||
* can be found under https://esphome.io/web-api/.
|
||||
*/
|
||||
class WebServer final : public Controller, public Component, public AsyncWebHandler {
|
||||
class WebServer final : public Component, public AsyncWebHandler {
|
||||
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
|
||||
friend class DeferredUpdateEventSourceList;
|
||||
#endif
|
||||
@@ -292,7 +291,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void on_sensor_update(sensor::Sensor *obj) override;
|
||||
void on_sensor_update(sensor::Sensor *obj);
|
||||
/// Handle a sensor request under '/sensor/<id>'.
|
||||
void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -301,7 +300,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_update(switch_::Switch *obj) override;
|
||||
void on_switch_update(switch_::Switch *obj);
|
||||
|
||||
/// Handle a switch request under '/switch/<id>/</turn_on/turn_off/toggle>'.
|
||||
void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -319,7 +318,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj);
|
||||
|
||||
/// Handle a binary sensor request under '/binary_sensor/<id>'.
|
||||
void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -329,7 +328,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void on_fan_update(fan::Fan *obj) override;
|
||||
void on_fan_update(fan::Fan *obj);
|
||||
|
||||
/// Handle a fan request under '/fan/<id>/</turn_on/turn_off/toggle>'.
|
||||
void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -339,7 +338,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_update(light::LightState *obj) override;
|
||||
void on_light_update(light::LightState *obj);
|
||||
|
||||
/// Handle a light request under '/light/<id>/</turn_on/turn_off/toggle>'.
|
||||
void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -349,7 +348,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj);
|
||||
|
||||
/// Handle a text sensor request under '/text_sensor/<id>'.
|
||||
void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -359,7 +358,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
void on_cover_update(cover::Cover *obj) override;
|
||||
void on_cover_update(cover::Cover *obj);
|
||||
|
||||
/// Handle a cover request under '/cover/<id>/<open/close/stop/set>'.
|
||||
void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -369,7 +368,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void on_number_update(number::Number *obj) override;
|
||||
void on_number_update(number::Number *obj);
|
||||
/// Handle a number request under '/number/<id>'.
|
||||
void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -378,7 +377,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void on_date_update(datetime::DateEntity *obj) override;
|
||||
void on_date_update(datetime::DateEntity *obj);
|
||||
/// Handle a date request under '/date/<id>'.
|
||||
void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -387,7 +386,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void on_time_update(datetime::TimeEntity *obj) override;
|
||||
void on_time_update(datetime::TimeEntity *obj);
|
||||
/// Handle a time request under '/time/<id>'.
|
||||
void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -396,7 +395,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
||||
void on_datetime_update(datetime::DateTimeEntity *obj);
|
||||
/// Handle a datetime request under '/datetime/<id>'.
|
||||
void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -405,7 +404,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void on_text_update(text::Text *obj) override;
|
||||
void on_text_update(text::Text *obj);
|
||||
/// Handle a text input request under '/text/<id>'.
|
||||
void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -414,7 +413,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj) override;
|
||||
void on_select_update(select::Select *obj);
|
||||
/// Handle a select request under '/select/<id>'.
|
||||
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -423,7 +422,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_update(climate::Climate *obj) override;
|
||||
void on_climate_update(climate::Climate *obj);
|
||||
/// Handle a climate request under '/climate/<id>'.
|
||||
void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -432,7 +431,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
void on_lock_update(lock::Lock *obj);
|
||||
|
||||
/// Handle a lock request under '/lock/<id>/</lock/unlock/open>'.
|
||||
void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -442,7 +441,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_VALVE
|
||||
void on_valve_update(valve::Valve *obj) override;
|
||||
void on_valve_update(valve::Valve *obj);
|
||||
|
||||
/// Handle a valve request under '/valve/<id>/<open/close/stop/set>'.
|
||||
void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -452,7 +451,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
|
||||
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj);
|
||||
|
||||
/// Handle a alarm_control_panel request under '/alarm_control_panel/<id>'.
|
||||
void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -462,7 +461,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
void on_water_heater_update(water_heater::WaterHeater *obj) override;
|
||||
void on_water_heater_update(water_heater::WaterHeater *obj);
|
||||
|
||||
/// Handle a water_heater request under '/water_heater/<id>/<mode/set>'.
|
||||
void handle_water_heater_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
@@ -484,8 +483,12 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
static json::SerializationBuffer<> radio_frequency_all_json_generator(WebServer *web_server, void *source);
|
||||
#endif
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_update(media_player::MediaPlayer *obj) {}
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void on_event(event::Event *obj) override;
|
||||
void on_event(event::Event *obj);
|
||||
|
||||
static json::SerializationBuffer<> event_state_json_generator(WebServer *web_server, void *source);
|
||||
static json::SerializationBuffer<> event_all_json_generator(WebServer *web_server, void *source);
|
||||
@@ -495,7 +498,7 @@ class WebServer final : public Controller, public Component, public AsyncWebHand
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
void on_update(update::UpdateEntity *obj) override;
|
||||
void on_update(update::UpdateEntity *obj);
|
||||
|
||||
/// Handle a update request under '/update/<id>'.
|
||||
void handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -52,8 +52,8 @@ 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"
|
||||
# Key for the controllers (APIServer, WebServer) that receive entity state updates
|
||||
KEY_CONTROLLER_REGISTRY_CONTROLLERS = "controller_registry_controllers"
|
||||
|
||||
# CORE.data key for the "is_rp2040 deprecation warning already fired this
|
||||
# run" flag. Mirrors the ``cv.only_on_rp2040`` dedupe pattern; cleared
|
||||
@@ -1209,10 +1209,14 @@ class EsphomeCore:
|
||||
if not self.platform_counts[platform_name]:
|
||||
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
|
||||
def register_controller(self, controller: "MockObj") -> None:
|
||||
"""Register a controller that receives every entity state update.
|
||||
|
||||
Code generation defines the ControllerRegistry notify functions in
|
||||
main.cpp as direct calls on each registered controller, so the C++
|
||||
class only needs the on_*_update methods, not a base class.
|
||||
"""
|
||||
self.data.setdefault(KEY_CONTROLLER_REGISTRY_CONTROLLERS, []).append(controller)
|
||||
|
||||
@property
|
||||
def cpp_main_section(self):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/entity_includes.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
|
||||
+31
-8
@@ -46,7 +46,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import (
|
||||
CORE,
|
||||
KEY_CONTROLLER_REGISTRY_COUNT,
|
||||
KEY_CONTROLLER_REGISTRY_CONTROLLERS,
|
||||
CoroPriority,
|
||||
coroutine_with_priority,
|
||||
)
|
||||
@@ -674,12 +674,35 @@ async def _add_platform_defines() -> None:
|
||||
|
||||
|
||||
@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)
|
||||
async def _add_controller_registry_dispatch() -> None:
|
||||
# Define ControllerRegistry::notify_*() in main.cpp as direct calls on each
|
||||
# registered controller. entity_types.h expands one definition per entity
|
||||
# type that is compiled in, so only the callbacks in use are emitted.
|
||||
controllers = CORE.data.get(KEY_CONTROLLER_REGISTRY_CONTROLLERS)
|
||||
if not controllers:
|
||||
return
|
||||
cg.add_define("USE_CONTROLLER_REGISTRY")
|
||||
for var in controllers:
|
||||
cg.add_global(
|
||||
cg.RawStatement(
|
||||
f"static_assert(ControllerContract<std::remove_pointer_t<decltype({var})>>, "
|
||||
f'"{var} is missing an on_*_update() callback for an entity type in this build '
|
||||
'(esphome/core/controller_registry.h)");'
|
||||
)
|
||||
)
|
||||
calls = " \\\n".join(f" {var}->on_##callback(obj);" for var in controllers)
|
||||
cg.add_global(
|
||||
cg.RawStatement(
|
||||
"#define ENTITY_TYPE_(type, singular, plural, count, upper)\n"
|
||||
"#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \\\n"
|
||||
" void ControllerRegistry::notify_##callback(type *obj) { \\\n"
|
||||
f"{calls} \\\n"
|
||||
" }\n"
|
||||
'#include "esphome/core/entity_types.h"\n'
|
||||
"#undef ENTITY_TYPE_\n"
|
||||
"#undef ENTITY_CONTROLLER_TYPE_"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
@@ -755,7 +778,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
)
|
||||
|
||||
CORE.add_job(_add_platform_defines)
|
||||
CORE.add_job(_add_controller_registry_define)
|
||||
CORE.add_job(_add_controller_registry_dispatch)
|
||||
CORE.add_job(_add_looping_components)
|
||||
|
||||
CORE.add_job(_add_automations, config)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/entity_includes.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class Controller {
|
||||
public:
|
||||
// Controller virtual methods (generated from entity_types.h)
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback
|
||||
#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) virtual void on_##callback(type *obj){};
|
||||
#include "esphome/core/entity_types.h"
|
||||
#undef ENTITY_TYPE_
|
||||
#undef ENTITY_CONTROLLER_TYPE_
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "esphome/core/controller_registry.h"
|
||||
|
||||
#ifdef USE_CONTROLLER_REGISTRY
|
||||
|
||||
namespace esphome {
|
||||
|
||||
StaticVector<Controller *, CONTROLLER_REGISTRY_MAX> ControllerRegistry::controllers;
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_CONTROLLER_REGISTRY
|
||||
@@ -5,37 +5,34 @@
|
||||
#ifdef USE_CONTROLLER_REGISTRY
|
||||
|
||||
#include "esphome/core/entity_includes.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class Controller;
|
||||
/// A controller provides a plain on_*_update() member for every entity type in the build.
|
||||
/// Checked by a static_assert in the generated main.cpp for each registered controller.
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
template<typename T>
|
||||
concept ControllerContract = requires(T &controller) {
|
||||
controller;
|
||||
#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback
|
||||
#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \
|
||||
controller.on_##callback(static_cast<type *>(nullptr));
|
||||
#include "esphome/core/entity_types.h"
|
||||
#undef ENTITY_TYPE_
|
||||
#undef ENTITY_CONTROLLER_TYPE_
|
||||
};
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
|
||||
/** Global registry for Controllers to receive entity state updates.
|
||||
/** Fan-out of entity state updates to the controllers (APIServer, WebServer).
|
||||
*
|
||||
* 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).
|
||||
*
|
||||
* Each notify method directly iterates controllers and calls the virtual method,
|
||||
* avoiding function pointer indirection for minimal dispatch overhead.
|
||||
*
|
||||
* 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
|
||||
* Entities call ControllerRegistry::notify_*_update() instead of holding
|
||||
* per-entity controller callbacks. The notify functions are only declared here;
|
||||
* code generation defines them in main.cpp as direct calls on each controller
|
||||
* that registered through CORE.register_controller(), so there is no virtual
|
||||
* dispatch, no controller base class and no runtime list of controllers.
|
||||
*/
|
||||
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) { controllers.push_back(controller); }
|
||||
|
||||
// Notify method declarations (generated from entity_types.h)
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback
|
||||
@@ -45,38 +42,8 @@ class ControllerRegistry {
|
||||
#undef ENTITY_TYPE_
|
||||
#undef ENTITY_CONTROLLER_TYPE_
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
|
||||
protected:
|
||||
static StaticVector<Controller *, CONTROLLER_REGISTRY_MAX> controllers;
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
// Include controller.h AFTER the class definition so notify methods can be
|
||||
// defined inline. This is safe because controller_registry.h is only ever
|
||||
// included from .cpp files, never from other headers.
|
||||
#include "esphome/core/controller.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
// Inline notify methods — each is a tiny loop over 1-2 controllers.
|
||||
// Defining them here (rather than in controller_registry.cpp) allows the
|
||||
// compiler to inline them into the single call site in each entity's
|
||||
// notify_frontend_(), eliminating an unnecessary function-call frame.
|
||||
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define ENTITY_TYPE_(type, singular, plural, count, upper) // no controller callback
|
||||
#define ENTITY_CONTROLLER_TYPE_(type, singular, plural, count, upper, callback) \
|
||||
inline void ControllerRegistry::notify_##callback(type *obj) { \
|
||||
for (auto *controller : controllers) { \
|
||||
controller->on_##callback(obj); \
|
||||
} \
|
||||
}
|
||||
#include "esphome/core/entity_types.h"
|
||||
#undef ENTITY_TYPE_
|
||||
#undef ENTITY_CONTROLLER_TYPE_
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_CONTROLLER_REGISTRY
|
||||
|
||||
@@ -629,7 +629,6 @@
|
||||
#define USE_DASHBOARD_IMPORT
|
||||
|
||||
// Default counts for static analysis
|
||||
#define CONTROLLER_REGISTRY_MAX 2
|
||||
#define ESPHOME_AREA_COUNT 10
|
||||
#define ESPHOME_COMPONENT_COUNT 50
|
||||
#define ESPHOME_DEVICE_COUNT 10
|
||||
|
||||
@@ -37,7 +37,6 @@ optional = global_ns.namespace("std").class_("optional")
|
||||
arduino_json_ns = global_ns.namespace("ArduinoJson")
|
||||
JsonObject = arduino_json_ns.class_("JsonObject")
|
||||
JsonObjectConst = arduino_json_ns.class_("JsonObjectConst")
|
||||
Controller = esphome_ns.class_("Controller")
|
||||
GPIOPin = esphome_ns.class_("GPIOPin")
|
||||
InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin)
|
||||
gpio_ns = esphome_ns.namespace("gpio")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.core import CORE
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
@@ -12,6 +11,5 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
cg.add_define("API_MAX_SEND_QUEUE", 8)
|
||||
cg.add_define("MAX_API_CONNECTIONS", 1)
|
||||
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
|
||||
CORE.register_controller() # api_server registers with the controller registry
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
|
||||
@@ -70,7 +70,6 @@ from esphome.cpp_generator import _extract_component_ns
|
||||
"arduino_json_ns",
|
||||
"JsonObject",
|
||||
"JsonObjectConst",
|
||||
"Controller",
|
||||
"GPIOPin",
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user