"""Nexia RoomIQ utility class.""" import asyncio from collections.abc import Callable, Coroutine from typing import Any from .util import SingleShot from .zone import NexiaThermostatZone class NexiaRoomIQHarmonizer: """Controller to track which RoomIQ sensors are to be selected for a zone and make the selection after inactivity. This helps coordinate separate manual actions taken to select active sensors. """ def __init__( self, zone: NexiaThermostatZone, async_request_refetch: Callable[[], Coroutine[Any, Any, None]], signal_updated: Callable[[], None], after_last_change_seconds=4.0, ) -> None: """Initialize this instance. :param zone: zone to control :param async_request_refetch: coroutine to request a refetch of zone status :param signal_updated: function to signal that our state has changed :param after_last_change_seconds: seconds to delay before selecting sensors """ self.selected_sensor_ids = zone.get_active_sensor_ids() self._zone = zone self._async_request_refetch = async_request_refetch self._signal_updated = signal_updated self._loop = asyncio.get_running_loop() self._request_time: float | None = None self._single_shot = SingleShot( self._loop, after_last_change_seconds, self._select_sensors ) def trigger_add_sensor(self, sensor_id: int) -> None: """Trigger selecting the specified sensor for the zone.""" self._start_trigger() self.selected_sensor_ids.add(sensor_id) self._single_shot.reset_delayed_action_trigger() def trigger_remove_sensor(self, sensor_id: int) -> None: """Trigger removing the specified sensor from the zone selection.""" self._start_trigger() self.selected_sensor_ids.discard(sensor_id) self._single_shot.reset_delayed_action_trigger() def _start_trigger(self): """Start a trigger sequence. If none yet pending, update sensor selection.""" if self._request_time is None: self.selected_sensor_ids = self._zone.get_active_sensor_ids() self._request_time = self._loop.time() async def _select_sensors(self) -> None: """Select the RoomIQ sensors now that the delay has completed. Fires a while following the *last* trigger in the zone's sensor selections. """ active_sensors = self._zone.get_active_sensor_ids() # At least one sensor must be selected and the request should differ. if not self.selected_sensor_ids or self.selected_sensor_ids == active_sensors: self._request_time = None self._signal_updated() return select_time = self._request_time try: await self._zone.select_room_iq_sensors(self.selected_sensor_ids) finally: if self._request_time == select_time: # No new requests have triggered. self._request_time = None await self._async_request_refetch() self._signal_updated() def request_pending(self) -> bool: """Return if a triggered sensor selection is pending.""" return self._request_time is not None async def async_shutdown(self) -> None: """Clean up before stopping.""" self._single_shot.async_shutdown() self._request_time = None self._signal_updated()