diff --git a/esphome/codegen.py b/esphome/codegen.py index bfa1683ce7..59e03c1914 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -26,6 +26,7 @@ from esphome.cpp_generator import ( # noqa: F401 add_global, add_library, add_platformio_option, + define_entity, get_variable, get_variable_with_full_id, is_template, @@ -33,6 +34,7 @@ from esphome.cpp_generator import ( # noqa: F401 new_variable, process_lambda, progmem_array, + register_entity, safe_exp, statement, static_const_array, diff --git a/esphome/components/alarm_control_panel/__init__.py b/esphome/components/alarm_control_panel/__init__.py index 8987d708fd..e7051d682c 100644 --- a/esphome/components/alarm_control_panel/__init__.py +++ b/esphome/components/alarm_control_panel/__init__.py @@ -206,7 +206,7 @@ async def setup_alarm_control_panel_core_(var, config): async def register_alarm_control_panel(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_alarm_control_panel(var)) + cg.register_entity(AlarmControlPanel, var) await setup_alarm_control_panel_core_(var, config) @@ -315,3 +315,4 @@ async def alarm_control_panel_is_armed_to_code( async def to_code(config): cg.add_global(alarm_control_panel_ns.using) cg.add_define("USE_ALARM_CONTROL_PANEL") + cg.define_entity(AlarmControlPanel) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 95fd17bcc0..9fb5d52c69 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -551,7 +551,7 @@ async def setup_binary_sensor_core_(var, config): async def register_binary_sensor(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_binary_sensor(var)) + cg.register_entity(BinarySensor, var) await setup_binary_sensor_core_(var, config) @@ -588,3 +588,4 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args) async def to_code(config): cg.add_define("USE_BINARY_SENSOR") cg.add_global(binary_sensor_ns.using) + cg.define_entity(BinarySensor) diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py index 3010d3006a..4389aeeeb0 100644 --- a/esphome/components/button/__init__.py +++ b/esphome/components/button/__init__.py @@ -105,7 +105,7 @@ async def setup_button_core_(var, config): async def register_button(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_button(var)) + cg.register_entity(Button, var) await setup_button_core_(var, config) @@ -132,3 +132,4 @@ async def button_press_to_code(config, action_id, template_arg, args): async def to_code(config): cg.add_global(button_ns.using) cg.add_define("USE_BUTTON") + cg.define_entity(Button) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index c7e4ce7745..a6021eacb4 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -416,7 +416,7 @@ async def setup_climate_core_(var, config): async def register_climate(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_climate(var)) + cg.register_entity(Climate, var) await setup_climate_core_(var, config) @@ -485,3 +485,4 @@ async def climate_control_to_code(config, action_id, template_arg, args): async def to_code(config): cg.add_define("USE_CLIMATE") cg.add_global(climate_ns.using) + cg.define_entity(Climate) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index d25dd91148..74cde88014 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -160,7 +160,7 @@ async def setup_cover_core_(var, config): async def register_cover(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_cover(var)) + cg.register_entity(Cover, var) await setup_cover_core_(var, config) @@ -229,3 +229,4 @@ async def cover_control_to_code(config, action_id, template_arg, args): async def to_code(config): cg.add_define("USE_COVER") cg.add_global(cover_ns.using) + cg.define_entity(Cover) diff --git a/esphome/components/datetime/__init__.py b/esphome/components/datetime/__init__.py index 4fda97c5bc..fbef36275d 100644 --- a/esphome/components/datetime/__init__.py +++ b/esphome/components/datetime/__init__.py @@ -151,9 +151,12 @@ async def setup_datetime_core_(var, config): async def register_datetime(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(getattr(cg.App, f"register_{config[CONF_TYPE].lower()}")(var)) + entity = f"datetime::{str(config[CONF_ID].type).split('Template')[1]}Entity" + print(entity) + cg.register_entity(entity, var) await setup_datetime_core_(var, config) cg.add_define(f"USE_DATETIME_{config[CONF_TYPE]}") + cg.define_entity(entity) async def new_datetime(config, *args): diff --git a/esphome/components/event/__init__.py b/esphome/components/event/__init__.py index 031a4c0de8..9deee8f90b 100644 --- a/esphome/components/event/__init__.py +++ b/esphome/components/event/__init__.py @@ -101,7 +101,7 @@ async def setup_event_core_(var, config, *, event_types: list[str]): async def register_event(var, config, *, event_types: list[str]): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_event(var)) + cg.register_entity(Event, var) await setup_event_core_(var, config, event_types=event_types) @@ -132,3 +132,4 @@ async def event_fire_to_code(config, action_id, template_arg, args): async def to_code(config): cg.add_define("USE_EVENT") cg.add_global(event_ns.using) + cg.define_entity(Event) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 62624ec6e3..2a8775ca6e 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -248,7 +248,7 @@ async def setup_fan_core_(var, config): async def register_fan(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_fan(var)) + cg.register_entity(Fan, var) await setup_fan_core_(var, config) @@ -352,3 +352,4 @@ async def fan_is_on_off_to_code(config, condition_id, template_arg, args): async def to_code(config): cg.add_define("USE_FAN") cg.add_global(fan_ns.using) + cg.define_entity(Fan) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index d9f139d2f4..517506620b 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -188,7 +188,7 @@ async def setup_light_core_(light_var, output_var, config): async def register_light(output_var, config): light_var = cg.new_Pvariable(config[CONF_ID], output_var) - cg.add(cg.App.register_light(light_var)) + cg.register_entity(LightState, light_var) await cg.register_component(light_var, config) await setup_light_core_(light_var, output_var, config) @@ -197,3 +197,4 @@ async def register_light(output_var, config): async def to_code(config): cg.add_define("USE_LIGHT") cg.add_global(light_ns.using) + cg.define_entity(LightState) diff --git a/esphome/components/lock/__init__.py b/esphome/components/lock/__init__.py index 6b92bc264b..fd1d36d200 100644 --- a/esphome/components/lock/__init__.py +++ b/esphome/components/lock/__init__.py @@ -74,7 +74,7 @@ async def setup_lock_core_(var, config): async def register_lock(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_lock(var)) + cg.register_entity(Lock, var) await setup_lock_core_(var, config) @@ -109,3 +109,4 @@ async def lock_is_off_to_code(config, condition_id, template_arg, args): async def to_code(config): cg.add_global(lock_ns.using) cg.add_define("USE_LOCK") + cg.define_entity(Lock) diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index 423cb065dc..bfadd98439 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -85,7 +85,7 @@ async def setup_media_player_core_(var, config): async def register_media_player(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_media_player(var)) + cg.register_entity(MediaPlayer, var) await setup_media_player_core_(var, config) @@ -191,3 +191,4 @@ async def media_player_volume_set_action(config, action_id, template_arg, args): async def to_code(config): cg.add_global(media_player_ns.using) cg.add_define("USE_MEDIA_PLAYER") + cg.define_entity(MediaPlayer) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index ece738af49..cb12bc739a 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -265,7 +265,7 @@ async def register_number( ): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_number(var)) + cg.register_entity(Number, var) await setup_number_core_( var, config, min_value=min_value, max_value=max_value, step=step ) @@ -308,6 +308,7 @@ async def number_in_range_to_code(config, condition_id, template_arg, args): async def to_code(config): cg.add_define("USE_NUMBER") cg.add_global(number_ns.using) + cg.define_entity(Number) OPERATION_BASE_SCHEMA = cv.Schema( diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 2bc68d43ec..1b6df1c0b6 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -112,7 +112,7 @@ async def setup_select_core_(var, config, *, options: list[str]): async def register_select(var, config, *, options: list[str]): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_select(var)) + cg.register_entity(Select, var) await setup_select_core_(var, config, options=options) @@ -126,6 +126,7 @@ async def new_select(config, *, options: list[str]): async def to_code(config): cg.add_define("USE_SELECT") cg.add_global(select_ns.using) + cg.define_entity(Select) OPERATION_BASE_SCHEMA = cv.Schema( diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 867cdc1f48..4525b1dc86 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -808,7 +808,7 @@ async def setup_sensor_core_(var, config): async def register_sensor(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_sensor(var)) + cg.register_entity(Sensor, var) await setup_sensor_core_(var, config) @@ -948,3 +948,4 @@ def _lstsq(a, b): async def to_code(config): cg.add_define("USE_SENSOR") cg.add_global(sensor_ns.using) + cg.define_entity(Sensor) diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index fef4f7f007..dfa6db7a2f 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -169,7 +169,7 @@ async def setup_switch_core_(var, config): async def register_switch(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_switch(var)) + cg.register_entity(Switch, var) await setup_switch_core_(var, config) @@ -210,3 +210,4 @@ async def switch_is_off_to_code(config, condition_id, template_arg, args): async def to_code(config): cg.add_global(switch_ns.using) cg.add_define("USE_SWITCH") + cg.define_entity(Switch) diff --git a/esphome/components/text/__init__.py b/esphome/components/text/__init__.py index 386baaf756..8f574d29ba 100644 --- a/esphome/components/text/__init__.py +++ b/esphome/components/text/__init__.py @@ -97,7 +97,7 @@ async def register_text( ): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_text(var)) + cg.register_entity(Text, var) await setup_text_core_( var, config, min_length=min_length, max_length=max_length, pattern=pattern ) @@ -121,6 +121,7 @@ async def new_text( async def to_code(config): cg.add_define("USE_TEXT") cg.add_global(text_ns.using) + cg.define_entity(Text) OPERATION_BASE_SCHEMA = cv.Schema( diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index ba8a2def41..4073f67b17 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -220,7 +220,7 @@ async def setup_text_sensor_core_(var, config): async def register_text_sensor(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_text_sensor(var)) + cg.register_entity(TextSensor, var) await setup_text_sensor_core_(var, config) @@ -234,6 +234,7 @@ async def new_text_sensor(config, *args): async def to_code(config): cg.add_define("USE_TEXT_SENSOR") cg.add_global(text_sensor_ns.using) + cg.define_entity(TextSensor) @automation.register_condition( diff --git a/esphome/components/update/__init__.py b/esphome/components/update/__init__.py index ba3b2f20df..75aaaa5a1e 100644 --- a/esphome/components/update/__init__.py +++ b/esphome/components/update/__init__.py @@ -81,7 +81,7 @@ async def setup_update_core_(var, config): async def register_update(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_update(var)) + cg.register_entity(UpdateEntity, var) await setup_update_core_(var, config) @@ -95,6 +95,7 @@ async def new_update(config): async def to_code(config): cg.add_define("USE_UPDATE") cg.add_global(update_ns.using) + cg.define_entity(UpdateEntity) @automation.register_action( diff --git a/esphome/components/valve/__init__.py b/esphome/components/valve/__init__.py index 3c03bab857..a3c0f7b69f 100644 --- a/esphome/components/valve/__init__.py +++ b/esphome/components/valve/__init__.py @@ -132,7 +132,7 @@ async def setup_valve_core_(var, config): async def register_valve(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) - cg.add(cg.App.register_valve(var)) + cg.register_entity(Valve, var) await setup_valve_core_(var, config) @@ -203,3 +203,4 @@ async def valve_control_to_code(config, action_id, template_arg, args): async def to_code(config): cg.add_define("USE_VALVE") cg.add_global(valve_ns.using) + cg.define_entity(Valve) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index a97c3b18c9..5878496ce3 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -529,6 +529,8 @@ class EsphomeCore: self.verbose = False # Whether ESPHome was started in quiet mode self.quiet = False + # A set of available entities + self.entities: set[str] = set() def reset(self): from esphome.pins import PIN_SCHEMA_REGISTRY @@ -553,6 +555,7 @@ class EsphomeCore: self.loaded_integrations = set() self.component_ids = set() PIN_SCHEMA_REGISTRY.reset() + self.entities = set() @property def address(self) -> Optional[str]: @@ -779,6 +782,9 @@ class EsphomeCore: _LOGGER.debug("Adding define: %s", define) return define + def define_entity(self, class_): + self.entities.add(class_) + def add_platformio_option(self, key: str, value: Union[str, list[str]]) -> None: new_val = value old_val = self.platformio_options.get(key) diff --git a/esphome/core/application.h b/esphome/core/application.h index 2697357456..e373bd20ed 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -72,10 +72,16 @@ #ifdef USE_UPDATE #include "esphome/components/update/update_entity.h" #endif +#include "esphome/core/entities.h" namespace esphome { class Application { + template struct entityRegistry; + template struct entityRegistry> { + using type = std::tuple...>; + }; + public: void pre_setup(const std::string &name, const std::string &friendly_name, const std::string &area, const char *comment, const char *compilation_time, bool name_add_mac_suffix) { @@ -97,93 +103,22 @@ class Application { this->compilation_time_ = compilation_time; } -#ifdef USE_BINARY_SENSOR - void register_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { - this->binary_sensors_.push_back(binary_sensor); + template void register_entity(Entity *entity) { + get_by_type>(entities_).push_back(entity); } -#endif -#ifdef USE_SENSOR - void register_sensor(sensor::Sensor *sensor) { this->sensors_.push_back(sensor); } -#endif - -#ifdef USE_SWITCH - void register_switch(switch_::Switch *a_switch) { this->switches_.push_back(a_switch); } -#endif - -#ifdef USE_BUTTON - void register_button(button::Button *button) { this->buttons_.push_back(button); } -#endif - -#ifdef USE_TEXT_SENSOR - void register_text_sensor(text_sensor::TextSensor *sensor) { this->text_sensors_.push_back(sensor); } -#endif - -#ifdef USE_FAN - void register_fan(fan::Fan *state) { this->fans_.push_back(state); } -#endif - -#ifdef USE_COVER - void register_cover(cover::Cover *cover) { this->covers_.push_back(cover); } -#endif - -#ifdef USE_CLIMATE - void register_climate(climate::Climate *climate) { this->climates_.push_back(climate); } -#endif - -#ifdef USE_LIGHT - void register_light(light::LightState *light) { this->lights_.push_back(light); } -#endif - -#ifdef USE_NUMBER - void register_number(number::Number *number) { this->numbers_.push_back(number); } -#endif - -#ifdef USE_DATETIME_DATE - void register_date(datetime::DateEntity *date) { this->dates_.push_back(date); } -#endif - -#ifdef USE_DATETIME_TIME - void register_time(datetime::TimeEntity *time) { this->times_.push_back(time); } -#endif - -#ifdef USE_DATETIME_DATETIME - void register_datetime(datetime::DateTimeEntity *datetime) { this->datetimes_.push_back(datetime); } -#endif - -#ifdef USE_TEXT - void register_text(text::Text *text) { this->texts_.push_back(text); } -#endif - -#ifdef USE_SELECT - void register_select(select::Select *select) { this->selects_.push_back(select); } -#endif - -#ifdef USE_LOCK - void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); } -#endif - -#ifdef USE_VALVE - void register_valve(valve::Valve *valve) { this->valves_.push_back(valve); } -#endif - -#ifdef USE_MEDIA_PLAYER - void register_media_player(media_player::MediaPlayer *media_player) { this->media_players_.push_back(media_player); } -#endif - -#ifdef USE_ALARM_CONTROL_PANEL - void register_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { - this->alarm_control_panels_.push_back(a_alarm_control_panel); + template const std::vector &get_entities() { + return get_by_type>(entities_); } -#endif -#ifdef USE_EVENT - void register_event(event::Event *event) { this->events_.push_back(event); } -#endif - -#ifdef USE_UPDATE - void register_update(update::UpdateEntity *update) { this->updates_.push_back(update); } -#endif + template Entity *get_entity_by_key(uint32_t key, bool include_internal) { + for (auto *obj : this->get_entities()) { + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) { + return obj; + } + } + return nullptr; + } /// Register the component in this Application instance. template C *register_component(C *c) { @@ -244,197 +179,142 @@ class Application { uint32_t get_app_state() const { return this->app_state_; } #ifdef USE_BINARY_SENSOR - const std::vector &get_binary_sensors() { return this->binary_sensors_; } + const std::vector &get_binary_sensors() { + return this->get_entities(); + } binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->binary_sensors_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_SWITCH - const std::vector &get_switches() { return this->switches_; } + const std::vector &get_switches() { return this->get_entities(); } switch_::Switch *get_switch_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->switches_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_BUTTON - const std::vector &get_buttons() { return this->buttons_; } + const std::vector &get_buttons() { return this->get_entities(); } button::Button *get_button_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->buttons_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_SENSOR - const std::vector &get_sensors() { return this->sensors_; } + const std::vector &get_sensors() { return this->get_entities(); } sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->sensors_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_TEXT_SENSOR - const std::vector &get_text_sensors() { return this->text_sensors_; } + const std::vector &get_text_sensors() { + return this->get_entities(); + } text_sensor::TextSensor *get_text_sensor_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->text_sensors_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_FAN - const std::vector &get_fans() { return this->fans_; } + const std::vector &get_fans() { return this->get_entities(); } fan::Fan *get_fan_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->fans_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_COVER - const std::vector &get_covers() { return this->covers_; } + const std::vector &get_covers() { return this->get_entities(); } cover::Cover *get_cover_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->covers_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_LIGHT - const std::vector &get_lights() { return this->lights_; } + const std::vector &get_lights() { return this->get_entities(); } light::LightState *get_light_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->lights_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_CLIMATE - const std::vector &get_climates() { return this->climates_; } + const std::vector &get_climates() { return this->get_entities(); } climate::Climate *get_climate_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->climates_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_NUMBER - const std::vector &get_numbers() { return this->numbers_; } + const std::vector &get_numbers() { return this->get_entities(); } number::Number *get_number_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->numbers_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_DATETIME_DATE - const std::vector &get_dates() { return this->dates_; } + const std::vector &get_dates() { return this->get_entities(); } datetime::DateEntity *get_date_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->dates_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_DATETIME_TIME - const std::vector &get_times() { return this->times_; } + const std::vector &get_times() { return this->get_entities(); } datetime::TimeEntity *get_time_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->times_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_DATETIME_DATETIME - const std::vector &get_datetimes() { return this->datetimes_; } + const std::vector &get_datetimes() { + return this->get_entities(); + } datetime::DateTimeEntity *get_datetime_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->datetimes_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_TEXT - const std::vector &get_texts() { return this->texts_; } + const std::vector &get_texts() { return this->get_entities(); } text::Text *get_text_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->texts_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_SELECT - const std::vector &get_selects() { return this->selects_; } + const std::vector &get_selects() { return this->get_entities(); } select::Select *get_select_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->selects_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_LOCK - const std::vector &get_locks() { return this->locks_; } + const std::vector &get_locks() { return this->get_entities(); } lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->locks_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_VALVE - const std::vector &get_valves() { return this->valves_; } + const std::vector &get_valves() { return this->get_entities(); } valve::Valve *get_valve_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->valves_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_MEDIA_PLAYER - const std::vector &get_media_players() { return this->media_players_; } + const std::vector &get_media_players() { + return this->get_entities(); + } media_player::MediaPlayer *get_media_player_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->media_players_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_ALARM_CONTROL_PANEL const std::vector &get_alarm_control_panels() { - return this->alarm_control_panels_; + return this->get_entities(); } alarm_control_panel::AlarmControlPanel *get_alarm_control_panel_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->alarm_control_panels_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_EVENT - const std::vector &get_events() { return this->events_; } + const std::vector &get_events() { return this->get_entities(); } event::Event *get_event_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->events_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif #ifdef USE_UPDATE - const std::vector &get_updates() { return this->updates_; } + const std::vector &get_updates() { return this->get_entities(); } update::UpdateEntity *get_update_by_key(uint32_t key, bool include_internal = false) { - for (auto *obj : this->updates_) - if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) - return obj; - return nullptr; + return this->get_entity_by_key(key, include_internal); } #endif @@ -452,70 +332,6 @@ class Application { std::vector components_{}; std::vector looping_components_{}; -#ifdef USE_BINARY_SENSOR - std::vector binary_sensors_{}; -#endif -#ifdef USE_SWITCH - std::vector switches_{}; -#endif -#ifdef USE_BUTTON - std::vector buttons_{}; -#endif -#ifdef USE_EVENT - std::vector events_{}; -#endif -#ifdef USE_SENSOR - std::vector sensors_{}; -#endif -#ifdef USE_TEXT_SENSOR - std::vector text_sensors_{}; -#endif -#ifdef USE_FAN - std::vector fans_{}; -#endif -#ifdef USE_COVER - std::vector covers_{}; -#endif -#ifdef USE_CLIMATE - std::vector climates_{}; -#endif -#ifdef USE_LIGHT - std::vector lights_{}; -#endif -#ifdef USE_NUMBER - std::vector numbers_{}; -#endif -#ifdef USE_DATETIME_DATE - std::vector dates_{}; -#endif -#ifdef USE_DATETIME_TIME - std::vector times_{}; -#endif -#ifdef USE_DATETIME_DATETIME - std::vector datetimes_{}; -#endif -#ifdef USE_SELECT - std::vector selects_{}; -#endif -#ifdef USE_TEXT - std::vector texts_{}; -#endif -#ifdef USE_LOCK - std::vector locks_{}; -#endif -#ifdef USE_VALVE - std::vector valves_{}; -#endif -#ifdef USE_MEDIA_PLAYER - std::vector media_players_{}; -#endif -#ifdef USE_ALARM_CONTROL_PANEL - std::vector alarm_control_panels_{}; -#endif -#ifdef USE_UPDATE - std::vector updates_{}; -#endif - std::string name_; std::string friendly_name_; std::string area_; @@ -526,6 +342,7 @@ class Application { uint32_t loop_interval_{16}; size_t dump_config_at_{SIZE_MAX}; uint32_t app_state_{0}; + entityRegistry::type entities_; }; /// Global storage of Application pointer - only one Application can exist. diff --git a/esphome/core/entities.h b/esphome/core/entities.h new file mode 100644 index 0000000000..85cc9f81ad --- /dev/null +++ b/esphome/core/entities.h @@ -0,0 +1,78 @@ +#pragma once + +// This file is not used by the runtime, instead, a version is generated during +// compilation with only the relevant feature flags for the current build. +// +// This file is only used by static analyzers and IDEs. + +namespace esphome { +namespace update { +class UpdateEntity; +} +namespace alarm_control_panel { +class AlarmControlPanel; +} +namespace number { +class Number; +} +namespace light { +class LightState; +} +namespace switch_ { +class Switch; +} +namespace climate { +class Climate; +} +namespace fan { +class Fan; +} +namespace datetime { +class DateTimeEntity; +} +namespace binary_sensor { +class BinarySensor; +} +namespace datetime { +class TimeEntity; +} +namespace text { +class Text; +} +namespace lock { +class Lock; +} +namespace event { +class Event; +} +namespace text_sensor { +class TextSensor; +} +namespace datetime { +class DateEntity; +} +namespace cover { +class Cover; +} +namespace sensor { +class Sensor; +} +namespace select { +class Select; +} +namespace valve { +class Valve; +} +namespace button { +class Button; +} +namespace media_player { +class MediaPlayer; +} +using entities_t = + std::tuple; +} // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 3e6fe9433e..516e1d3b1c 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -715,4 +715,22 @@ std::string hexencode(const T &data) { ///@} +namespace detail { + +// Helper struct to get the index of a type T in a parameter pack +template struct index_of; + +template struct index_of { + static constexpr size_t value = 0; // NOLINT(readability-identifier-naming) +}; + +template struct index_of { + static constexpr size_t value = 1 + index_of::value; // NOLINT(readability-identifier-naming) +}; +} // end namespace detail + +template T &get_by_type(std::tuple &tup) { + return std::get::value>(tup); +} + } // namespace esphome diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 7a82d5cba1..2a32ffa603 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -619,6 +619,16 @@ def add_define(name: str, value: SafeExpType = None): CORE.add_define(Define(name, safe_exp(value))) +def register_entity(class_, var): + """Add an entity to the codegen section.""" + add(MockObj(f"App.register_entity<{class_}>")(var)) + + +def define_entity(class_): + """Add a entity to std::tuple inside the auto-generated entities.h file.""" + CORE.define_entity(str(class_)) + + def add_platformio_option(key: str, value: Union[str, list[str]]): CORE.add_platformio_option(key, value) diff --git a/esphome/writer.py b/esphome/writer.py index c6111cbe3f..fd10aeba01 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -216,8 +216,16 @@ VERSION_H_FORMAT = """\ #define ESPHOME_VERSION "{}" #define ESPHOME_VERSION_CODE VERSION_CODE({}, {}, {}) """ +ENTITIES_H_FORMAT = """\ +#pragma once +namespace esphome {{ +{} +using entities_t = std::tuple<{}>; +}} +""" DEFINES_H_TARGET = "esphome/core/defines.h" VERSION_H_TARGET = "esphome/core/version.h" +ENTITIES_H_TARGET = "esphome/core/entities.h" ESPHOME_README_TXT = """ THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY @@ -251,7 +259,9 @@ def copy_src_tree(): include_s = "\n".join(include_l) source_files_copy = source_files_map.copy() - ignore_targets = [Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET)] + ignore_targets = [ + Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET, ENTITIES_H_TARGET) + ] for t in ignore_targets: source_files_copy.pop(t) @@ -290,6 +300,9 @@ def copy_src_tree(): write_file_if_changed( CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() ) + write_file_if_changed( + CORE.relative_src_path("esphome", "core", "entities.h"), generate_entities_h() + ) if CORE.is_esp32: from esphome.components.esp32 import copy_files @@ -327,6 +340,20 @@ def generate_version_h(): ) +def generate_entities_h(): + forward_declaration_l = [] + entities_l = [] + for type in CORE.entities: + namespace = type.split("::")[0] + class_ = type.split("::")[1] + forward_declaration_l += [f"""namespace {namespace} {{class {class_}; }}"""] + entities_l += [f"""{type} *"""] + + return ENTITIES_H_FORMAT.format( + "\n".join(forward_declaration_l), ", ".join(entities_l) + ) + + def write_cpp(code_s): path = CORE.relative_src_path("main.cpp") if os.path.isfile(path): diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.py b/tests/component_tests/binary_sensor/test_binary_sensor.py index 514bc6ee5f..8048503c05 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.py +++ b/tests/component_tests/binary_sensor/test_binary_sensor.py @@ -14,7 +14,7 @@ def test_binary_sensor_is_setup(generate_main): # Then assert "new gpio::GPIOBinarySensor();" in main_cpp - assert "App.register_binary_sensor" in main_cpp + assert "App.register_entity" in main_cpp def test_binary_sensor_sets_mandatory_fields(generate_main): diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py index 512ef42b44..fe69526c6e 100644 --- a/tests/component_tests/button/test_button.py +++ b/tests/component_tests/button/test_button.py @@ -12,7 +12,7 @@ def test_button_is_setup(generate_main): # Then assert "new wake_on_lan::WakeOnLanButton();" in main_cpp - assert "App.register_button" in main_cpp + assert "App.register_entity" in main_cpp assert "App.register_component" in main_cpp diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index 51fcb3d382..5cb3adb459 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -12,7 +12,7 @@ def test_text_is_setup(generate_main): # Then assert "new template_::TemplateText();" in main_cpp - assert "App.register_text" in main_cpp + assert "App.register_entity" in main_cpp def test_text_sets_mandatory_fields(generate_main): diff --git a/tests/component_tests/text_sensor/test_text_sensor.py b/tests/component_tests/text_sensor/test_text_sensor.py index 1c4ef6633d..2cc70cc7b1 100644 --- a/tests/component_tests/text_sensor/test_text_sensor.py +++ b/tests/component_tests/text_sensor/test_text_sensor.py @@ -12,7 +12,7 @@ def test_text_sensor_is_setup(generate_main): # Then assert "new template_::TemplateTextSensor();" in main_cpp - assert "App.register_text_sensor" in main_cpp + assert "App.register_entity" in main_cpp def test_text_sensor_sets_mandatory_fields(generate_main):