[ld6002b] Use Apply entities and find the setting by entity pointer (#20937)

This commit is contained in:
J. Nick Koston authored and GitHub committed 2026-10-26 12:09:03 -04:00
1 parent 78f0b1ceba
commit a680974da2
18 files changed
+454 -372

No files matched your search

@@ -0,0 +1,49 @@
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
uart:
- id: uart_a
tx_pin: GPIO17
rx_pin: GPIO16
baud_rate: 115200
- id: uart_b
tx_pin: GPIO18
rx_pin: GPIO19
baud_rate: 115200
ld6002b:
- id: hub_a
uart_id: uart_a
wakeup_pin: GPIO14
- id: hub_b
uart_id: uart_b
button:
# Two blocks on hub_a: the dispatcher names must not clash.
- platform: ld6002b
ld6002b_id: hub_a
get_delay:
id: a_get_delay
name: A Get Delay
get_sensitivity:
id: a_get_sensitivity
name: A Get Sensitivity
wake:
id: a_wake
name: A Wake
- platform: ld6002b
ld6002b_id: hub_a
get_z_range:
id: a_get_z_range
name: A Get Z Range
# A single button on hub_b: no pointer chain, only the fallback type.
- platform: ld6002b
ld6002b_id: hub_b
get_installation:
id: b_get_installation
name: B Get Installation
@@ -0,0 +1,82 @@
"""Code generation tests for the ld6002b button dispatcher.
Each button block gets one function that maps the pressed entity to its button
type and calls its own hub; the last button of a block is the fallback type.
"""
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
import re
import pytest
YAML = Path(__file__).parent / "button_dispatch.yaml"
def _fn_body(main_cpp: str, fn: str) -> str:
match = re.search(
rf"static void {fn}\(button::Button \* entity\) \{{\n(.*?)\n\}}",
main_cpp,
re.DOTALL,
)
assert match is not None, f"{fn} not defined"
return match.group(1)
@pytest.mark.parametrize(
("fn", "type_expr", "hub"),
[
# Two blocks on hub_a: each named after its first button, chain ends in the fallback.
(
"esphome__a_get_delay__button_fn",
(
"entity == ::a_get_delay ? ld6002b::ButtonType::GET_DELAY : "
"entity == ::a_get_sensitivity ? ld6002b::ButtonType::GET_SENSITIVITY : "
"ld6002b::ButtonType::WAKE"
),
"hub_a",
),
(
"esphome__a_get_z_range__button_fn",
"ld6002b::ButtonType::GET_Z_RANGE",
"hub_a",
),
# A single button on a second hub: no chain, its own hub.
(
"esphome__b_get_installation__button_fn",
"ld6002b::ButtonType::GET_INSTALLATION",
"hub_b",
),
],
)
def test_dispatch_function(
generate_main: Callable[[str | Path], str], fn: str, type_expr: str, hub: str
) -> None:
main_cpp = generate_main(YAML)
assert main_cpp.count(f"static void {fn}(button::Button * entity) {{") == 1
assert _fn_body(main_cpp, fn) == (
f" const auto type = {type_expr};\n ::{hub}->press_button(type);"
)
def test_each_button_uses_its_block_function(
generate_main: Callable[[str | Path], str],
) -> None:
main_cpp = generate_main(YAML)
expected = {
"a_get_delay": "esphome__a_get_delay__button_fn",
"a_get_sensitivity": "esphome__a_get_delay__button_fn",
"a_wake": "esphome__a_get_delay__button_fn",
"a_get_z_range": "esphome__a_get_z_range__button_fn",
"b_get_installation": "esphome__b_get_installation__button_fn",
}
for button_id, fn in expected.items():
assert f"new({button_id}) button::ApplyButton<{fn}>();" in main_cpp
# One definition per block, no more.
assert (
len(re.findall(r"static void esphome__\w+__button_fn\(.*\) \{", main_cpp)) == 3
)
@@ -9,11 +9,17 @@ from __future__ import annotations
import pytest
from esphome.components.ld6002b import validate_unique_settings
from esphome.components.ld6002b.button import (
CONFIG_SCHEMA as BUTTON_CONFIG_SCHEMA,
FINAL_VALIDATE_SCHEMA as BUTTON_FINAL_VALIDATE_SCHEMA,
)
from esphome.components.ld6002b.const import CONF_AREA_CONFIG, CONF_Z_MIN
from esphome.components.ld6002b.const import (
CONF_AREA_CONFIG,
CONF_HOLD_DELAY,
CONF_Z_MAX,
CONF_Z_MIN,
)
from esphome.components.ld6002b.number import (
CONFIG_SCHEMA as NUMBER_CONFIG_SCHEMA,
FINAL_VALIDATE_SCHEMA as NUMBER_FINAL_VALIDATE_SCHEMA,
@@ -40,6 +46,7 @@ def _full_config(
*,
selects: list[ConfigType] | None = None,
buttons: list[ConfigType] | None = None,
numbers: list[ConfigType] | None = None,
) -> Config:
"""A full config carrying one ld6002b hub, as the ID pass leaves it.
@@ -57,6 +64,8 @@ def _full_config(
full["select"] = selects
if buttons is not None:
full[CONF_BUTTON] = buttons
if numbers is not None:
full["number"] = numbers
return full
@@ -214,3 +223,53 @@ def test_area_config_without_select_is_rejected(
),
):
_validated_numbers(_area_numbers())
def _number_block(**settings: ConfigType) -> ConfigType:
"""A number platform block on the shared hub, as final validation sees it."""
return {
"platform": "ld6002b",
"ld6002b_id": ID(HUB_ID, is_declaration=False, type="ld6002b"),
**settings,
}
@pytest.mark.parametrize(
("first", "second", "error"),
[
(
{CONF_HOLD_DELAY: {"name": "A"}},
{CONF_HOLD_DELAY: {"name": "B"}},
r"^hold_delay is already set .* @ data\['hold_delay'\]$",
),
(
{CONF_AREA_CONFIG: {CONF_Z_MIN: {"name": "A"}}},
{CONF_AREA_CONFIG: {CONF_Z_MIN: {"name": "B"}}},
r"^area_config\.z_min is already set .* @ data\['area_config'\]\['z_min'\]$",
),
(
{CONF_AREA_CONFIG: {CONF_Z_MIN: {"name": "A"}}},
{CONF_AREA_CONFIG: {CONF_Z_MAX: {"name": "B"}}},
None,
),
({CONF_HOLD_DELAY: {"name": "A"}}, {CONF_Z_MIN: {"name": "B"}}, None),
],
)
def test_setting_in_two_blocks_of_one_hub(
set_core_config: SetCoreConfigCallable,
first: ConfigType,
second: ConfigType,
error: str | None,
) -> None:
"""Each setting maps to one entity pointer, so two blocks cannot share one."""
blocks = [_number_block(**first), _number_block(**second)]
set_core_config(
PlatformFramework.ESP32_IDF,
full_config=_full_config(_hub(wakeup_pin=False), numbers=blocks),
)
if error is None:
validate_unique_settings("number", blocks[1])
return
with pytest.raises(cv.Invalid, match=error):
validate_unique_settings("number", blocks[1])