From b26dc6716d0ce012eb4ace01ba863b852fa89198 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 12 Mar 2026 12:05:26 -0400 Subject: [PATCH 1/6] Update __init__.py Add await pour la fonction qui check la mise en cache locale du devicelist. --- custom_components/hilo/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 3329c250..7f11b5d3 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -713,6 +713,10 @@ async def async_init(self, scan_interval: int) -> None: if TYPE_CHECKING: assert self._api.refresh_token assert self._api.websocket + + # Wait for websocket device cache to be populated + # This ensures devices have correct names and IDs from the start + await self._api.wait_for_device_cache(timeout=10.0) await self.devices.async_init() await self.graphql_helper.async_init() From 5ceb615ead114c01b00490d7b0b1dccd716b34e9 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:32:29 -0400 Subject: [PATCH 2/6] Update __init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enlevé doublons, on callait subscribe to location dans les challenge, et les challenge aussi. --- custom_components/hilo/__init__.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 7f11b5d3..bf255059 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( @@ -640,16 +644,16 @@ async def request_challenge_consumption_update( @callback 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 @@ -713,7 +717,7 @@ async def async_init(self, scan_interval: int) -> None: if TYPE_CHECKING: assert self._api.refresh_token assert self._api.websocket - + # Wait for websocket device cache to be populated # This ensures devices have correct names and IDs from the start await self._api.wait_for_device_cache(timeout=10.0) From 214ccdfa2a7577cf94ce9fb08768d4d2c50c4f6d Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:41:52 -0400 Subject: [PATCH 3/6] Update __init__.py Move this after websocket connects, else we would time out before they started. --- custom_components/hilo/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index bf255059..b0202de7 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -720,7 +720,7 @@ async def async_init(self, scan_interval: int) -> None: # Wait for websocket device cache to be populated # This ensures devices have correct names and IDs from the start - await self._api.wait_for_device_cache(timeout=10.0) + # await self._api.wait_for_device_cache(timeout=10.0) await self.devices.async_init() await self.graphql_helper.async_init() @@ -757,6 +757,7 @@ async def async_init(self, scan_interval: int) -> None: self.start_websocket_loop(self._api.websocket_challenges, 1) ) + await self._api.wait_for_device_cache(timeout=10.0) # asyncio.create_task(self._api.websocket_devices.async_connect()) async def websocket_disconnect_listener(_: Event) -> None: From 1f77209cb300264b2d1b6b52ab7cc7461b4d1b59 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:43:22 -0400 Subject: [PATCH 4/6] Update __init__.py Missing docstrings to make mypy happy --- custom_components/hilo/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index b0202de7..d70e3127 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -644,6 +644,7 @@ async def request_challenge_consumption_update( @callback 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["device"].items(): @@ -651,6 +652,7 @@ async def request_status_update(self) -> None: @callback async def request_status_update_challenge(self) -> None: + """Request a status update from the challenge websockets.""" await self._api.websocket_challenges.send_status() for inv_id, inv_cb in self.invocations["challenge"].items(): @@ -718,10 +720,6 @@ async def async_init(self, scan_interval: int) -> None: assert self._api.refresh_token assert self._api.websocket - # Wait for websocket device cache to be populated - # This ensures devices have correct names and IDs from the start - # await self._api.wait_for_device_cache(timeout=10.0) - await self.devices.async_init() await self.graphql_helper.async_init() self.subscriptions[0] = asyncio.create_task( From c02989958e9d80332fd2edbd5d713501b5e45c38 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:57:40 -0400 Subject: [PATCH 5/6] Update manifest.json Bump up versions --- custom_components/hilo/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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" } From af56ebb9c6ce0f38500208d87f9a208a2f2281d3 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Fri, 13 Mar 2026 16:44:38 -0400 Subject: [PATCH 6/6] Update __init__.py Plus de commentaires pour expliquer ce qu'on fait. --- custom_components/hilo/__init__.py | 67 ++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index d70e3127..a8825060 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -444,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 @@ -473,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.") @@ -496,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)) @@ -652,7 +656,7 @@ async def request_status_update(self) -> None: @callback async def request_status_update_challenge(self) -> None: - """Request a status update from the challenge websockets.""" + """Request a status update from the challenge websocket.""" await self._api.websocket_challenges.send_status() for inv_id, inv_cb in self.invocations["challenge"].items(): @@ -715,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( @@ -729,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) ) @@ -742,22 +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) - ) - - await self._api.wait_for_device_cache(timeout=10.0) - # 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: