diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 3329c250..a8825060 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -291,9 +291,13 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, api: API) -> None: self._update_task: list[asyncio.Task | None] = [None, None] self.subscriptions: List[Optional[asyncio.Task]] = [None] self.invocations = { - 0: self.subscribe_to_location, - 1: self.subscribe_to_challenge, - 2: self.subscribe_to_challengelist, + "device": { + 0: self.subscribe_to_location, + }, + "challenge": { + 1: self.subscribe_to_challenge, + 2: self.subscribe_to_challengelist, + }, } self.hq_plan_name = entry.options.get(CONF_HQ_PLAN_NAME, DEFAULT_HQ_PLAN_NAME) self.appreciation = entry.options.get( @@ -440,9 +444,10 @@ async def _handle_device_events(self, event: WebsocketEvent) -> None: ) if new_devices: LOG.warning( - "Device list appears to be desynchronized, forcing a refresh thru the API..." + "Device list appears to be desynchronized, " + "waiting for next DeviceListInitialValuesReceived to refresh..." ) - await self.devices.update() + # Device list will refresh on next websocket reconnect/subscribe updated_devices = self.devices.parse_values_received(event.arguments[0]) # NOTE(dvd): If we don't do this, we need to wait until the coordinator @@ -469,7 +474,7 @@ async def _handle_device_events(self, event: WebsocketEvent) -> None: elif event.target == "DeviceAdded": devices = [event.arguments[0]] - await self.devices.update_devicelist_from_signalr(devices) + await self.devices.add_device_from_signalr(devices) elif event.target == "DeviceDeleted": LOG.debug("Received 'DeviceDeleted' message, not implemented yet.") @@ -492,7 +497,10 @@ async def on_websocket_event(self, event: WebsocketEvent) -> None: async_dispatcher_send(self._hass, DISPATCHER_TOPIC_WEBSOCKET_EVENT, event) if event.event_type == "COMPLETE": - cb = self.invocations.get(event.invocation) + # Look up the callback in both device and challenge invocation groups + cb = self.invocations["device"].get(event.invocation) or self.invocations[ + "challenge" + ].get(event.invocation) if cb: async_call_later(self._hass, 3, cb(event.invocation)) @@ -642,14 +650,16 @@ async def request_challenge_consumption_update( async def request_status_update(self) -> None: """Request a status update from the device websocket.""" await self._api.websocket_devices.send_status() - for inv_id, inv_cb in self.invocations.items(): + + for inv_id, inv_cb in self.invocations["device"].items(): await inv_cb(inv_id) @callback async def request_status_update_challenge(self) -> None: """Request a status update from the challenge websocket.""" await self._api.websocket_challenges.send_status() - for inv_id, inv_cb in self.invocations.items(): + + for inv_id, inv_cb in self.invocations["challenge"].items(): await inv_cb(inv_id) @callback @@ -709,12 +719,46 @@ async def get_event_details(self, event_id: int): return self._events[event_id] async def async_init(self, scan_interval: int) -> None: - """Initialize the Hilo "manager" class.""" + """Initialize the Hilo "manager" class. + + Flow: + 1. Get location IDs (REST - kept) + 2. Register websocket callbacks and connect + 3. Websocket subscribes to location, triggering DeviceListInitialValuesReceived + 4. Wait for device cache to be populated from websocket + 5. Build device list (websocket devices + gateway from REST) + 6. Initialize GraphQL, register custom devices, start coordinator + """ if TYPE_CHECKING: assert self._api.refresh_token assert self._api.websocket + # Step 1: Get location IDs (still REST) await self.devices.async_init() + + # Step 2: Register websocket callbacks and start connections + # The connect callback triggers subscribe_to_location, which causes + # the server to send DeviceListInitialValuesReceived + self._api.websocket_devices.add_connect_callback(self.request_status_update) + self._api.websocket_devices.add_event_callback(self.on_websocket_event) + self._api.websocket_challenges.add_connect_callback( + self.request_status_update_challenge + ) + self._api.websocket_challenges.add_event_callback(self.on_websocket_event) + self._websocket_reconnect_tasks[0] = asyncio.create_task( + self.start_websocket_loop(self._api.websocket_devices, 0) + ) + self._websocket_reconnect_tasks[1] = asyncio.create_task( + self.start_websocket_loop(self._api.websocket_challenges, 1) + ) + + # Step 3: Wait for DeviceListInitialValuesReceived from websocket + await self._api.wait_for_device_cache(timeout=30.0) + + # Step 4: Build device list (websocket devices + gateway REST + callbacks) + await self.devices.update() + + # Step 5: Initialize GraphQL await self.graphql_helper.async_init() self.subscriptions[0] = asyncio.create_task( self.graphql_helper.subscribe_to_device_updated( @@ -723,6 +767,7 @@ async def async_init(self, scan_interval: int) -> None: ) ) + # Step 6: Register custom devices in HA _async_register_custom_device( self._hass, self.entry, self.devices.find_device(1) ) @@ -736,21 +781,6 @@ async def async_init(self, scan_interval: int) -> None: self._hass, self.entry, self.unknown_tracker_device ) - self._api.websocket_devices.add_connect_callback(self.request_status_update) - self._api.websocket_devices.add_event_callback(self.on_websocket_event) - self._api.websocket_challenges.add_connect_callback( - self.request_status_update_challenge - ) - self._api.websocket_challenges.add_event_callback(self.on_websocket_event) - self._websocket_reconnect_tasks[0] = asyncio.create_task( - self.start_websocket_loop(self._api.websocket_devices, 0) - ) - self._websocket_reconnect_tasks[1] = asyncio.create_task( - self.start_websocket_loop(self._api.websocket_challenges, 1) - ) - - # asyncio.create_task(self._api.websocket_devices.async_connect()) - async def websocket_disconnect_listener(_: Event) -> None: """Define an event handler to disconnect from the websocket.""" if TYPE_CHECKING: diff --git a/custom_components/hilo/manifest.json b/custom_components/hilo/manifest.json index 54dfc225..ff9656e0 100755 --- a/custom_components/hilo/manifest.json +++ b/custom_components/hilo/manifest.json @@ -11,6 +11,6 @@ "documentation": "https://github.com/dvd-dev/hilo", "iot_class": "cloud_push", "issue_tracker": "https://github.com/dvd-dev/hilo/issues", - "requirements": ["python-hilo>=2026.3.1"], - "version": "2026.3.1" + "requirements": ["python-hilo>=2026.3.2"], + "version": "2026.3.2" }