mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 13:43:00 +00:00
[ld2450] Add integration tests with mock UART (#14611)
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user