diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 0c6660ce64..46c000562e 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -48,8 +48,8 @@ void arch_init() { void HOT arch_feed_wdt() { esp_task_wdt_reset(); } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } -uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } const char *progmem_read_ptr(const char *const *addr) { return *addr; } +uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); } uint32_t arch_get_cpu_freq_hz() { uint32_t freq = 0; diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 22e14ae9ee..3a5d87792b 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -368,16 +368,11 @@ SETTERS = { } -@setup_entity("camera") -async def _setup_esp32_camera(var, config): - pass - - async def to_code(config): cg.add_define("USE_CAMERA") socket.require_wake_loop_threadsafe() var = cg.new_Pvariable(config[CONF_ID]) - await _setup_esp32_camera(var, config) + await setup_entity(var, config, "camera") await cg.register_component(var, config) for key, setter in SETTERS.items(): diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index a43e917b1c..159ec20e77 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -34,12 +34,12 @@ void HOT arch_feed_wdt() { system_soft_wdt_feed(); } uint8_t progmem_read_byte(const uint8_t *addr) { return pgm_read_byte(addr); // NOLINT } -uint16_t progmem_read_uint16(const uint16_t *addr) { - return pgm_read_word(addr); // NOLINT -} const char *progmem_read_ptr(const char *const *addr) { return reinterpret_cast(pgm_read_ptr(addr)); // NOLINT } +uint16_t progmem_read_uint16(const uint16_t *addr) { + return pgm_read_word(addr); // NOLINT +} uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return esp_get_cycle_count(); } uint32_t arch_get_cpu_freq_hz() { return F_CPU; } diff --git a/esphome/components/host/core.cpp b/esphome/components/host/core.cpp index e252b10456..d5c61ec986 100644 --- a/esphome/components/host/core.cpp +++ b/esphome/components/host/core.cpp @@ -59,8 +59,8 @@ void HOT arch_feed_wdt() { } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } -uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } const char *progmem_read_ptr(const char *const *addr) { return *addr; } +uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } uint32_t arch_get_cpu_cycle_count() { struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); diff --git a/esphome/components/libretiny/core.cpp b/esphome/components/libretiny/core.cpp index 47f7b6d2fa..893a79440a 100644 --- a/esphome/components/libretiny/core.cpp +++ b/esphome/components/libretiny/core.cpp @@ -36,8 +36,8 @@ void HOT arch_feed_wdt() { lt_wdt_feed(); } uint32_t arch_get_cpu_cycle_count() { return lt_cpu_get_cycle_count(); } uint32_t arch_get_cpu_freq_hz() { return lt_cpu_get_freq(); } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } -uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } const char *progmem_read_ptr(const char *const *addr) { return *addr; } +uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } } // namespace esphome diff --git a/esphome/components/rp2040/core.cpp b/esphome/components/rp2040/core.cpp index 6715ea7ca1..63b154d80d 100644 --- a/esphome/components/rp2040/core.cpp +++ b/esphome/components/rp2040/core.cpp @@ -34,10 +34,10 @@ void HOT arch_feed_wdt() { watchdog_update(); } uint8_t progmem_read_byte(const uint8_t *addr) { return pgm_read_byte(addr); // NOLINT } -uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } const char *progmem_read_ptr(const char *const *addr) { return reinterpret_cast(pgm_read_ptr(addr)); // NOLINT } +uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } uint32_t HOT arch_get_cpu_cycle_count() { return ulMainGetRunTimeCounterValue(); } uint32_t arch_get_cpu_freq_hz() { return RP2040::f_cpu(); } diff --git a/esphome/components/zephyr/core.cpp b/esphome/components/zephyr/core.cpp index c06bbb2a5d..eee7fb3f4f 100644 --- a/esphome/components/zephyr/core.cpp +++ b/esphome/components/zephyr/core.cpp @@ -60,8 +60,8 @@ void arch_restart() { sys_reboot(SYS_REBOOT_COLD); } uint32_t arch_get_cpu_cycle_count() { return k_cycle_get_32(); } uint32_t arch_get_cpu_freq_hz() { return sys_clock_hw_cycles_per_sec(); } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } -uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } const char *progmem_read_ptr(const char *const *addr) { return *addr; } +uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } Mutex::Mutex() { auto *mutex = new k_mutex(); diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index fd151d82d9..551e35df65 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -236,33 +236,44 @@ def get_base_entity_object_id( return sanitize(snake_case(base_str)) -def setup_entity(platform: str) -> Callable: - """Decorator for component setup functions. +def setup_entity(var_or_platform, config=None, platform=None): + """Set up entity properties — works as both decorator and direct call. - Wraps the function to: - 1. Set up common entity properties (name, icon, etc.) - 2. Run the wrapped function (which may call setup_device_class, etc.) - 3. Finalize entity strings (pack dc/uom/icon indices into uint32_t) - - Usage:: + Decorator mode:: @setup_entity("sensor") async def setup_sensor_core_(var, config): setup_device_class(config) setup_unit_of_measurement(config) ... + + Direct call mode (for entities with no extra string properties):: + + await setup_entity(var, config, "camera") """ + if isinstance(var_or_platform, str) and config is None: + # Decorator mode: @setup_entity("sensor") + platform = var_or_platform - def decorator(func: Callable) -> Callable: - @functools.wraps(func) - async def wrapper(var: MockObj, config: ConfigType, *args, **kwargs) -> None: - await _setup_entity_impl(var, config, platform) - await func(var, config, *args, **kwargs) - finalize_entity_strings(var, config) + def decorator(func: Callable) -> Callable: + @functools.wraps(func) + async def wrapper( + var: MockObj, config: ConfigType, *args, **kwargs + ) -> None: + await _setup_entity_impl(var, config, platform) + await func(var, config, *args, **kwargs) + finalize_entity_strings(var, config) - return wrapper + return wrapper - return decorator + return decorator + + # Direct call mode: await setup_entity(var, config, "camera") + async def _do() -> None: + await _setup_entity_impl(var_or_platform, config, platform) + finalize_entity_strings(var_or_platform, config) + + return _do() async def _setup_entity_impl(var: MockObj, config: ConfigType, platform: str) -> None: diff --git a/esphome/core/hal.h b/esphome/core/hal.h index a0593b811a..c2c9b1a325 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -42,7 +42,7 @@ void arch_feed_wdt(); uint32_t arch_get_cpu_cycle_count(); uint32_t arch_get_cpu_freq_hz(); uint8_t progmem_read_byte(const uint8_t *addr); -uint16_t progmem_read_uint16(const uint16_t *addr); const char *progmem_read_ptr(const char *const *addr); +uint16_t progmem_read_uint16(const uint16_t *addr); } // namespace esphome diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index 5dc017a5ed..a5cfad5ab6 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -23,6 +23,7 @@ from esphome.core.entity_helpers import ( _setup_entity_impl, entity_duplicate_validator, get_base_entity_object_id, + setup_entity, ) from esphome.cpp_generator import MockObj from esphome.helpers import sanitize, snake_case @@ -924,3 +925,51 @@ async def test_setup_entity_with_entity_category( assert any( 'set_entity_category("diagnostic")' in expr for expr in added_expressions ) + + +@pytest.mark.asyncio +async def test_setup_entity_direct_call(setup_test_environment: list[str]) -> None: + """Test setup_entity in direct call mode (legacy / backward compat).""" + added_expressions = setup_test_environment + + var = MockObj("camera1") + config = { + CONF_NAME: "My Camera", + CONF_DISABLED_BY_DEFAULT: False, + CONF_ICON: "mdi:camera", + } + + # Direct call mode: await setup_entity(var, config, "camera") + await setup_entity(var, config, "camera") + + # Should have called set_name + object_id = extract_object_id_from_expressions(added_expressions) + assert object_id == "my_camera" + + # Icon index should have been stored and finalized + assert config.get("_entity_icon_idx", 0) > 0 + + +@pytest.mark.asyncio +async def test_setup_entity_decorator_mode(setup_test_environment: list[str]) -> None: + """Test setup_entity in decorator mode.""" + added_expressions = setup_test_environment + + body_called = False + + @setup_entity("sensor") + async def my_setup(var, config): + nonlocal body_called + body_called = True + + var = MockObj("sensor1") + config = { + CONF_NAME: "Temperature", + CONF_DISABLED_BY_DEFAULT: False, + } + + await my_setup(var, config) + + assert body_called + object_id = extract_object_id_from_expressions(added_expressions) + assert object_id == "temperature"