diff --git a/esphome/components/ld6002b/__init__.py b/esphome/components/ld6002b/__init__.py index 4c21d6249c2..c7529f93149 100644 --- a/esphome/components/ld6002b/__init__.py +++ b/esphome/components/ld6002b/__init__.py @@ -1,12 +1,9 @@ -from collections.abc import Awaitable, Callable - from esphome import pins import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_WAKEUP_PIN -from esphome.cpp_generator import MockObj -from esphome.types import ConfigType, Expression +from esphome.types import ConfigType from .const import CONF_AUTO_WAKE, CONF_WAKEUP_PULSE @@ -76,47 +73,3 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_wakeup_pulse_ms(config[CONF_WAKEUP_PULSE].total_milliseconds)) cg.add(var.set_auto_wake(config[CONF_AUTO_WAKE])) - - -# (type, entity config, creates the entity from its template arguments, hub setter or None) -DispatchEntity = tuple[ - MockObj, - ConfigType, - Callable[[cg.TemplateArguments], Awaitable[MockObj]], - Callable[[MockObj], Expression] | None, -] - - -async def build_type_dispatch( - hub: MockObj, - params: str, - call: str, - entities: list[DispatchEntity], - publish_before: str | None = None, - publish_after: str | None = None, -) -> None: - """Give all of a platform's entities one generated function that finds the type by pointer. - - One class per domain instead of one per entity; ``call`` names the type as ``type``. - """ - if not entities: - return - fn = f"esphome__{entities[0][1][CONF_ID].id}__fn" - # Declared first: the entities' types name it and its body names the entities. - cg.add_global(cg.RawStatement(f"static void {fn}({params});")) - template = cg.TemplateArguments(cg.RawExpression(fn)) - created: list[tuple[MockObj, MockObj]] = [] - for type_, _, create, setter in entities: - var = await create(template) - if setter is not None: - cg.add(setter(var)) - created.append((type_, var)) - *head, (last_type, _) = created - chain = "".join(f"entity == ::{var} ? {type_} : " for type_, var in head) - body = [f"const auto type = {chain}{last_type};", f"::{hub}->{call};"] - if publish_before: - body.insert(0, publish_before) - if publish_after: - body.append(publish_after) - lines = "\n ".join(body) - cg.add_global(cg.RawStatement(f"static void {fn}({params}) {{\n {lines}\n}}")) diff --git a/esphome/components/ld6002b/button/__init__.py b/esphome/components/ld6002b/button/__init__.py index bbbee0c8472..22e88062c1b 100644 --- a/esphome/components/ld6002b/button/__init__.py +++ b/esphome/components/ld6002b/button/__init__.py @@ -1,5 +1,4 @@ -from functools import partial - +from esphome.automation import parent_ref import esphome.codegen as cg from esphome.components import button from esphome.components.select import DOMAIN as SELECT_DOMAIN @@ -10,10 +9,11 @@ from esphome.const import ( ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_DIAGNOSTIC, ) +from esphome.cpp_generator import ParameterListExpression import esphome.final_validate as fv from esphome.types import ConfigType -from .. import LD6002BComponent, build_type_dispatch, ld6002b_ns +from .. import LD6002BComponent, ld6002b_ns from ..const import ( CONF_APPLY_AREA, CONF_AUTO_INTERFERENCE, @@ -131,12 +131,33 @@ BUTTON_MAP = { async def to_code(config: ConfigType) -> None: - hub = await cg.get_variable(config[CONF_LD6002B_ID]) - entities = [ - (button_type, conf, partial(button.new_button, conf), None) + confs = [ + (button_type, conf) for key, button_type in BUTTON_MAP.items() if (conf := config.get(key)) is not None ] - await build_type_dispatch( - hub, "button::Button *entity", "press_button(type)", entities + if not confs: + return + hub = await cg.get_variable(config[CONF_LD6002B_ID]) + params = button.sub_buttons(config).apply_entity.params + # One function for all buttons, picking the type by pointer: one class instead of one per button. + fn = f"esphome__{hub}__button_fn" + cg.add_global( + cg.RawStatement(f"static void {fn}({ParameterListExpression(*params)});") + ) + template = cg.TemplateArguments(cg.RawExpression(fn)) + created = [ + (button_type, await button.new_button(conf, template)) + for button_type, conf in confs + ] + *head, (last_type, _) = created + chain = "".join(f"entity == {parent_ref(var)} ? {t} : " for t, var in head) + cg.static_function( + fn, + cg.void, + list(params), + [ + f"const auto type = {chain}{last_type};", + f"{parent_ref(hub)}->press_button(type);", + ], ) diff --git a/esphome/components/ld6002b/ld6002b.cpp b/esphome/components/ld6002b/ld6002b.cpp index 93a2ea341f4..f152488fa95 100644 --- a/esphome/components/ld6002b/ld6002b.cpp +++ b/esphome/components/ld6002b/ld6002b.cpp @@ -1368,7 +1368,30 @@ void LD6002BComponent::wake_() { [this]() { this->wakeup_pin_->digital_write(true); }); } -void LD6002BComponent::set_number_value(NumberType type, float value) { +#ifdef USE_NUMBER +void LD6002BComponent::set_number_value(number::Number *entity, float value) { + // Found by pointer, so the entities share one generated class instead of storing a type each + NumberType type; + if (entity == this->hold_delay_number_) + type = NumberType::HOLD_DELAY; + else if (entity == this->z_min_number_) + type = NumberType::Z_MIN; + else if (entity == this->z_max_number_) + type = NumberType::Z_MAX; + else if (entity == this->low_power_sleep_number_) + type = NumberType::LOW_POWER_SLEEP; + else if (entity == this->area_x_min_number_) + type = NumberType::AREA_X_MIN; + else if (entity == this->area_x_max_number_) + type = NumberType::AREA_X_MAX; + else if (entity == this->area_y_min_number_) + type = NumberType::AREA_Y_MIN; + else if (entity == this->area_y_max_number_) + type = NumberType::AREA_Y_MAX; + else if (entity == this->area_z_min_number_) + type = NumberType::AREA_Z_MIN; + else + type = NumberType::AREA_Z_MAX; switch (type) { case NumberType::HOLD_DELAY: { uint32_t delay = static_cast(value); @@ -1418,8 +1441,20 @@ void LD6002BComponent::set_number_value(NumberType type, float value) { break; } } +#endif -void LD6002BComponent::set_select_value(SelectType type, size_t index) { +#ifdef USE_SELECT +void LD6002BComponent::set_select_value(select::Select *entity, size_t index) { + // Found by pointer, so the entities share one generated class instead of storing a type each + SelectType type; + if (entity == this->sensitivity_select_) + type = SelectType::SENSITIVITY; + else if (entity == this->trigger_speed_select_) + type = SelectType::TRIGGER_SPEED; + else if (entity == this->installation_select_) + type = SelectType::INSTALLATION_MODE; + else + type = SelectType::AREA_ID; switch (type) { case SelectType::SENSITIVITY: if (index == 0) { @@ -1454,6 +1489,7 @@ void LD6002BComponent::set_select_value(SelectType type, size_t index) { break; } } +#endif void LD6002BComponent::update_area_numbers_(const AreaConfig &area) { // A report refreshes every axis the user is not in the middle of changing. An @@ -1781,7 +1817,16 @@ void LD6002BComponent::clear_target_state_() { } } -void LD6002BComponent::set_switch_state(SwitchType type, bool state) { +#ifdef USE_SWITCH +void LD6002BComponent::set_switch_state(switch_::Switch *entity, bool state) { + // Found by pointer, so the entities share one generated class instead of storing a type each + SwitchType type; + if (entity == this->low_power_switch_) + type = SwitchType::LOW_POWER; + else if (entity == this->point_cloud_switch_) + type = SwitchType::POINT_CLOUD; + else + type = SwitchType::TARGET_DISPLAY; switch (type) { case SwitchType::LOW_POWER: this->low_power_enabled_ = state; @@ -1812,6 +1857,7 @@ void LD6002BComponent::set_switch_state(SwitchType type, bool state) { break; } } +#endif void LD6002BComponent::press_button(ButtonType type) { switch (type) { diff --git a/esphome/components/ld6002b/ld6002b.h b/esphome/components/ld6002b/ld6002b.h index c52f770d6e9..a47ec4a8709 100644 --- a/esphome/components/ld6002b/ld6002b.h +++ b/esphome/components/ld6002b/ld6002b.h @@ -264,9 +264,15 @@ class LD6002BComponent : public Component, public uart::UARTDevice { void set_target_display_switch(switch_::Switch *sw) { this->target_display_switch_ = sw; } #endif - void set_number_value(NumberType type, float value); - void set_select_value(SelectType type, size_t index); - void set_switch_state(SwitchType type, bool state); +#ifdef USE_NUMBER + void set_number_value(number::Number *entity, float value); +#endif +#ifdef USE_SELECT + void set_select_value(select::Select *entity, size_t index); +#endif +#ifdef USE_SWITCH + void set_switch_state(switch_::Switch *entity, bool state); +#endif void press_button(ButtonType type); protected: diff --git a/esphome/components/ld6002b/number/__init__.py b/esphome/components/ld6002b/number/__init__.py index f335c708189..203bed88b30 100644 --- a/esphome/components/ld6002b/number/__init__.py +++ b/esphome/components/ld6002b/number/__init__.py @@ -1,5 +1,3 @@ -from functools import partial - import esphome.codegen as cg from esphome.components import number from esphome.components.button import DOMAIN as BUTTON_DOMAIN @@ -17,7 +15,7 @@ from esphome.const import ( import esphome.final_validate as fv from esphome.types import ConfigType -from .. import DispatchEntity, LD6002BComponent, build_type_dispatch, ld6002b_ns +from .. import LD6002BComponent from ..const import ( CONF_APPLY_AREA, CONF_AREA_CONFIG, @@ -34,8 +32,6 @@ from ..const import ( DEPENDENCIES = ["ld6002b"] -NumberType = ld6002b_ns.enum("NumberType", is_class=True) - CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_LD6002B_ID): cv.use_id(LD6002BComponent), @@ -140,52 +136,36 @@ FINAL_VALIDATE_SCHEMA = final_validate async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_LD6002B_ID]) - entities: list[DispatchEntity] = [] - - for key, number_type, setter, min_value, max_value, step in ( - (CONF_HOLD_DELAY, NumberType.HOLD_DELAY, "set_hold_delay_number", 0, 65535, 1), - (CONF_Z_MIN, NumberType.Z_MIN, "set_z_min_number", -10, 10, 0.1), - (CONF_Z_MAX, NumberType.Z_MAX, "set_z_max_number", -10, 10, 0.1), - # 0x0205 carries a uint32 of milliseconds; the vendor documents 500 ms as - # the default and no upper bound, so the range ends at a minute rather - # than at a default the module is free to be sleeping past. - ( - CONF_LOW_POWER_SLEEP_TIME, - NumberType.LOW_POWER_SLEEP, - "set_low_power_sleep_number", - 0, - 60000, - 100, - ), - ): - if (conf := config.get(key)) is not None: - create = partial( - number.new_number, - conf, - min_value=min_value, - max_value=max_value, - step=step, - ) - entities.append((number_type, conf, create, getattr(hub, setter))) + control = {"apply": "set_number_value(entity, value)", "publish": "before"} + numbers = number.sub_numbers(config, parent=hub) + await numbers( + CONF_HOLD_DELAY, + hub.set_hold_delay_number, + min_value=0, + max_value=65535, + step=1, + **control, + ) + meters = {"min_value": -10, "max_value": 10, "step": 0.1, **control} + await numbers(CONF_Z_MIN, hub.set_z_min_number, **meters) + await numbers(CONF_Z_MAX, hub.set_z_max_number, **meters) + # 0x0205 carries a uint32 of milliseconds; the vendor documents 500 ms as + # the default and no upper bound, so the range ends at a minute rather + # than at a default the module is free to be sleeping past. + await numbers( + CONF_LOW_POWER_SLEEP_TIME, + hub.set_low_power_sleep_number, + min_value=0, + max_value=60000, + step=100, + **control, + ) if area_config := config.get(CONF_AREA_CONFIG): - for key, number_type, setter in ( - (KEY_X_MIN, NumberType.AREA_X_MIN, "set_area_x_min_number"), - (KEY_X_MAX, NumberType.AREA_X_MAX, "set_area_x_max_number"), - (KEY_Y_MIN, NumberType.AREA_Y_MIN, "set_area_y_min_number"), - (KEY_Y_MAX, NumberType.AREA_Y_MAX, "set_area_y_max_number"), - (CONF_Z_MIN, NumberType.AREA_Z_MIN, "set_area_z_min_number"), - (CONF_Z_MAX, NumberType.AREA_Z_MAX, "set_area_z_max_number"), - ): - if (conf := area_config.get(key)) is not None: - create = partial( - number.new_number, conf, min_value=-10, max_value=10, step=0.1 - ) - entities.append((number_type, conf, create, getattr(hub, setter))) - await build_type_dispatch( - hub, - "number::Number *entity, float value", - "set_number_value(type, value)", - entities, - publish_before="entity->publish_state(value);", - ) + area = number.sub_numbers(area_config, parent=hub) + await area(KEY_X_MIN, hub.set_area_x_min_number, **meters) + await area(KEY_X_MAX, hub.set_area_x_max_number, **meters) + await area(KEY_Y_MIN, hub.set_area_y_min_number, **meters) + await area(KEY_Y_MAX, hub.set_area_y_max_number, **meters) + await area(CONF_Z_MIN, hub.set_area_z_min_number, **meters) + await area(CONF_Z_MAX, hub.set_area_z_max_number, **meters) diff --git a/esphome/components/ld6002b/select/__init__.py b/esphome/components/ld6002b/select/__init__.py index 281b7579fc2..835d301e4cf 100644 --- a/esphome/components/ld6002b/select/__init__.py +++ b/esphome/components/ld6002b/select/__init__.py @@ -1,18 +1,14 @@ -from functools import partial - import esphome.codegen as cg from esphome.components import select import esphome.config_validation as cv from esphome.const import CONF_AREA_ID, CONF_SENSITIVITY, ENTITY_CATEGORY_CONFIG from esphome.types import ConfigType -from .. import DispatchEntity, LD6002BComponent, build_type_dispatch, ld6002b_ns +from .. import LD6002BComponent from ..const import CONF_INSTALLATION_MODE, CONF_LD6002B_ID, CONF_TRIGGER_SPEED DEPENDENCIES = ["ld6002b"] -SelectType = ld6002b_ns.enum("SelectType", is_class=True) - AREA_ID_OPTIONS = [ "interference_area_0", "interference_area_1", @@ -43,40 +39,28 @@ CONFIG_SCHEMA = cv.Schema( ) -SELECT_MAP = ( - ( - CONF_SENSITIVITY, - SelectType.SENSITIVITY, - "set_sensitivity_select", - ["low", "medium", "high"], - ), - ( - CONF_TRIGGER_SPEED, - SelectType.TRIGGER_SPEED, - "set_trigger_speed_select", - ["slow", "medium", "fast"], - ), - ( - CONF_INSTALLATION_MODE, - SelectType.INSTALLATION_MODE, - "set_installation_select", - ["top", "side"], - ), - (CONF_AREA_ID, SelectType.AREA_ID, "set_area_id_select", AREA_ID_OPTIONS), -) - - async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_LD6002B_ID]) - entities: list[DispatchEntity] = [] - for key, select_type, setter, options in SELECT_MAP: - if (conf := config.get(key)) is not None: - create = partial(select.new_select, conf, options=options) - entities.append((select_type, conf, create, getattr(hub, setter))) - await build_type_dispatch( - hub, - "select::Select *entity, size_t index", - "set_select_value(type, index)", - entities, - publish_before="entity->publish_state(index);", + control = {"apply": "set_select_value(entity, index)", "publish": "before"} + selects = select.sub_selects(config, parent=hub) + await selects( + CONF_SENSITIVITY, + hub.set_sensitivity_select, + options=["low", "medium", "high"], + **control, + ) + await selects( + CONF_TRIGGER_SPEED, + hub.set_trigger_speed_select, + options=["slow", "medium", "fast"], + **control, + ) + await selects( + CONF_INSTALLATION_MODE, + hub.set_installation_select, + options=["top", "side"], + **control, + ) + await selects( + CONF_AREA_ID, hub.set_area_id_select, options=AREA_ID_OPTIONS, **control ) diff --git a/esphome/components/ld6002b/switch/__init__.py b/esphome/components/ld6002b/switch/__init__.py index 7ee4612c832..247cecb16d5 100644 --- a/esphome/components/ld6002b/switch/__init__.py +++ b/esphome/components/ld6002b/switch/__init__.py @@ -1,12 +1,10 @@ -from functools import partial - import esphome.codegen as cg from esphome.components import switch import esphome.config_validation as cv from esphome.const import DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG from esphome.types import ConfigType -from .. import DispatchEntity, LD6002BComponent, build_type_dispatch, ld6002b_ns +from .. import LD6002BComponent from ..const import ( CONF_LD6002B_ID, CONF_LOW_POWER, @@ -16,8 +14,6 @@ from ..const import ( DEPENDENCIES = ["ld6002b"] -SwitchType = ld6002b_ns.enum("SwitchType", is_class=True) - # None of these three carry an inversion. They name what the module is doing, not # how something is wired to it, so an inverted one would only report the opposite # of the truth -- and the boot restore, which applies a state nothing reports back, @@ -50,19 +46,8 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_LD6002B_ID]) - entities: list[DispatchEntity] = [] - for key, switch_type, setter in ( - (CONF_LOW_POWER, SwitchType.LOW_POWER, "set_low_power_switch"), - (CONF_POINT_CLOUD, SwitchType.POINT_CLOUD, "set_point_cloud_switch"), - (CONF_TARGET_DISPLAY, SwitchType.TARGET_DISPLAY, "set_target_display_switch"), - ): - if (conf := config.get(key)) is not None: - create = partial(switch.new_switch, conf) - entities.append((switch_type, conf, create, getattr(hub, setter))) - await build_type_dispatch( - hub, - "switch_::Switch *entity, bool state", - "set_switch_state(type, state)", - entities, - publish_after="entity->publish_state(state);", - ) + control = {"apply": "set_switch_state(entity, state)", "publish": "after"} + switches = switch.sub_switches(config, parent=hub) + await switches(CONF_LOW_POWER, hub.set_low_power_switch, **control) + await switches(CONF_POINT_CLOUD, hub.set_point_cloud_switch, **control) + await switches(CONF_TARGET_DISPLAY, hub.set_target_display_switch, **control)