[ld2450] Add integration tests with mock UART (#14611)

This commit is contained in:
J. Nick Koston
2026-03-08 14:32:59 -10:00
committed by GitHub
parent e1c849d5d2
commit aef2d74e41
3 changed files with 448 additions and 5 deletions

View File

@@ -13,6 +13,7 @@ from aioesphomeapi import (
EntityInfo,
EntityState,
SensorState,
TextSensorState,
)
_LOGGER = logging.getLogger(__name__)
@@ -244,12 +245,13 @@ class InitialStateHelper:
class SensorStateCollector:
"""Collects sensor and binary sensor state updates and provides wait helpers.
"""Collects sensor, binary sensor, and text sensor state updates with wait helpers.
Usage:
collector = SensorStateCollector(
sensor_names=["moving_distance", "still_distance"],
binary_sensor_names=["has_target"],
text_sensor_names=["direction"],
)
# Use collector.on_state as the callback (or wrap it)
client.subscribe_states(helper.on_state_wrapper(collector.on_state))
@@ -259,18 +261,23 @@ class SensorStateCollector:
# Access collected states
assert collector.sensor_states["moving_distance"][0] == approx(100.0)
assert collector.text_sensor_states["direction"][0] == "Approaching"
"""
def __init__(
self,
sensor_names: list[str],
binary_sensor_names: list[str] | None = None,
text_sensor_names: list[str] | None = None,
entities: list[EntityInfo] | None = None,
) -> None:
self.sensor_states: dict[str, list[float]] = {name: [] for name in sensor_names}
self.binary_states: dict[str, list[bool]] = {
name: [] for name in (binary_sensor_names or [])
}
self.text_sensor_states: dict[str, list[str]] = {
name: [] for name in (text_sensor_names or [])
}
self._key_to_sensor: dict[int, str] = {}
self._waiters: list[tuple[Callable[[], bool], asyncio.Future[bool]]] = []
@@ -279,7 +286,11 @@ class SensorStateCollector:
def build_key_mapping(self, entities: list[EntityInfo]) -> None:
"""Build key-to-name mapping from entities. Sorted by descending length."""
all_names = list(self.sensor_states.keys()) + list(self.binary_states.keys())
all_names = (
list(self.sensor_states.keys())
+ list(self.binary_states.keys())
+ list(self.text_sensor_states.keys())
)
all_names.sort(key=len, reverse=True)
self._key_to_sensor = build_key_to_entity_mapping(entities, all_names)
@@ -295,6 +306,11 @@ class SensorStateCollector:
if sensor_name and sensor_name in self.binary_states:
self.binary_states[sensor_name].append(state.state)
self._check_waiters()
elif isinstance(state, TextSensorState) and not state.missing_state:
sensor_name = self._key_to_sensor.get(state.key)
if sensor_name and sensor_name in self.text_sensor_states:
self.text_sensor_states[sensor_name].append(state.state)
self._check_waiters()
def _check_waiters(self) -> None:
"""Check all pending waiters and resolve any whose condition is met."""
@@ -303,9 +319,11 @@ class SensorStateCollector:
future.set_result(True)
def _all_have_values(self) -> bool:
"""Check if all sensor and binary sensor lists have at least one value."""
return all(len(v) >= 1 for v in self.sensor_states.values()) and all(
len(v) >= 1 for v in self.binary_states.values()
"""Check if all sensor, binary sensor, and text sensor lists have at least one value."""
return (
all(len(v) >= 1 for v in self.sensor_states.values())
and all(len(v) >= 1 for v in self.binary_states.values())
and all(len(v) >= 1 for v in self.text_sensor_states.values())
)
async def wait_for_all(self, timeout: float = 3.0) -> None: