From 6b53ccc85ab90467933df165c22deab9095ec178 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 6 Mar 2026 07:39:49 -1000 Subject: [PATCH] make it safer --- esphome/core/entity_base.cpp | 2 +- esphome/core/entity_base.h | 13 ++++++++++--- esphome/core/entity_helpers.py | 8 ++++---- .../binary_sensor/test_binary_sensor.py | 2 +- tests/component_tests/button/test_button.py | 2 +- tests/component_tests/sensor/test_sensor.py | 2 +- tests/component_tests/text/test_text.py | 2 +- .../component_tests/text_sensor/test_text_sensor.py | 10 +++++----- tests/unit_tests/core/test_entity_helpers.py | 10 +++++----- 9 files changed, 29 insertions(+), 22 deletions(-) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index d06f6ad4700..f1f9f6dfba5 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -11,7 +11,7 @@ static const char *const TAG = "entity_base"; // Entity Name const StringRef &EntityBase::get_name() const { return this->name_; } -void EntityBase::configure_entity(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed) { +void EntityBase::configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed) { this->name_ = StringRef(name); if (this->name_.empty()) { #ifdef USE_DEVICES diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 8eddce93173..945cbf14778 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -12,6 +12,10 @@ #include "device.h" #endif +// Forward declarations for friend access from codegen-generated setup() +void setup(); // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h +void original_setup(); // NOLINT(readability-redundant-declaration) - used by cpp unit tests + namespace esphome { // Extern lookup functions for entity string tables. @@ -52,9 +56,6 @@ class EntityBase { // Get the name of this Entity const StringRef &get_name() const; - /// Combined entity setup from codegen: set name, object_id hash, and entity string indices. - void configure_entity(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed); - // Get whether this Entity has its own name or it should use the device friendly_name. bool has_own_name() const { return this->flags_.has_own_name; } @@ -201,6 +202,12 @@ class EntityBase { } protected: + friend void ::setup(); + friend void ::original_setup(); + + /// Combined entity setup from codegen: set name, object_id hash, and entity string indices. + void configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed); + /// Non-template helper for make_entity_preference() to avoid code bloat. /// When preference hash algorithm changes, migration logic goes here. ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version); diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index 127f0bb3ed6..5ce74a70827 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -34,7 +34,7 @@ _KEY_ICON_IDX = "_entity_icon_idx" _KEY_ENTITY_NAME = "_entity_name" _KEY_OBJECT_ID_HASH = "_entity_object_id_hash" -# Bit layout for entity_strings_packed in configure_entity() — must match C++ in entity_base.h: +# Bit layout for entity_strings_packed in configure_entity_() — must match C++ in entity_base.h: # [23..16] icon (8 bits) | [15..8] UoM (8 bits) | [7..0] device_class (8 bits) _DC_SHIFT = 0 _UOM_SHIFT = 8 @@ -217,7 +217,7 @@ def setup_unit_of_measurement(config: ConfigType) -> None: def finalize_entity_strings(var: MockObj, config: ConfigType) -> None: - """Emit a single configure_entity() call with name, hash, and packed string indices. + """Emit a single configure_entity_() call with name, hash, and packed string indices. Call this at the end of each component's setup function, after setup_entity() and any register_device_class/register_unit_of_measurement calls. @@ -228,7 +228,7 @@ def finalize_entity_strings(var: MockObj, config: ConfigType) -> None: uom_idx = config.get(_KEY_UOM_IDX, 0) icon_idx = config.get(_KEY_ICON_IDX, 0) packed = (dc_idx << _DC_SHIFT) | (uom_idx << _UOM_SHIFT) | (icon_idx << _ICON_SHIFT) - add(var.configure_entity(entity_name, object_id_hash, packed)) + add(var.configure_entity_(entity_name, object_id_hash, packed)) def get_base_entity_object_id( @@ -330,7 +330,7 @@ async def _setup_entity_impl(var: MockObj, config: ConfigType, platform: str) -> device: MockObj = await get_variable(device_id_obj) add(var.set_device(device)) - # Pre-compute entity name and object_id hash for configure_entity() + # Pre-compute entity name and object_id hash for configure_entity_() # which is emitted later by finalize_entity_strings(). # For named entities: pre-compute hash from entity name # For empty-name entities: pass 0, C++ calculates hash at runtime from diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.py b/tests/component_tests/binary_sensor/test_binary_sensor.py index d36d4a4e10a..fbc2f37d9a1 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.py +++ b/tests/component_tests/binary_sensor/test_binary_sensor.py @@ -29,7 +29,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main): ) # Then - assert 'bs_1->configure_entity("test bs1",' in main_cpp + assert 'bs_1->configure_entity_("test bs1",' in main_cpp assert "bs_1->set_pin(" in main_cpp diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py index da90f2c1a55..9f94d61c8c4 100644 --- a/tests/component_tests/button/test_button.py +++ b/tests/component_tests/button/test_button.py @@ -26,7 +26,7 @@ def test_button_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/button/test_button.yaml") # Then - assert 'wol_1->configure_entity("wol_test_1",' in main_cpp + assert 'wol_1->configure_entity_("wol_test_1",' in main_cpp assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp diff --git a/tests/component_tests/sensor/test_sensor.py b/tests/component_tests/sensor/test_sensor.py index c489f99b503..1fd9322c079 100644 --- a/tests/component_tests/sensor/test_sensor.py +++ b/tests/component_tests/sensor/test_sensor.py @@ -11,4 +11,4 @@ def test_sensor_device_class_set(generate_main): main_cpp = generate_main("tests/component_tests/sensor/test_sensor.yaml") # Then - assert "s_1->configure_entity(" in main_cpp + assert "s_1->configure_entity_(" in main_cpp diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index 2d168aa79dd..3ceaa9b8f81 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -25,7 +25,7 @@ def test_text_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/text/test_text.yaml") # Then - assert 'it_1->configure_entity("test 1 text",' in main_cpp + assert 'it_1->configure_entity_("test 1 text",' in main_cpp def test_text_config_value_internal_set(generate_main): diff --git a/tests/component_tests/text_sensor/test_text_sensor.py b/tests/component_tests/text_sensor/test_text_sensor.py index 2203cce5617..cdbb9d2b66e 100644 --- a/tests/component_tests/text_sensor/test_text_sensor.py +++ b/tests/component_tests/text_sensor/test_text_sensor.py @@ -25,9 +25,9 @@ def test_text_sensor_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") # Then - assert 'ts_1->configure_entity("Template Text Sensor 1",' in main_cpp - assert 'ts_2->configure_entity("Template Text Sensor 2",' in main_cpp - assert 'ts_3->configure_entity("Template Text Sensor 3",' in main_cpp + assert 'ts_1->configure_entity_("Template Text Sensor 1",' in main_cpp + assert 'ts_2->configure_entity_("Template Text Sensor 2",' in main_cpp + assert 'ts_3->configure_entity_("Template Text Sensor 3",' in main_cpp def test_text_sensor_config_value_internal_set(generate_main): @@ -54,5 +54,5 @@ def test_text_sensor_device_class_set(generate_main): main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") # Then - assert "ts_2->configure_entity(" in main_cpp - assert "ts_3->configure_entity(" in main_cpp + assert "ts_2->configure_entity_(" in main_cpp + assert "ts_3->configure_entity_(" in main_cpp diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index 0a9f70ca75a..7531e210608 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -31,10 +31,10 @@ from esphome.helpers import sanitize, snake_case from .common import load_config_from_fixture -# Pre-compiled regex pattern for extracting names from configure_entity/set_name calls -# Matches: .configure_entity("name", ...) or .set_name("name", ...) +# Pre-compiled regex pattern for extracting names from configure_entity_/set_name calls +# Matches: .configure_entity_("name", ...) or .set_name("name", ...) ENTITY_NAME_PATTERN = re.compile( - r'\.(?:configure_entity|set_name)\(["\']([^"\']*)["\']' + r'\.(?:configure_entity_|set_name)\(["\']([^"\']*)["\']' ) FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers" @@ -291,7 +291,7 @@ def extract_object_id_from_config(config: dict[str, Any]) -> str | None: def extract_object_id_from_expressions(expressions: list[str]) -> str | None: - """Extract the object ID from configure_entity() calls in generated expressions.""" + """Extract the object ID from configure_entity_() calls in generated expressions.""" for expr in expressions: if match := ENTITY_NAME_PATTERN.search(expr): name = match.group(1) @@ -954,7 +954,7 @@ async def test_setup_entity_direct_call(setup_test_environment: list[str]) -> No # Direct call mode: await setup_entity(var, config, "camera") await setup_entity(var, config, "camera") - # Should have emitted configure_entity + # Should have emitted configure_entity_ object_id = extract_object_id_from_expressions(added_expressions) assert object_id == "my_camera"