From 4f08f0750a66299d4de5e562b4b406ce14bf22f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Nov 2025 22:34:53 -0600 Subject: [PATCH 01/22] [ai_instructions] Add public API and breaking changes guidelines (#11756) --- .ai/instructions.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.ai/instructions.md b/.ai/instructions.md index 9309c67c65..8d81c6cf0f 100644 --- a/.ai/instructions.md +++ b/.ai/instructions.md @@ -440,3 +440,45 @@ This document provides essential context for AI models interacting with this pro * **Python:** When adding a new Python dependency, add it to the appropriate `requirements*.txt` file and `pyproject.toml`. * **C++ / PlatformIO:** When adding a new C++ dependency, add it to `platformio.ini` and use `cg.add_library`. * **Build Flags:** Use `cg.add_build_flag(...)` to add compiler flags. + +## 8. Public API and Breaking Changes + +* **Public C++ API:** + * **Components**: Only documented features at [esphome.io](https://esphome.io) are public API. Undocumented `public` members are internal. + * **Core/Base Classes** (`esphome/core/`, `Component`, `Sensor`, etc.): All `public` members are public API. + * **Components with Global Accessors** (`global_api_server`, etc.): All `public` members are public API (except config setters). + +* **Public Python API:** + * All documented configuration options at [esphome.io](https://esphome.io) are public API. + * Python code in `esphome/core/` actively used by existing core components is considered stable API. + * Other Python code is internal unless explicitly documented for external component use. + +* **Breaking Changes Policy:** + * Aim for **6-month deprecation window** when possible + * Clean breaks allowed for: signature changes, deep refactorings, resource constraints + * Must document migration path in PR description (generates release notes) + * Blog post required for core/base class changes or significant architectural changes + * Full details: https://developers.esphome.io/contributing/code/#public-api-and-breaking-changes + +* **Breaking Change Checklist:** + - [ ] Clear justification (RAM/flash savings, architectural improvement) + - [ ] Explored non-breaking alternatives + - [ ] Added deprecation warnings if possible (use `ESPDEPRECATED` macro for C++) + - [ ] Documented migration path in PR description with before/after examples + - [ ] Updated all internal usage and esphome-docs + - [ ] Tested backward compatibility during deprecation period + +* **Deprecation Pattern (C++):** + ```cpp + // Remove before 2026.6.0 + ESPDEPRECATED("Use new_method() instead. Removed in 2026.6.0", "2025.12.0") + void old_method() { this->new_method(); } + ``` + +* **Deprecation Pattern (Python):** + ```python + # Remove before 2026.6.0 + if CONF_OLD_KEY in config: + _LOGGER.warning(f"'{CONF_OLD_KEY}' deprecated, use '{CONF_NEW_KEY}'. Removed in 2026.6.0") + config[CONF_NEW_KEY] = config.pop(CONF_OLD_KEY) # Auto-migrate + ``` From 85d2565f25057cba2ca362fa2c40db5122f212b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 01:18:43 -0600 Subject: [PATCH 02/22] [tests] Fix determine_jobs tests failing when target branch is beta (#11758) --- tests/script/test_determine_jobs.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index 4894a5e28a..cadc7f9cd7 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -70,6 +70,13 @@ def mock_changed_files() -> Generator[Mock, None, None]: yield mock +@pytest.fixture +def mock_target_branch_dev() -> Generator[Mock, None, None]: + """Mock get_target_branch to return 'dev' for memory impact tests.""" + with patch.object(determine_jobs, "get_target_branch", return_value="dev") as mock: + yield mock + + @pytest.fixture(autouse=True) def clear_determine_jobs_caches() -> None: """Clear all cached functions before each test.""" @@ -688,6 +695,7 @@ def test_main_detects_components_with_variant_tests( # Tests for detect_memory_impact_config function +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_with_common_platform(tmp_path: Path) -> None: """Test memory impact detection when components share a common platform.""" # Create test directory structure @@ -722,6 +730,7 @@ def test_detect_memory_impact_config_with_common_platform(tmp_path: Path) -> Non assert result["use_merged_config"] == "true" +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_core_only_changes(tmp_path: Path) -> None: """Test memory impact detection with core C++ changes (no component changes).""" # Create test directory structure with fallback component @@ -779,6 +788,7 @@ def test_detect_memory_impact_config_core_python_only_changes(tmp_path: Path) -> assert result["should_run"] == "false" +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_no_common_platform(tmp_path: Path) -> None: """Test memory impact detection when components have no common platform.""" # Create test directory structure @@ -855,6 +865,7 @@ def test_detect_memory_impact_config_no_components_with_tests(tmp_path: Path) -> assert result["should_run"] == "false" +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_includes_base_bus_components( tmp_path: Path, ) -> None: @@ -897,6 +908,7 @@ def test_detect_memory_impact_config_includes_base_bus_components( assert result["platform"] == "esp32-idf" # Common platform +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_with_variant_tests(tmp_path: Path) -> None: """Test memory impact detection for components with only variant test files. @@ -1125,6 +1137,7 @@ def test_main_core_files_changed_still_detects_components( assert len(output["changed_components"]) > 0 +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_filters_incompatible_esp32_on_esp8266( tmp_path: Path, ) -> None: @@ -1178,6 +1191,7 @@ def test_detect_memory_impact_config_filters_incompatible_esp32_on_esp8266( assert result["use_merged_config"] == "true" +@pytest.mark.usefixtures("mock_target_branch_dev") def test_detect_memory_impact_config_filters_incompatible_esp8266_on_esp32( tmp_path: Path, ) -> None: From a5bf55b6acbf6c1715f20487c167a7ff2593ed2a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 01:19:45 -0600 Subject: [PATCH 03/22] =?UTF-8?q?[ci]=20Fix=20component=20batching=20for?= =?UTF-8?q?=20beta/release=20branches=20(3-4=20=E2=86=92=2040=20per=20batc?= =?UTF-8?q?h)=20(#11759)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/determine-jobs.py | 18 +++- tests/script/test_determine_jobs.py | 148 ++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 39a7571fbe..5cc3f2570a 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -756,11 +756,27 @@ def main() -> None: component_test_batches: list[str] if changed_components_with_tests: tests_dir = Path(root_path) / ESPHOME_TESTS_COMPONENTS_PATH + + # For beta/release branches, group all components for faster CI + # (no isolation, all components are groupable) + target_branch = get_target_branch() + is_release_branch = target_branch and ( + target_branch.startswith("release") or target_branch.startswith("beta") + ) + + if is_release_branch: + # For beta/release: Don't isolate any components - group everything + # This allows components to be merged into single builds + batch_directly_changed = set() # Empty set - no isolation + else: + # Normal PR: only directly changed components are isolated + batch_directly_changed = directly_changed_with_tests + batches, _ = create_intelligent_batches( components=changed_components_with_tests, tests_dir=tests_dir, batch_size=COMPONENT_TEST_BATCH_SIZE, - directly_changed=directly_changed_with_tests, + directly_changed=batch_directly_changed, ) # Convert batches to space-separated strings for CI matrix component_test_batches = [" ".join(batch) for batch in batches] diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index cadc7f9cd7..291a23967b 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -19,6 +19,8 @@ sys.path.insert(0, script_dir) # Import helpers module for patching import helpers # noqa: E402 +import script.helpers # noqa: E402 + spec = importlib.util.spec_from_file_location( "determine_jobs", os.path.join(script_dir, "determine-jobs.py") ) @@ -132,6 +134,16 @@ def test_main_all_tests_should_run( ["wifi", "api"] if not deps else ["wifi", "api", "sensor"] ), ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + patch.object( + determine_jobs, + "create_intelligent_batches", + return_value=([["wifi", "api", "sensor"]], {}), + ), ): determine_jobs.main() @@ -203,6 +215,16 @@ def test_main_no_tests_should_run( patch.object( determine_jobs, "get_components_with_dependencies", return_value=[] ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + patch.object( + determine_jobs, + "create_intelligent_batches", + return_value=([], {}), + ), ): determine_jobs.main() @@ -266,6 +288,16 @@ def test_main_with_branch_argument( patch.object( determine_jobs, "get_components_with_dependencies", return_value=["mqtt"] ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + patch.object( + determine_jobs, + "create_intelligent_batches", + return_value=([["mqtt"]], {}), + ), ): determine_jobs.main() @@ -571,6 +603,11 @@ def test_main_filters_components_without_tests( ), ), patch.object(determine_jobs, "changed_files", return_value=[]), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), ): # Clear the cache since we're mocking root_path determine_jobs.main() @@ -670,6 +707,11 @@ def test_main_detects_components_with_variant_tests( ), ), patch.object(determine_jobs, "changed_files", return_value=[]), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), ): # Clear the cache since we're mocking root_path determine_jobs.main() @@ -1124,6 +1166,16 @@ def test_main_core_files_changed_still_detects_components( else ["select", "api", "bluetooth_proxy", "logger"] ), ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + patch.object( + determine_jobs, + "create_intelligent_batches", + return_value=([["select", "api", "bluetooth_proxy", "logger"]], {}), + ), ): determine_jobs.main() @@ -1367,3 +1419,99 @@ def test_detect_memory_impact_config_runs_at_component_limit(tmp_path: Path) -> # Memory impact should run at exactly 40 components (at limit but not over) assert result["should_run"] == "true" assert len(result["components"]) == 40 + + +def test_component_batching_beta_branch_40_per_batch( + tmp_path: Path, + mock_should_run_integration_tests: Mock, + mock_should_run_clang_tidy: Mock, + mock_should_run_clang_format: Mock, + mock_should_run_python_linters: Mock, + mock_changed_files: Mock, + mock_determine_cpp_unit_tests: Mock, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test that beta/release branches create batches with 40 actual components each. + + For beta/release branches, all components should be groupable (not isolated), + and each batch should contain 40 actual components with weight 1 each. + This matches the original behavior before consolidation. + """ + # Create 120 test components with test files + component_names = [f"comp_{i:03d}" for i in range(120)] + tests_dir = tmp_path / "tests" / "components" + + for comp in component_names: + comp_dir = tests_dir / comp + comp_dir.mkdir(parents=True) + (comp_dir / "test.esp32-idf.yaml").write_text(f"# Test for {comp}") + + # Setup mocks + mock_should_run_integration_tests.return_value = False + mock_should_run_clang_tidy.return_value = False + mock_should_run_clang_format.return_value = False + mock_should_run_python_linters.return_value = False + mock_determine_cpp_unit_tests.return_value = (False, []) + + # Mock changed_files to return all component files + changed_files = [ + f"esphome/components/{comp}/{comp}.cpp" for comp in component_names + ] + mock_changed_files.return_value = changed_files + + # Run main function with beta branch + # Don't mock create_intelligent_batches - that's what we're testing! + with ( + patch("sys.argv", ["determine-jobs.py", "--branch", "beta"]), + patch.object(determine_jobs, "root_path", str(tmp_path)), + patch.object(helpers, "root_path", str(tmp_path)), + patch.object(script.helpers, "root_path", str(tmp_path)), + patch.object(determine_jobs, "get_target_branch", return_value="beta"), + patch.object(determine_jobs, "_is_clang_tidy_full_scan", return_value=False), + patch.object( + determine_jobs, + "get_changed_components", + return_value=component_names, + ), + patch.object( + determine_jobs, + "filter_component_and_test_files", + side_effect=lambda f: f.startswith("esphome/components/"), + ), + patch.object( + determine_jobs, + "get_components_with_dependencies", + side_effect=lambda files, deps: component_names, + ), + patch.object( + determine_jobs, + "detect_memory_impact_config", + return_value={"should_run": "false"}, + ), + ): + determine_jobs.main() + + # Check output + captured = capsys.readouterr() + output = json.loads(captured.out) + + # Verify batches are present and properly sized + assert "component_test_batches" in output + batches = output["component_test_batches"] + + # Should have 3 batches (120 components / 40 per batch = 3) + assert len(batches) == 3, f"Expected 3 batches, got {len(batches)}" + + # Each batch should have approximately 40 components (all weight=1, groupable) + for i, batch_str in enumerate(batches): + batch_components = batch_str.split() + assert len(batch_components) == 40, ( + f"Batch {i} should have 40 components, got {len(batch_components)}" + ) + + # Verify all 120 components are in batches + all_components = [] + for batch_str in batches: + all_components.extend(batch_str.split()) + assert len(all_components) == 120 + assert set(all_components) == set(component_names) From 79d1a558af778df183764b656a838ec650d9fb47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:12:15 +0000 Subject: [PATCH 04/22] Bump ruff from 0.14.3 to 0.14.4 (#11768) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5356bffd96..dab660b03f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.3 + rev: v0.14.4 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index 11367172b1..81cb711eec 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.2 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.3 # also change in .pre-commit-config.yaml when updating +ruff==0.14.4 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.0 # also change in .pre-commit-config.yaml when updating pre-commit From 295fe8da040bd822475e5b047f38154391a2954f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 15:32:46 -0600 Subject: [PATCH 05/22] controller registry phase1/2 --- esphome/components/api/api_server.cpp | 26 +- esphome/components/api/api_server.h | 12 +- esphome/components/web_server/web_server.cpp | 49 +++- esphome/components/web_server/web_server.h | 12 +- esphome/core/controller.h | 12 +- esphome/core/controller_registry.cpp | 177 +++++++++++++ esphome/core/controller_registry.h | 263 +++++++++++++++++++ 7 files changed, 509 insertions(+), 42 deletions(-) create mode 100644 esphome/core/controller_registry.cpp create mode 100644 esphome/core/controller_registry.h diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index e5f0d9795e..a08554acd8 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -4,6 +4,7 @@ #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" @@ -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 diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index f1f44a266d..4b03023957 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; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f1d1a75875..5c2596d731 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -3,6 +3,7 @@ #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" #include "esphome/core/application.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 +295,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 +431,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 +476,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 +518,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 +632,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 +673,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 +749,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 +824,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 +910,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 +978,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 +1035,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 +1091,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 +1147,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 +1203,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 +1264,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 +1407,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 +1480,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 +1563,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) { diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 328140cfae..8e74c42bff 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); diff --git a/esphome/core/controller.h b/esphome/core/controller.h index b475e326ee..a62e53d9fc 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -80,22 +80,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 +107,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){}; diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp new file mode 100644 index 0000000000..3223d92170 --- /dev/null +++ b/esphome/core/controller_registry.cpp @@ -0,0 +1,177 @@ +#include "esphome/core/controller_registry.h" +#include "esphome/core/controller.h" + +namespace esphome { + +std::vector ControllerRegistry::controllers_; + +void ControllerRegistry::register_controller(Controller *controller) { controllers_.push_back(controller); } + +void ControllerRegistry::unregister_controller(Controller *controller) { + auto it = std::find(controllers_.begin(), controllers_.end(), controller); + if (it != controllers_.end()) { + controllers_.erase(it); + } +} + +#ifdef USE_BINARY_SENSOR +void ControllerRegistry::notify_binary_sensor_update(binary_sensor::BinarySensor *obj) { + for (auto *controller : controllers_) { + controller->on_binary_sensor_update(obj); + } +} +#endif + +#ifdef USE_FAN +void ControllerRegistry::notify_fan_update(fan::Fan *obj) { + for (auto *controller : controllers_) { + controller->on_fan_update(obj); + } +} +#endif + +#ifdef USE_LIGHT +void ControllerRegistry::notify_light_update(light::LightState *obj) { + for (auto *controller : controllers_) { + controller->on_light_update(obj); + } +} +#endif + +#ifdef USE_SENSOR +void ControllerRegistry::notify_sensor_update(sensor::Sensor *obj) { + for (auto *controller : controllers_) { + controller->on_sensor_update(obj); + } +} +#endif + +#ifdef USE_SWITCH +void ControllerRegistry::notify_switch_update(switch_::Switch *obj) { + for (auto *controller : controllers_) { + controller->on_switch_update(obj); + } +} +#endif + +#ifdef USE_COVER +void ControllerRegistry::notify_cover_update(cover::Cover *obj) { + for (auto *controller : controllers_) { + controller->on_cover_update(obj); + } +} +#endif + +#ifdef USE_TEXT_SENSOR +void ControllerRegistry::notify_text_sensor_update(text_sensor::TextSensor *obj) { + for (auto *controller : controllers_) { + controller->on_text_sensor_update(obj); + } +} +#endif + +#ifdef USE_CLIMATE +void ControllerRegistry::notify_climate_update(climate::Climate *obj) { + for (auto *controller : controllers_) { + controller->on_climate_update(obj); + } +} +#endif + +#ifdef USE_NUMBER +void ControllerRegistry::notify_number_update(number::Number *obj) { + for (auto *controller : controllers_) { + controller->on_number_update(obj); + } +} +#endif + +#ifdef USE_DATETIME_DATE +void ControllerRegistry::notify_date_update(datetime::DateEntity *obj) { + for (auto *controller : controllers_) { + controller->on_date_update(obj); + } +} +#endif + +#ifdef USE_DATETIME_TIME +void ControllerRegistry::notify_time_update(datetime::TimeEntity *obj) { + for (auto *controller : controllers_) { + controller->on_time_update(obj); + } +} +#endif + +#ifdef USE_DATETIME_DATETIME +void ControllerRegistry::notify_datetime_update(datetime::DateTimeEntity *obj) { + for (auto *controller : controllers_) { + controller->on_datetime_update(obj); + } +} +#endif + +#ifdef USE_TEXT +void ControllerRegistry::notify_text_update(text::Text *obj) { + for (auto *controller : controllers_) { + controller->on_text_update(obj); + } +} +#endif + +#ifdef USE_SELECT +void ControllerRegistry::notify_select_update(select::Select *obj) { + for (auto *controller : controllers_) { + controller->on_select_update(obj); + } +} +#endif + +#ifdef USE_LOCK +void ControllerRegistry::notify_lock_update(lock::Lock *obj) { + for (auto *controller : controllers_) { + controller->on_lock_update(obj); + } +} +#endif + +#ifdef USE_VALVE +void ControllerRegistry::notify_valve_update(valve::Valve *obj) { + for (auto *controller : controllers_) { + controller->on_valve_update(obj); + } +} +#endif + +#ifdef USE_MEDIA_PLAYER +void ControllerRegistry::notify_media_player_update(media_player::MediaPlayer *obj) { + for (auto *controller : controllers_) { + controller->on_media_player_update(obj); + } +} +#endif + +#ifdef USE_ALARM_CONTROL_PANEL +void ControllerRegistry::notify_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) { + for (auto *controller : controllers_) { + controller->on_alarm_control_panel_update(obj); + } +} +#endif + +#ifdef USE_EVENT +void ControllerRegistry::notify_event(event::Event *obj, const std::string &event_type) { + for (auto *controller : controllers_) { + controller->on_event(obj, event_type); + } +} +#endif + +#ifdef USE_UPDATE +void ControllerRegistry::notify_update(update::UpdateEntity *obj) { + for (auto *controller : controllers_) { + controller->on_update(obj); + } +} +#endif + +} // namespace esphome diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h new file mode 100644 index 0000000000..33cd2ccebc --- /dev/null +++ b/esphome/core/controller_registry.h @@ -0,0 +1,263 @@ +#pragma once + +#include "esphome/core/defines.h" +#include + +// 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 a list of controller callbacks (32 bytes overhead), + * entities call ControllerRegistry::notify_*_update() which iterates the small list + * of registered controllers (typically 2: API and WebServer). + * + * Memory savings: 32 bytes per entity (2 controllers × 16 bytes std::function overhead) + * For 80 entities: 2,560 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); + + /** Unregister a controller (rarely used). + * + * Controllers are typically never unregistered in ESPHome's lifecycle, + * but this is provided for completeness and testing. + */ + static void unregister_controller(Controller *controller); + +#ifdef USE_BINARY_SENSOR + /** Notify all controllers of a binary sensor state update. */ + static void notify_binary_sensor_update(binary_sensor::BinarySensor *obj); +#endif + +#ifdef USE_FAN + /** Notify all controllers of a fan state update. */ + static void notify_fan_update(fan::Fan *obj); +#endif + +#ifdef USE_LIGHT + /** Notify all controllers of a light state update. */ + static void notify_light_update(light::LightState *obj); +#endif + +#ifdef USE_SENSOR + /** Notify all controllers of a sensor state update. */ + static void notify_sensor_update(sensor::Sensor *obj); +#endif + +#ifdef USE_SWITCH + /** Notify all controllers of a switch state update. */ + static void notify_switch_update(switch_::Switch *obj); +#endif + +#ifdef USE_COVER + /** Notify all controllers of a cover state update. */ + static void notify_cover_update(cover::Cover *obj); +#endif + +#ifdef USE_TEXT_SENSOR + /** Notify all controllers of a text sensor state update. */ + static void notify_text_sensor_update(text_sensor::TextSensor *obj); +#endif + +#ifdef USE_CLIMATE + /** Notify all controllers of a climate state update. */ + static void notify_climate_update(climate::Climate *obj); +#endif + +#ifdef USE_NUMBER + /** Notify all controllers of a number state update. */ + static void notify_number_update(number::Number *obj); +#endif + +#ifdef USE_DATETIME_DATE + /** Notify all controllers of a date entity state update. */ + static void notify_date_update(datetime::DateEntity *obj); +#endif + +#ifdef USE_DATETIME_TIME + /** Notify all controllers of a time entity state update. */ + static void notify_time_update(datetime::TimeEntity *obj); +#endif + +#ifdef USE_DATETIME_DATETIME + /** Notify all controllers of a datetime entity state update. */ + static void notify_datetime_update(datetime::DateTimeEntity *obj); +#endif + +#ifdef USE_TEXT + /** Notify all controllers of a text entity state update. */ + static void notify_text_update(text::Text *obj); +#endif + +#ifdef USE_SELECT + /** Notify all controllers of a select entity state update. */ + static void notify_select_update(select::Select *obj); +#endif + +#ifdef USE_LOCK + /** Notify all controllers of a lock state update. */ + static void notify_lock_update(lock::Lock *obj); +#endif + +#ifdef USE_VALVE + /** Notify all controllers of a valve state update. */ + static void notify_valve_update(valve::Valve *obj); +#endif + +#ifdef USE_MEDIA_PLAYER + /** Notify all controllers of a media player state update. */ + static void notify_media_player_update(media_player::MediaPlayer *obj); +#endif + +#ifdef USE_ALARM_CONTROL_PANEL + /** Notify all controllers of an alarm control panel state update. */ + static void notify_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj); +#endif + +#ifdef USE_EVENT + /** Notify all controllers of an event trigger. */ + static void notify_event(event::Event *obj, const std::string &event_type); +#endif + +#ifdef USE_UPDATE + /** Notify all controllers of an update entity state update. */ + static void notify_update(update::UpdateEntity *obj); +#endif + + protected: + static std::vector controllers_; +}; + +} // namespace esphome From f1009a7468742a374e53d92650272aa3784719e4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 15:44:17 -0600 Subject: [PATCH 06/22] tweak --- .../alarm_control_panel.cpp | 3 +- esphome/components/api/api_server.cpp | 6 +- esphome/components/api/api_server.h | 2 +- .../binary_sensor/binary_sensor.cpp | 2 + esphome/components/climate/climate.cpp | 2 + esphome/components/cover/cover.cpp | 2 + esphome/components/datetime/date_entity.cpp | 3 +- .../components/datetime/datetime_entity.cpp | 3 +- esphome/components/datetime/time_entity.cpp | 3 +- esphome/components/event/event.cpp | 3 +- esphome/components/fan/fan.cpp | 2 + esphome/components/light/light_state.cpp | 6 +- esphome/components/lock/lock.cpp | 2 + .../components/media_player/media_player.cpp | 7 +- esphome/components/number/number.cpp | 2 + esphome/components/select/select.cpp | 3 +- esphome/components/sensor/sensor.cpp | 2 + esphome/components/switch/switch.cpp | 2 + esphome/components/text/text.cpp | 2 + .../components/text_sensor/text_sensor.cpp | 2 + esphome/components/update/update_entity.cpp | 3 +- esphome/components/valve/valve.cpp | 2 + esphome/core/controller.cpp | 134 ------------------ esphome/core/controller.h | 3 +- esphome/core/controller_registry.cpp | 4 +- esphome/core/controller_registry.h | 2 +- 26 files changed, 54 insertions(+), 153 deletions(-) delete mode 100644 esphome/core/controller.cpp diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index 9f1485ee90..d7dd06b1aa 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -1,7 +1,7 @@ #include #include "alarm_control_panel.h" - +#include "esphome/core/controller_registry.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -34,6 +34,7 @@ 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(); + ControllerRegistry::notify_alarm_control_panel_update(this); if (state == ACP_STATE_TRIGGERED) { this->triggered_callback_.call(); } else if (state == ACP_STATE_ARMING) { diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index a08554acd8..c0c4cc3c03 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -348,12 +348,12 @@ 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 - it reads event_type from obj->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->last_event_type); } #endif diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 4b03023957..2d58063d6c 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -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..a41bdb032c 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -1,4 +1,5 @@ #include "binary_sensor.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -37,6 +38,7 @@ 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)); + ControllerRegistry::notify_binary_sensor_update(this); } } diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 7df38758dc..8df81dcc69 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -1,4 +1,5 @@ #include "climate.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/macros.h" namespace esphome { @@ -463,6 +464,7 @@ void Climate::publish_state() { // Send state to frontend this->state_callback_.call(*this); + ControllerRegistry::notify_climate_update(this); // Save state this->save_state_(); } diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 654bb956a5..b3285a34a4 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -1,5 +1,6 @@ #include "cover.h" #include +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -169,6 +170,7 @@ void Cover::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation)); this->state_callback_.call(); + ControllerRegistry::notify_cover_update(this); if (save) { CoverRestoreState restore{}; diff --git a/esphome/components/datetime/date_entity.cpp b/esphome/components/datetime/date_entity.cpp index c164a98b2e..cc3993eb58 100644 --- a/esphome/components/datetime/date_entity.cpp +++ b/esphome/components/datetime/date_entity.cpp @@ -1,5 +1,5 @@ #include "date_entity.h" - +#include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_DATE #include "esphome/core/log.h" @@ -32,6 +32,7 @@ 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(); + ControllerRegistry::notify_date_update(this); } 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..cdb5b555b5 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -1,5 +1,5 @@ #include "datetime_entity.h" - +#include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_DATETIME #include "esphome/core/log.h" @@ -48,6 +48,7 @@ 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(); + ControllerRegistry::notify_datetime_update(this); } 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..39533144e8 100644 --- a/esphome/components/datetime/time_entity.cpp +++ b/esphome/components/datetime/time_entity.cpp @@ -1,5 +1,5 @@ #include "time_entity.h" - +#include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_TIME #include "esphome/core/log.h" @@ -29,6 +29,7 @@ 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(); + ControllerRegistry::notify_time_update(this); } TimeCall TimeEntity::make_call() { return TimeCall(this); } diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 20549ad0a5..649e430e64 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -1,5 +1,5 @@ #include "event.h" - +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -23,6 +23,7 @@ void Event::trigger(const std::string &event_type) { last_event_type = found; ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str()); this->event_callback_.call(event_type); + ControllerRegistry::notify_event(this); } void Event::add_on_event_callback(std::function &&callback) { diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 959572e9d9..c7377b7070 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -1,4 +1,5 @@ #include "fan.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -181,6 +182,7 @@ void Fan::publish_state() { ESP_LOGD(TAG, " Preset Mode: %s", preset); } this->state_callback_.call(); + ControllerRegistry::notify_fan_update(this); this->save_state_(); } diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 7b0a698bb8..ed566783a4 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -1,3 +1,4 @@ +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include "light_output.h" @@ -137,7 +138,10 @@ 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(); + ControllerRegistry::notify_light_update(this); +} LightOutput *LightState::get_output() const { return this->output_; } diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index ddc5445349..3601552218 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -1,4 +1,5 @@ #include "lock.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -53,6 +54,7 @@ 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(); + ControllerRegistry::notify_lock_update(this); } 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..a16dfccfc2 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -1,5 +1,5 @@ #include "media_player.h" - +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -148,7 +148,10 @@ 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(); + ControllerRegistry::notify_media_player_update(this); +} } // namespace media_player } // namespace esphome diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index da08faf655..22463a4552 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -1,4 +1,5 @@ #include "number.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -32,6 +33,7 @@ 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); + ControllerRegistry::notify_number_update(this); } 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..12512060e5 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -1,5 +1,5 @@ #include "select.h" -#include "esphome/core/log.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include namespace esphome { @@ -33,6 +33,7 @@ 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); + ControllerRegistry::notify_select_update(this); } 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..dff76c5e9b 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -1,4 +1,5 @@ #include "sensor.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -131,6 +132,7 @@ 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); + ControllerRegistry::notify_sensor_update(this); } } // namespace sensor diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 02cee91a76..93e3ff5b44 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -1,4 +1,5 @@ #include "switch.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -62,6 +63,7 @@ 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); + ControllerRegistry::notify_switch_update(this); } bool Switch::assumed_state() { return false; } diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index 654893d4e4..5761723dc9 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -1,4 +1,5 @@ #include "text.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -16,6 +17,7 @@ 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); + ControllerRegistry::notify_text_update(this); } 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..5c790d5e76 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -1,4 +1,5 @@ #include "text_sensor.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -84,6 +85,7 @@ 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); + ControllerRegistry::notify_text_sensor_update(this); } } // namespace text_sensor diff --git a/esphome/components/update/update_entity.cpp b/esphome/components/update/update_entity.cpp index ce97fb1b77..069d08459b 100644 --- a/esphome/components/update/update_entity.cpp +++ b/esphome/components/update/update_entity.cpp @@ -1,5 +1,5 @@ #include "update_entity.h" - +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" namespace esphome { @@ -32,6 +32,7 @@ void UpdateEntity::publish_state() { this->set_has_state(true); this->state_callback_.call(); + ControllerRegistry::notify_update(this); } } // namespace update diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index b041fe8449..086d0e469f 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -1,4 +1,5 @@ #include "valve.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include @@ -147,6 +148,7 @@ void Valve::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", valve_operation_to_str(this->current_operation)); this->state_callback_.call(); + ControllerRegistry::notify_valve_update(this); if (save) { ValveRestoreState restore{}; 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 a62e53d9fc..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 @@ -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 index 3223d92170..c838dceb5f 100644 --- a/esphome/core/controller_registry.cpp +++ b/esphome/core/controller_registry.cpp @@ -159,9 +159,9 @@ void ControllerRegistry::notify_alarm_control_panel_update(alarm_control_panel:: #endif #ifdef USE_EVENT -void ControllerRegistry::notify_event(event::Event *obj, const std::string &event_type) { +void ControllerRegistry::notify_event(event::Event *obj) { for (auto *controller : controllers_) { - controller->on_event(obj, event_type); + controller->on_event(obj); } } #endif diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index 33cd2ccebc..16ca768cb3 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -248,7 +248,7 @@ class ControllerRegistry { #ifdef USE_EVENT /** Notify all controllers of an event trigger. */ - static void notify_event(event::Event *obj, const std::string &event_type); + static void notify_event(event::Event *obj); #endif #ifdef USE_UPDATE From c77bb3b269bf7fba517fb359535c59cccef49152 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 15:46:16 -0600 Subject: [PATCH 07/22] [event] Store event types in flash memory (#11767) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 3 +- esphome/components/api/api_pb2.cpp | 10 +++--- esphome/components/api/api_pb2.h | 2 +- esphome/components/api/api_pb2_dump.cpp | 2 +- esphome/components/event/event.cpp | 30 +++++++++++++---- esphome/components/event/event.h | 35 +++++++++++++++++--- esphome/components/mqtt/mqtt_event.cpp | 4 +-- esphome/components/web_server/web_server.cpp | 5 +-- 9 files changed, 67 insertions(+), 26 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 7a50fa6b17..e115e4630d 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -2147,7 +2147,7 @@ message ListEntitiesEventResponse { EntityCategory entity_category = 7; string device_class = 8; - repeated string event_types = 9; + repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector"]; uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"]; } message EventResponse { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5ab8a6eb05..8c293b41a2 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1310,8 +1310,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c auto *event = static_cast(entity); ListEntitiesEventResponse msg; msg.set_device_class(event->get_device_class_ref()); - for (const auto &event_type : event->get_event_types()) - msg.event_types.push_back(event_type); + msg.event_types = &event->get_event_types(); return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index dfa1a1320f..0a073fb662 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2877,8 +2877,8 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_string(8, this->device_class_ref_); - for (auto &it : this->event_types) { - buffer.encode_string(9, it, true); + for (const char *it : *this->event_types) { + buffer.encode_string(9, it, strlen(it), true); } #ifdef USE_DEVICES buffer.encode_uint32(10, this->device_id); @@ -2894,9 +2894,9 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); size.add_length(1, this->device_class_ref_.size()); - if (!this->event_types.empty()) { - for (const auto &it : this->event_types) { - size.add_length_force(1, it.size()); + if (!this->event_types->empty()) { + for (const char *it : *this->event_types) { + size.add_length_force(1, strlen(it)); } } #ifdef USE_DEVICES diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 716f1a6e9b..358049026e 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -2788,7 +2788,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage { #endif StringRef device_class_ref_{}; void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } - std::vector event_types{}; + const FixedVector *event_types{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index d94ceaaa9c..d9662483bf 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -2053,7 +2053,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "device_class", this->device_class_ref_); - for (const auto &it : this->event_types) { + for (const auto &it : *this->event_types) { dump_field(out, "event_types", it, 4); } #ifdef USE_DEVICES diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 20549ad0a5..a14afbd7f5 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -8,11 +8,11 @@ namespace event { static const char *const TAG = "event"; void Event::trigger(const std::string &event_type) { - // Linear search - faster than std::set for small datasets (1-5 items typical) - const std::string *found = nullptr; - for (const auto &type : this->types_) { - if (type == event_type) { - found = &type; + // Linear search with strcmp - faster than std::set for small datasets (1-5 items typical) + const char *found = nullptr; + for (const char *type : this->types_) { + if (strcmp(type, event_type.c_str()) == 0) { + found = type; break; } } @@ -20,11 +20,27 @@ void Event::trigger(const std::string &event_type) { ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str()); return; } - last_event_type = found; - ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str()); + 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); } +void Event::set_event_types(const FixedVector &event_types) { + this->types_.init(event_types.size()); + for (const char *type : event_types) { + this->types_.push_back(type); + } + this->last_event_type_ = nullptr; // Reset when types change +} + +void Event::set_event_types(const std::vector &event_types) { + this->types_.init(event_types.size()); + for (const char *type : event_types) { + this->types_.push_back(type); + } + this->last_event_type_ = nullptr; // Reset when types change +} + void Event::add_on_event_callback(std::function &&callback) { this->event_callback_.add(std::move(callback)); } diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index 2f6267a200..e4b2e0b845 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include "esphome/core/component.h" #include "esphome/core/entity_base.h" @@ -22,16 +24,39 @@ namespace event { class Event : public EntityBase, public EntityBase_DeviceClass { public: - const std::string *last_event_type; - void trigger(const std::string &event_type); - void set_event_types(const std::initializer_list &event_types) { this->types_ = event_types; } - const FixedVector &get_event_types() const { return this->types_; } + + /// Set the event types supported by this event (from initializer list). + void set_event_types(std::initializer_list event_types) { + this->types_ = event_types; + this->last_event_type_ = nullptr; // Reset when types change + } + /// Set the event types supported by this event (from FixedVector). + void set_event_types(const FixedVector &event_types); + /// Set the event types supported by this event (from vector). + void set_event_types(const std::vector &event_types); + + // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages + void set_event_types(std::initializer_list event_types) = delete; + void set_event_types(const FixedVector &event_types) = delete; + void set_event_types(const std::vector &event_types) = delete; + + /// Return the event types supported by this event. + const FixedVector &get_event_types() const { return this->types_; } + + /// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet. + const char *get_last_event_type() const { return this->last_event_type_; } + void add_on_event_callback(std::function &&callback); protected: CallbackManager event_callback_; - FixedVector types_; + FixedVector types_; + + private: + /// Last triggered event type - must point to entry in types_ to ensure valid lifetime. + /// Set by trigger() after validation, reset to nullptr when types_ changes. + const char *last_event_type_{nullptr}; }; } // namespace event diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index e206335446..fd095ea041 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -38,8 +38,8 @@ void MQTTEventComponent::setup() { void MQTTEventComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Event '%s': ", this->event_->get_name().c_str()); ESP_LOGCONFIG(TAG, "Event Types: "); - for (const auto &event_type : this->event_->get_event_types()) { - ESP_LOGCONFIG(TAG, "- %s", event_type.c_str()); + for (const char *event_type : this->event_->get_event_types()) { + ESP_LOGCONFIG(TAG, "- %s", event_type); } LOG_MQTT_COMPONENT(true, true); } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f1d1a75875..91ca076474 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1628,7 +1628,8 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa } static std::string get_event_type(event::Event *event) { - return (event && event->last_event_type) ? *event->last_event_type : ""; + const char *last_type = event ? event->get_last_event_type() : nullptr; + return last_type ? last_type : ""; } std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) { @@ -1649,7 +1650,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty } if (start_config == DETAIL_ALL) { JsonArray event_types = root["event_types"].to(); - for (auto const &event_type : obj->get_event_types()) { + for (const char *event_type : obj->get_event_types()) { event_types.add(event_type); } root["device_class"] = obj->get_device_class_ref(); From 51eb8ea1d0e326c2ec357171800e05e81d4cf9fa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 15:48:02 -0600 Subject: [PATCH 08/22] controller registry --- esphome/components/api/api_server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index c0c4cc3c03..d9919bb849 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -348,12 +348,12 @@ API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player) #endif #ifdef USE_EVENT -// Event is a special case - it reads event_type from obj->last_event_type +// Event is a special case - it reads event_type from 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, *obj->last_event_type); + c->send_event(obj, obj->get_last_event_type()); } #endif From 6fa0f1e29051049aa29b9c33fa3b8f0dc944a631 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 15:51:13 -0600 Subject: [PATCH 09/22] controller registry --- esphome/components/select/select.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 12512060e5..ab3ac75a80 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -1,5 +1,6 @@ #include "select.h" -#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include "esphome/core/controller_registry.h" +#include "esphome/core/log.h" #include namespace esphome { From 929279dc234768189709ec0f5719afdb35b957ab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 15:55:22 -0600 Subject: [PATCH 10/22] controller registry --- esphome/components/web_server/web_server.cpp | 4 +++- esphome/components/web_server/web_server.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 8ebabf545f..6d0cdeb07b 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1642,7 +1642,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 8e74c42bff..7e1af88645 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -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); From 6ef2763cabaa2e78723a2b713f0fe367c1cce4f9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:01:45 -0600 Subject: [PATCH 11/22] controller registry --- esphome/core/controller_registry.cpp | 7 ------ esphome/core/controller_registry.h | 35 +++++----------------------- 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp index c838dceb5f..da7fd5c837 100644 --- a/esphome/core/controller_registry.cpp +++ b/esphome/core/controller_registry.cpp @@ -7,13 +7,6 @@ std::vector ControllerRegistry::controllers_; void ControllerRegistry::register_controller(Controller *controller) { controllers_.push_back(controller); } -void ControllerRegistry::unregister_controller(Controller *controller) { - auto it = std::find(controllers_.begin(), controllers_.end(), controller); - if (it != controllers_.end()) { - controllers_.erase(it); - } -} - #ifdef USE_BINARY_SENSOR void ControllerRegistry::notify_binary_sensor_update(binary_sensor::BinarySensor *obj) { for (auto *controller : controllers_) { diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index 16ca768cb3..6a72195b63 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -133,12 +133,16 @@ class UpdateEntity; * This singleton registry allows Controllers (APIServer, WebServer) to receive * entity state change notifications without storing per-entity callbacks. * - * Instead of each entity maintaining a list of controller callbacks (32 bytes overhead), + * 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) - * For 80 entities: 2,560 bytes saved + * Typical config (25 entities): ~780 bytes saved + * Large config (80 entities): ~2,540 bytes saved */ class ControllerRegistry { public: @@ -149,110 +153,83 @@ class ControllerRegistry { */ static void register_controller(Controller *controller); - /** Unregister a controller (rarely used). - * - * Controllers are typically never unregistered in ESPHome's lifecycle, - * but this is provided for completeness and testing. - */ - static void unregister_controller(Controller *controller); - #ifdef USE_BINARY_SENSOR - /** Notify all controllers of a binary sensor state update. */ static void notify_binary_sensor_update(binary_sensor::BinarySensor *obj); #endif #ifdef USE_FAN - /** Notify all controllers of a fan state update. */ static void notify_fan_update(fan::Fan *obj); #endif #ifdef USE_LIGHT - /** Notify all controllers of a light state update. */ static void notify_light_update(light::LightState *obj); #endif #ifdef USE_SENSOR - /** Notify all controllers of a sensor state update. */ static void notify_sensor_update(sensor::Sensor *obj); #endif #ifdef USE_SWITCH - /** Notify all controllers of a switch state update. */ static void notify_switch_update(switch_::Switch *obj); #endif #ifdef USE_COVER - /** Notify all controllers of a cover state update. */ static void notify_cover_update(cover::Cover *obj); #endif #ifdef USE_TEXT_SENSOR - /** Notify all controllers of a text sensor state update. */ static void notify_text_sensor_update(text_sensor::TextSensor *obj); #endif #ifdef USE_CLIMATE - /** Notify all controllers of a climate state update. */ static void notify_climate_update(climate::Climate *obj); #endif #ifdef USE_NUMBER - /** Notify all controllers of a number state update. */ static void notify_number_update(number::Number *obj); #endif #ifdef USE_DATETIME_DATE - /** Notify all controllers of a date entity state update. */ static void notify_date_update(datetime::DateEntity *obj); #endif #ifdef USE_DATETIME_TIME - /** Notify all controllers of a time entity state update. */ static void notify_time_update(datetime::TimeEntity *obj); #endif #ifdef USE_DATETIME_DATETIME - /** Notify all controllers of a datetime entity state update. */ static void notify_datetime_update(datetime::DateTimeEntity *obj); #endif #ifdef USE_TEXT - /** Notify all controllers of a text entity state update. */ static void notify_text_update(text::Text *obj); #endif #ifdef USE_SELECT - /** Notify all controllers of a select entity state update. */ static void notify_select_update(select::Select *obj); #endif #ifdef USE_LOCK - /** Notify all controllers of a lock state update. */ static void notify_lock_update(lock::Lock *obj); #endif #ifdef USE_VALVE - /** Notify all controllers of a valve state update. */ static void notify_valve_update(valve::Valve *obj); #endif #ifdef USE_MEDIA_PLAYER - /** Notify all controllers of a media player state update. */ static void notify_media_player_update(media_player::MediaPlayer *obj); #endif #ifdef USE_ALARM_CONTROL_PANEL - /** Notify all controllers of an alarm control panel state update. */ static void notify_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj); #endif #ifdef USE_EVENT - /** Notify all controllers of an event trigger. */ static void notify_event(event::Event *obj); #endif #ifdef USE_UPDATE - /** Notify all controllers of an update entity state update. */ static void notify_update(update::UpdateEntity *obj); #endif From 871c5ddb4ef438cc8531368da6b564acc03e1d45 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:07:54 -0600 Subject: [PATCH 12/22] no ifdefs needed on forward decs --- esphome/core/controller_registry.h | 40 ------------------------------ 1 file changed, 40 deletions(-) diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index 6a72195b63..5e2939ff95 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -8,125 +8,85 @@ 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. * From c0e4f415f1d362ccbd9d98b9f3a3103e46683ebb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:10:56 -0600 Subject: [PATCH 13/22] Revert "no ifdefs needed on forward decs" This reverts commit 871c5ddb4ef438cc8531368da6b564acc03e1d45. --- esphome/core/controller_registry.h | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index 5e2939ff95..6a72195b63 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -8,85 +8,125 @@ 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. * From fc8dc33023e2614dd6e12ae46d98c084f938d80a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:13:59 -0600 Subject: [PATCH 14/22] fixes --- esphome/components/alarm_control_panel/alarm_control_panel.cpp | 2 ++ esphome/components/binary_sensor/binary_sensor.cpp | 2 ++ esphome/components/climate/climate.cpp | 2 ++ esphome/components/cover/cover.cpp | 2 ++ esphome/components/datetime/date_entity.cpp | 2 ++ esphome/components/datetime/datetime_entity.cpp | 2 ++ esphome/components/datetime/time_entity.cpp | 2 ++ esphome/components/event/event.cpp | 2 ++ esphome/components/fan/fan.cpp | 2 ++ esphome/components/light/light_state.cpp | 2 ++ esphome/components/lock/lock.cpp | 2 ++ esphome/components/media_player/media_player.cpp | 2 ++ esphome/components/number/number.cpp | 2 ++ esphome/components/select/select.cpp | 2 ++ esphome/components/sensor/sensor.cpp | 2 ++ esphome/components/switch/switch.cpp | 2 ++ esphome/components/text/text.cpp | 2 ++ esphome/components/text_sensor/text_sensor.cpp | 2 ++ esphome/components/update/update_entity.cpp | 2 ++ esphome/components/valve/valve.cpp | 2 ++ 20 files changed, 40 insertions(+) diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index d7dd06b1aa..baa7094a8a 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -34,7 +34,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(); +#ifdef USE_ALARM_CONTROL_PANEL 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/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index a41bdb032c..7c35ebe116 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -38,7 +38,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)); +#ifdef USE_BINARY_SENSOR ControllerRegistry::notify_binary_sensor_update(this); +#endif } } diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 8df81dcc69..0576674f3a 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -464,7 +464,9 @@ void Climate::publish_state() { // Send state to frontend this->state_callback_.call(*this); +#ifdef USE_CLIMATE 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 b3285a34a4..4542f0300e 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -170,7 +170,9 @@ void Cover::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation)); this->state_callback_.call(); +#ifdef USE_COVER 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 cc3993eb58..d5c63f5e0b 100644 --- a/esphome/components/datetime/date_entity.cpp +++ b/esphome/components/datetime/date_entity.cpp @@ -32,7 +32,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(); +#ifdef USE_DATETIME_DATE 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 cdb5b555b5..56f4e3f7af 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -48,7 +48,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(); +#ifdef USE_DATETIME_DATETIME 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 39533144e8..f06f962177 100644 --- a/esphome/components/datetime/time_entity.cpp +++ b/esphome/components/datetime/time_entity.cpp @@ -29,7 +29,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(); +#ifdef USE_DATETIME_TIME 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 5bcac99154..ffc3cdaa39 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -23,7 +23,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); +#ifdef USE_EVENT 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 c7377b7070..ee1962bf60 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -182,7 +182,9 @@ void Fan::publish_state() { ESP_LOGD(TAG, " Preset Mode: %s", preset); } this->state_callback_.call(); +#ifdef USE_FAN 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 ed566783a4..0f6695d968 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -140,7 +140,9 @@ float LightState::get_setup_priority() const { return setup_priority::HARDWARE - void LightState::publish_state() { this->remote_values_callback_.call(); +#ifdef USE_LIGHT 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 3601552218..2cbef07ca6 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -54,7 +54,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(); +#ifdef USE_LOCK 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 a16dfccfc2..878dcfe6f1 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -150,7 +150,9 @@ void MediaPlayer::add_on_state_callback(std::function &&callback) { void MediaPlayer::publish_state() { this->state_callback_.call(); +#ifdef USE_MEDIA_PLAYER ControllerRegistry::notify_media_player_update(this); +#endif } } // namespace media_player diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index 22463a4552..35e59ebdab 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -33,7 +33,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); +#ifdef USE_NUMBER 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 ab3ac75a80..fc933c15ef 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -34,7 +34,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); +#ifdef USE_SELECT 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 dff76c5e9b..ac70e457e0 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -132,7 +132,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); +#ifdef USE_SENSOR ControllerRegistry::notify_sensor_update(this); +#endif } } // namespace sensor diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 93e3ff5b44..49954e6f06 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -63,7 +63,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); +#ifdef USE_SWITCH 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 5761723dc9..3028cd85ea 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -17,7 +17,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); +#ifdef USE_TEXT 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 5c790d5e76..c4a941408a 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -85,7 +85,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); +#ifdef USE_TEXT_SENSOR 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 069d08459b..9ca2c35f81 100644 --- a/esphome/components/update/update_entity.cpp +++ b/esphome/components/update/update_entity.cpp @@ -32,7 +32,9 @@ void UpdateEntity::publish_state() { this->set_has_state(true); this->state_callback_.call(); +#ifdef USE_UPDATE ControllerRegistry::notify_update(this); +#endif } } // namespace update diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index 086d0e469f..0982892af3 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -148,7 +148,9 @@ void Valve::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", valve_operation_to_str(this->current_operation)); this->state_callback_.call(); +#ifdef USE_VALVE ControllerRegistry::notify_valve_update(this); +#endif if (save) { ValveRestoreState restore{}; From c87d07ba70ab657de3786bf4da73c392fe797df0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:15:07 -0600 Subject: [PATCH 15/22] fixes --- esphome/core/controller_registry.cpp | 44 ++++++++++++++-------------- esphome/core/controller_registry.h | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp index da7fd5c837..b3f4c646a3 100644 --- a/esphome/core/controller_registry.cpp +++ b/esphome/core/controller_registry.cpp @@ -3,13 +3,13 @@ namespace esphome { -std::vector ControllerRegistry::controllers_; +std::vector ControllerRegistry::controllers; -void ControllerRegistry::register_controller(Controller *controller) { controllers_.push_back(controller); } +void ControllerRegistry::register_controller(Controller *controller) { controllers.push_back(controller); } #ifdef USE_BINARY_SENSOR void ControllerRegistry::notify_binary_sensor_update(binary_sensor::BinarySensor *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_binary_sensor_update(obj); } } @@ -17,7 +17,7 @@ void ControllerRegistry::notify_binary_sensor_update(binary_sensor::BinarySensor #ifdef USE_FAN void ControllerRegistry::notify_fan_update(fan::Fan *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_fan_update(obj); } } @@ -25,7 +25,7 @@ void ControllerRegistry::notify_fan_update(fan::Fan *obj) { #ifdef USE_LIGHT void ControllerRegistry::notify_light_update(light::LightState *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_light_update(obj); } } @@ -33,7 +33,7 @@ void ControllerRegistry::notify_light_update(light::LightState *obj) { #ifdef USE_SENSOR void ControllerRegistry::notify_sensor_update(sensor::Sensor *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_sensor_update(obj); } } @@ -41,7 +41,7 @@ void ControllerRegistry::notify_sensor_update(sensor::Sensor *obj) { #ifdef USE_SWITCH void ControllerRegistry::notify_switch_update(switch_::Switch *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_switch_update(obj); } } @@ -49,7 +49,7 @@ void ControllerRegistry::notify_switch_update(switch_::Switch *obj) { #ifdef USE_COVER void ControllerRegistry::notify_cover_update(cover::Cover *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_cover_update(obj); } } @@ -57,7 +57,7 @@ void ControllerRegistry::notify_cover_update(cover::Cover *obj) { #ifdef USE_TEXT_SENSOR void ControllerRegistry::notify_text_sensor_update(text_sensor::TextSensor *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_text_sensor_update(obj); } } @@ -65,7 +65,7 @@ void ControllerRegistry::notify_text_sensor_update(text_sensor::TextSensor *obj) #ifdef USE_CLIMATE void ControllerRegistry::notify_climate_update(climate::Climate *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_climate_update(obj); } } @@ -73,7 +73,7 @@ void ControllerRegistry::notify_climate_update(climate::Climate *obj) { #ifdef USE_NUMBER void ControllerRegistry::notify_number_update(number::Number *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_number_update(obj); } } @@ -81,7 +81,7 @@ void ControllerRegistry::notify_number_update(number::Number *obj) { #ifdef USE_DATETIME_DATE void ControllerRegistry::notify_date_update(datetime::DateEntity *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_date_update(obj); } } @@ -89,7 +89,7 @@ void ControllerRegistry::notify_date_update(datetime::DateEntity *obj) { #ifdef USE_DATETIME_TIME void ControllerRegistry::notify_time_update(datetime::TimeEntity *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_time_update(obj); } } @@ -97,7 +97,7 @@ void ControllerRegistry::notify_time_update(datetime::TimeEntity *obj) { #ifdef USE_DATETIME_DATETIME void ControllerRegistry::notify_datetime_update(datetime::DateTimeEntity *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_datetime_update(obj); } } @@ -105,7 +105,7 @@ void ControllerRegistry::notify_datetime_update(datetime::DateTimeEntity *obj) { #ifdef USE_TEXT void ControllerRegistry::notify_text_update(text::Text *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_text_update(obj); } } @@ -113,7 +113,7 @@ void ControllerRegistry::notify_text_update(text::Text *obj) { #ifdef USE_SELECT void ControllerRegistry::notify_select_update(select::Select *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_select_update(obj); } } @@ -121,7 +121,7 @@ void ControllerRegistry::notify_select_update(select::Select *obj) { #ifdef USE_LOCK void ControllerRegistry::notify_lock_update(lock::Lock *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_lock_update(obj); } } @@ -129,7 +129,7 @@ void ControllerRegistry::notify_lock_update(lock::Lock *obj) { #ifdef USE_VALVE void ControllerRegistry::notify_valve_update(valve::Valve *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_valve_update(obj); } } @@ -137,7 +137,7 @@ void ControllerRegistry::notify_valve_update(valve::Valve *obj) { #ifdef USE_MEDIA_PLAYER void ControllerRegistry::notify_media_player_update(media_player::MediaPlayer *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_media_player_update(obj); } } @@ -145,7 +145,7 @@ void ControllerRegistry::notify_media_player_update(media_player::MediaPlayer *o #ifdef USE_ALARM_CONTROL_PANEL void ControllerRegistry::notify_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_alarm_control_panel_update(obj); } } @@ -153,7 +153,7 @@ void ControllerRegistry::notify_alarm_control_panel_update(alarm_control_panel:: #ifdef USE_EVENT void ControllerRegistry::notify_event(event::Event *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_event(obj); } } @@ -161,7 +161,7 @@ void ControllerRegistry::notify_event(event::Event *obj) { #ifdef USE_UPDATE void ControllerRegistry::notify_update(update::UpdateEntity *obj) { - for (auto *controller : controllers_) { + for (auto *controller : controllers) { controller->on_update(obj); } } diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index 6a72195b63..72485c356b 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -234,7 +234,7 @@ class ControllerRegistry { #endif protected: - static std::vector controllers_; + static std::vector controllers; }; } // namespace esphome From 1b6471f4b0ff273e15130bf5e77e1c31e577efdb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:30:38 -0600 Subject: [PATCH 16/22] cleanups --- esphome/components/api/__init__.py | 3 +++ esphome/components/web_server/__init__.py | 3 +++ esphome/core/__init__.py | 5 +++++ esphome/core/config.py | 9 +++++++++ esphome/core/controller_registry.cpp | 7 ++++++- esphome/core/controller_registry.h | 9 +++++++-- esphome/core/defines.h | 2 ++ 7 files changed, 35 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 449572c0e5..023e4bf115 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -244,6 +244,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/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/core/__init__.py b/esphome/core/__init__.py index fed5265d6b..84c9e36002 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -910,6 +910,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("controller_registry_count", 0) + self.data["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..3d98eda8bb 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -462,6 +462,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("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) diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp index b3f4c646a3..b22ec487d5 100644 --- a/esphome/core/controller_registry.cpp +++ b/esphome/core/controller_registry.cpp @@ -1,9 +1,12 @@ #include "esphome/core/controller_registry.h" + +#ifdef USE_CONTROLLER_REGISTRY + #include "esphome/core/controller.h" namespace esphome { -std::vector ControllerRegistry::controllers; +StaticVector ControllerRegistry::controllers; void ControllerRegistry::register_controller(Controller *controller) { controllers.push_back(controller); } @@ -168,3 +171,5 @@ void ControllerRegistry::notify_update(update::UpdateEntity *obj) { #endif } // namespace esphome + +#endif // USE_CONTROLLER_REGISTRY diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index 72485c356b..640a276a0a 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -1,7 +1,10 @@ #pragma once #include "esphome/core/defines.h" -#include + +#ifdef USE_CONTROLLER_REGISTRY + +#include "esphome/core/helpers.h" // Forward declarations namespace esphome { @@ -234,7 +237,9 @@ class ControllerRegistry { #endif protected: - static std::vector controllers; + 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 From 8229e3a47167bb1ac4a4a327cab9f50a4c08cbd7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:33:01 -0600 Subject: [PATCH 17/22] cleanups --- esphome/components/alarm_control_panel/alarm_control_panel.cpp | 3 ++- esphome/components/binary_sensor/binary_sensor.cpp | 3 ++- esphome/components/climate/climate.cpp | 3 ++- esphome/components/cover/cover.cpp | 3 ++- esphome/components/datetime/date_entity.cpp | 3 ++- esphome/components/datetime/datetime_entity.cpp | 3 ++- esphome/components/datetime/time_entity.cpp | 3 ++- esphome/components/event/event.cpp | 3 ++- esphome/components/fan/fan.cpp | 3 ++- esphome/components/light/light_state.cpp | 3 ++- esphome/components/lock/lock.cpp | 3 ++- esphome/components/media_player/media_player.cpp | 3 ++- esphome/components/number/number.cpp | 3 ++- esphome/components/select/select.cpp | 3 ++- esphome/components/sensor/sensor.cpp | 3 ++- esphome/components/switch/switch.cpp | 3 ++- esphome/components/text/text.cpp | 3 ++- esphome/components/text_sensor/text_sensor.cpp | 3 ++- esphome/components/update/update_entity.cpp | 3 ++- esphome/components/valve/valve.cpp | 3 ++- 20 files changed, 40 insertions(+), 20 deletions(-) diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index baa7094a8a..febd00f2c5 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -1,4 +1,5 @@ #include +#include "esphome/core/defines.h" #include "alarm_control_panel.h" #include "esphome/core/controller_registry.h" @@ -34,7 +35,7 @@ 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(); -#ifdef USE_ALARM_CONTROL_PANEL +#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_alarm_control_panel_update(this); #endif if (state == ACP_STATE_TRIGGERED) { diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 7c35ebe116..220ed685db 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -1,4 +1,5 @@ #include "binary_sensor.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -38,7 +39,7 @@ 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)); -#ifdef USE_BINARY_SENSOR +#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 0576674f3a..82b75660ba 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -1,4 +1,5 @@ #include "climate.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/macros.h" @@ -464,7 +465,7 @@ void Climate::publish_state() { // Send state to frontend this->state_callback_.call(*this); -#ifdef USE_CLIMATE +#if defined(USE_CLIMATE) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_climate_update(this); #endif // Save state diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 4542f0300e..ab800a8749 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -1,4 +1,5 @@ #include "cover.h" +#include "esphome/core/defines.h" #include #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -170,7 +171,7 @@ void Cover::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation)); this->state_callback_.call(); -#ifdef USE_COVER +#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_cover_update(this); #endif diff --git a/esphome/components/datetime/date_entity.cpp b/esphome/components/datetime/date_entity.cpp index d5c63f5e0b..2c2775ecf4 100644 --- a/esphome/components/datetime/date_entity.cpp +++ b/esphome/components/datetime/date_entity.cpp @@ -1,4 +1,5 @@ #include "date_entity.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_DATE @@ -32,7 +33,7 @@ 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(); -#ifdef USE_DATETIME_DATE +#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_date_update(this); #endif } diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index 56f4e3f7af..8606a47fa7 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -1,4 +1,5 @@ #include "datetime_entity.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_DATETIME @@ -48,7 +49,7 @@ 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(); -#ifdef USE_DATETIME_DATETIME +#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_datetime_update(this); #endif } diff --git a/esphome/components/datetime/time_entity.cpp b/esphome/components/datetime/time_entity.cpp index f06f962177..469be077ea 100644 --- a/esphome/components/datetime/time_entity.cpp +++ b/esphome/components/datetime/time_entity.cpp @@ -1,4 +1,5 @@ #include "time_entity.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #ifdef USE_DATETIME_TIME @@ -29,7 +30,7 @@ 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(); -#ifdef USE_DATETIME_TIME +#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_time_update(this); #endif } diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index ffc3cdaa39..4c74a11388 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -1,4 +1,5 @@ #include "event.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -23,7 +24,7 @@ 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); -#ifdef USE_EVENT +#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_event(this); #endif } diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index ee1962bf60..d37825a651 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -1,4 +1,5 @@ #include "fan.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -182,7 +183,7 @@ void Fan::publish_state() { ESP_LOGD(TAG, " Preset Mode: %s", preset); } this->state_callback_.call(); -#ifdef USE_FAN +#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 0f6695d968..8b53044554 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -1,4 +1,5 @@ #include "esphome/core/controller_registry.h" +#include "esphome/core/defines.h" #include "esphome/core/log.h" #include "light_output.h" @@ -140,7 +141,7 @@ float LightState::get_setup_priority() const { return setup_priority::HARDWARE - void LightState::publish_state() { this->remote_values_callback_.call(); -#ifdef USE_LIGHT +#if defined(USE_LIGHT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_light_update(this); #endif } diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index 2cbef07ca6..54fefe8745 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -1,4 +1,5 @@ #include "lock.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -54,7 +55,7 @@ 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(); -#ifdef USE_LOCK +#if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_lock_update(this); #endif } diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index 878dcfe6f1..b46ec39d30 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -1,4 +1,5 @@ #include "media_player.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -150,7 +151,7 @@ void MediaPlayer::add_on_state_callback(std::function &&callback) { void MediaPlayer::publish_state() { this->state_callback_.call(); -#ifdef USE_MEDIA_PLAYER +#if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_media_player_update(this); #endif } diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index 35e59ebdab..f12e0e9e1e 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -1,4 +1,5 @@ #include "number.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -33,7 +34,7 @@ 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); -#ifdef USE_NUMBER +#if defined(USE_NUMBER) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_number_update(this); #endif } diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index fc933c15ef..9fe7a52422 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -1,4 +1,5 @@ #include "select.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include @@ -34,7 +35,7 @@ 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); -#ifdef USE_SELECT +#if defined(USE_SELECT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_select_update(this); #endif } diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index ac70e457e0..df6bd644e8 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -1,4 +1,5 @@ #include "sensor.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -132,7 +133,7 @@ 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); -#ifdef USE_SENSOR +#if defined(USE_SENSOR) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_sensor_update(this); #endif } diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 49954e6f06..3c3a437ff3 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -1,4 +1,5 @@ #include "switch.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -63,7 +64,7 @@ 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); -#ifdef USE_SWITCH +#if defined(USE_SWITCH) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_switch_update(this); #endif } diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index 3028cd85ea..933d82c85c 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -1,4 +1,5 @@ #include "text.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -17,7 +18,7 @@ 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); -#ifdef USE_TEXT +#if defined(USE_TEXT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_update(this); #endif } diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index c4a941408a..a7bcf19967 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -1,4 +1,5 @@ #include "text_sensor.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -85,7 +86,7 @@ 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); -#ifdef USE_TEXT_SENSOR +#if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_sensor_update(this); #endif } diff --git a/esphome/components/update/update_entity.cpp b/esphome/components/update/update_entity.cpp index 9ca2c35f81..567fc9fc8e 100644 --- a/esphome/components/update/update_entity.cpp +++ b/esphome/components/update/update_entity.cpp @@ -1,4 +1,5 @@ #include "update_entity.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" @@ -32,7 +33,7 @@ void UpdateEntity::publish_state() { this->set_has_state(true); this->state_callback_.call(); -#ifdef USE_UPDATE +#if defined(USE_UPDATE) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_update(this); #endif } diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index 0982892af3..381d9061de 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -1,4 +1,5 @@ #include "valve.h" +#include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include @@ -148,7 +149,7 @@ void Valve::publish_state(bool save) { ESP_LOGD(TAG, " Current Operation: %s", valve_operation_to_str(this->current_operation)); this->state_callback_.call(); -#ifdef USE_VALVE +#if defined(USE_VALVE) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_valve_update(this); #endif From 327543303cc59f997333265343755d98f6c6cc56 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:34:37 -0600 Subject: [PATCH 18/22] cleanups --- esphome/components/api/api_server.cpp | 2 +- esphome/components/web_server/web_server.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index d9919bb849..9453a14eb3 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -4,8 +4,8 @@ #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/controller_registry.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/util.h" diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 6d0cdeb07b..5a8128ba43 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -3,6 +3,7 @@ #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" From 0962024d99e0ef2705755e6cb17d20a62fc04580 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:35:24 -0600 Subject: [PATCH 19/22] cleanups --- esphome/components/light/light_state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 8b53044554..5bb9087c5b 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -1,5 +1,5 @@ -#include "esphome/core/controller_registry.h" #include "esphome/core/defines.h" +#include "esphome/core/controller_registry.h" #include "esphome/core/log.h" #include "light_output.h" From ac85949f176b1772d4f4fe00189207e05cc187c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:38:32 -0600 Subject: [PATCH 20/22] cleanups --- esphome/core/__init__.py | 7 +++++-- esphome/core/config.py | 9 +++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 84c9e36002..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.""" @@ -912,8 +915,8 @@ class EsphomeCore: def register_controller(self) -> None: """Track registration of a Controller for ControllerRegistry StaticVector sizing.""" - controller_count = self.data.setdefault("controller_registry_count", 0) - self.data["controller_registry_count"] = controller_count + 1 + 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): diff --git a/esphome/core/config.py b/esphome/core/config.py index 3d98eda8bb..ce94ec11f2 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, @@ -465,7 +470,7 @@ 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("controller_registry_count", 0) + 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) From 6e7f66d393c3aa7cb58aa7b5c1c68c85a3249d98 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 16:40:36 -0600 Subject: [PATCH 21/22] missing registry --- esphome/core/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index ce94ec11f2..763f9ebd9f 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -497,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) From e3fb074a604778bc60699a455345cc68c283b667 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 7 Nov 2025 17:14:50 -0600 Subject: [PATCH 22/22] preen --- esphome/components/api/api_server.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 9453a14eb3..18601d74ff 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -348,7 +348,8 @@ API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player) #endif #ifdef USE_EVENT -// Event is a special case - it reads event_type from obj->get_last_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;