[ld6002b] Find the number, select and switch setting by entity pointer in the hub

This commit is contained in:
J. Nick Koston
2026-10-16 11:15:12 -10:00
parent bed58f5895
commit 93c1ef5c50
7 changed files with 149 additions and 174 deletions
+1 -48
View File
@@ -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}}"))
+29 -8
View File
@@ -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);",
],
)
+49 -3
View File
@@ -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<uint32_t>(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) {
+9 -3
View File
@@ -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:
+32 -52
View File
@@ -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)
+23 -39
View File
@@ -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
)
+6 -21
View File
@@ -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)