From 64232aa2230ad3889c84cf70e960e0485e731dca Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 18 Jul 2026 20:32:08 -0400 Subject: [PATCH 1/8] Adding some logging This lets us compare the REST API query to the GQL query. Logging can stay --- pyhilo/api.py | 1 + pyhilo/graphql.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/pyhilo/api.py b/pyhilo/api.py index f3051e0..583c4bb 100755 --- a/pyhilo/api.py +++ b/pyhilo/api.py @@ -720,6 +720,7 @@ async def get_gateway(self, location_id: int) -> dict[str, Any]: url = self._get_url("Gateways/Info", location_id=location_id) LOG.debug("Gateway URL is %s", url) req = await self.async_request("get", url) + LOG.debug("Gateway REST response: %s", json.dumps(req, indent=2)) saved_attrs = [ "zigBeePairingActivated", "zigBeeChannel", diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index 59126c2..6d3bfd4 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -590,6 +590,9 @@ async def call_get_location_query(self, location_hilo_id: str) -> None: return if "data" in response_json: + devices = response_json["data"].get("getLocation", {}).get("devices", []) + gateways = [d for d in devices if d.get("deviceType") in ("Gateway", "Hub")] + LOG.debug("Gateway devices in getLocation response: %s", json.dumps(gateways, indent=2)) self._handle_query_result(response_json["data"]) async def subscribe_to_device_updated( From 3bf722168b862ab9a15dea0af01d4505fcee76ba Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Sat, 18 Jul 2026 21:16:58 -0400 Subject: [PATCH 2/8] Initial commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On build le gateway via le retour de GQL plutôt que le REST API. Mérite sûrement une passe pour être plus joli. Manque des docstrings. WIP. --- pyhilo/devices.py | 18 +++++++++--------- pyhilo/graphql.py | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/pyhilo/devices.py b/pyhilo/devices.py index bfcfd4f..29bb846 100644 --- a/pyhilo/devices.py +++ b/pyhilo/devices.py @@ -111,15 +111,15 @@ async def update(self) -> None: self.devices.append(dev) # Append gateway from REST API (still available) - try: - gw = await self._api.get_gateway(self.location_id) - LOG.debug("Generating gateway device %s", gw) - gw_dev = self.generate_device(gw) - generated_devices.append(gw_dev) - if gw_dev not in self.devices: - self.devices.append(gw_dev) - except Exception as err: - LOG.error("Failed to get gateway: %s", err) + # try: + # gw = await self._api.get_gateway(self.location_id) + # LOG.debug("Generating gateway device %s", gw) + # gw_dev = self.generate_device(gw) + # generated_devices.append(gw_dev) + # if gw_dev not in self.devices: + # self.devices.append(gw_dev) + # except Exception as err: + # LOG.error("Failed to get gateway: %s", err) # Now add devices from external sources (e.g. unknown source tracker) for callback in self._api._get_device_callbacks: diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index 6d3bfd4..83276a8 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -793,11 +793,50 @@ async def _get_access_token(self) -> str: return await self._api.async_get_access_token() def _handle_query_result(self, result: Dict[str, Any]) -> None: - """This receives query results and maps them to the proper device.""" devices_values: List[Dict[str, Any]] = result["getLocation"]["devices"] + + for raw_device in devices_values: + if raw_device.get("deviceType") in ("Gateway", "Hub"): + if self._devices.find_device(1) is None: + gw = self._build_gateway_dict(raw_device) + LOG.debug("Creating gateway device from GraphQL: %s", gw) + gw_dev = self._devices.generate_device(gw) + if gw_dev not in self._devices.devices: + self._devices.devices.append(gw_dev) + attributes = self.mapper.map_query_values(devices_values) self._devices.parse_values_received(attributes) + def _build_gateway_dict(self, raw_device: Dict[str, Any]) -> Dict[str, Any]: + hilo_id = raw_device.get("hiloId", "") + parts = hilo_id.split(":") + mac = parts[3] if len(parts) > 3 else None + if mac is None: + LOG.warning("Unable to extract MAC from hiloId: %s", hilo_id) + + connection_status = raw_device.get("connectionStatus") + return { + "name": "Hilo Gateway", + "type": "Gateway", + "category": "Gateway", + "id": 1, + "identifier": mac, + "sdi": mac, + "provider": 1, + "model_number": "EQ000017", + "sw_version": raw_device.get("controllerSoftwareVersion"), + "supportedAttributes": "zigBeePairingActivated, zigBeeChannel, firmwareVersion, onlineStatus, lastStatusTime, disconnected", + "settableAttributes": "", + "Disconnected": {"value": connection_status != "CONNECTED"}, + "zigBeePairingActivated": {"value": bool(raw_device.get("zigBeePairingModeEnhanced"))}, + "zigBeeChannel": {"value": raw_device.get("zigBeeChannel")}, + "firmwareVersion": {"value": raw_device.get("controllerSoftwareVersion")}, + "onlineStatus": {"value": connection_status}, + "lastStatusTime": {"value": raw_device.get("lastConnectionTime")}, + "disconnected": {"value": connection_status != "CONNECTED"}, + } + + def _handle_device_subscription_result(self, result: Dict[str, Any]) -> str: device_value: Dict[str, Any] = result["onAnyDeviceUpdated"]["device"] attributes = self.mapper.map_device_subscription_values(device_value) From d246d697f5e8ba142596a8a2373d285979f5c555 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:52:45 -0400 Subject: [PATCH 3/8] Remove commented out code Retrait du code commented out + ajustement de docstrings + linting --- pyhilo/devices.py | 12 ------------ pyhilo/graphql.py | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/pyhilo/devices.py b/pyhilo/devices.py index 29bb846..9129179 100644 --- a/pyhilo/devices.py +++ b/pyhilo/devices.py @@ -110,18 +110,6 @@ async def update(self) -> None: if dev not in self.devices: self.devices.append(dev) - # Append gateway from REST API (still available) - # try: - # gw = await self._api.get_gateway(self.location_id) - # LOG.debug("Generating gateway device %s", gw) - # gw_dev = self.generate_device(gw) - # generated_devices.append(gw_dev) - # if gw_dev not in self.devices: - # self.devices.append(gw_dev) - # except Exception as err: - # LOG.error("Failed to get gateway: %s", err) - - # Now add devices from external sources (e.g. unknown source tracker) for callback in self._api._get_device_callbacks: try: cb_device = callback() diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index 83276a8..7f76319 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -590,9 +590,16 @@ async def call_get_location_query(self, location_hilo_id: str) -> None: return if "data" in response_json: - devices = response_json["data"].get("getLocation", {}).get("devices", []) - gateways = [d for d in devices if d.get("deviceType") in ("Gateway", "Hub")] - LOG.debug("Gateway devices in getLocation response: %s", json.dumps(gateways, indent=2)) + devices = ( + response_json["data"].get("getLocation", {}).get("devices", []) + ) + gateways = [ + d for d in devices if d.get("deviceType") in ("Gateway", "Hub") + ] + LOG.debug( + "Gateway devices in getLocation response: %s", + json.dumps(gateways, indent=2), + ) self._handle_query_result(response_json["data"]) async def subscribe_to_device_updated( @@ -828,7 +835,9 @@ def _build_gateway_dict(self, raw_device: Dict[str, Any]) -> Dict[str, Any]: "supportedAttributes": "zigBeePairingActivated, zigBeeChannel, firmwareVersion, onlineStatus, lastStatusTime, disconnected", "settableAttributes": "", "Disconnected": {"value": connection_status != "CONNECTED"}, - "zigBeePairingActivated": {"value": bool(raw_device.get("zigBeePairingModeEnhanced"))}, + "zigBeePairingActivated": { + "value": bool(raw_device.get("zigBeePairingModeEnhanced")) + }, "zigBeeChannel": {"value": raw_device.get("zigBeeChannel")}, "firmwareVersion": {"value": raw_device.get("controllerSoftwareVersion")}, "onlineStatus": {"value": connection_status}, @@ -836,7 +845,6 @@ def _build_gateway_dict(self, raw_device: Dict[str, Any]) -> Dict[str, Any]: "disconnected": {"value": connection_status != "CONNECTED"}, } - def _handle_device_subscription_result(self, result: Dict[str, Any]) -> str: device_value: Dict[str, Any] = result["onAnyDeviceUpdated"]["device"] attributes = self.mapper.map_device_subscription_values(device_value) From aae11729c3cede537b6c31d95acd6eb83eb57d1e Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:53:30 -0400 Subject: [PATCH 4/8] Random Typo. --- pyhilo/devices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyhilo/devices.py b/pyhilo/devices.py index 9129179..ac3f085 100644 --- a/pyhilo/devices.py +++ b/pyhilo/devices.py @@ -99,8 +99,8 @@ def generate_device(self, device: dict) -> HiloDevice: return dev async def update(self) -> None: - """Update device list from SignalR cache + gateway from REST.""" - # Get devices from SignalR cache (already populated by DeviceListInitialValuesReceived) + """Update device list from SignalR cache""" + # Get devices from SignalR cache (already populated by DeviceListInitialValuesReceived cached_devices = self._api.get_device_cache(self.location_id) generated_devices = [] for raw_device in cached_devices: From 0f77db3c2160f3747d3dfc917d03695ab9431c12 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:50:43 -0400 Subject: [PATCH 5/8] Another typo --- pyhilo/devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyhilo/devices.py b/pyhilo/devices.py index ac3f085..b43c83f 100644 --- a/pyhilo/devices.py +++ b/pyhilo/devices.py @@ -100,7 +100,7 @@ def generate_device(self, device: dict) -> HiloDevice: async def update(self) -> None: """Update device list from SignalR cache""" - # Get devices from SignalR cache (already populated by DeviceListInitialValuesReceived + # Get devices from SignalR cache (already populated by DeviceListInitialValuesReceived) cached_devices = self._api.get_device_cache(self.location_id) generated_devices = [] for raw_device in cached_devices: From 7b8a52b7b2a4141ec1f499cb816665d2390cd3f1 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:32:41 -0400 Subject: [PATCH 6/8] Remove dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avec le call retiré vers get_gateway et le build via GQL le code suivant est mort et peut être retiré --- pyhilo/api.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/pyhilo/api.py b/pyhilo/api.py index 583c4bb..c88040e 100755 --- a/pyhilo/api.py +++ b/pyhilo/api.py @@ -715,38 +715,6 @@ async def get_seasons(self, location_id: int) -> list[dict[str, Any]]: return all_seasons - async def get_gateway(self, location_id: int) -> dict[str, Any]: - """Gets info about the Hilo hub (gateway)""" - url = self._get_url("Gateways/Info", location_id=location_id) - LOG.debug("Gateway URL is %s", url) - req = await self.async_request("get", url) - LOG.debug("Gateway REST response: %s", json.dumps(req, indent=2)) - saved_attrs = [ - "zigBeePairingActivated", - "zigBeeChannel", - "firmwareVersion", - "onlineStatus", - "lastStatusTime", - "disconnected", - ] - - gw = { - "name": "Hilo Gateway", - "Disconnected": {"value": not req[0].get("onlineStatus") == "Online"}, - "type": "Gateway", - "category": "Gateway", - "supportedAttributes": ", ".join(saved_attrs), - "settableAttributes": "", - "id": 1, - "identifier": req[0].get("dsn"), - "sdi": req[0].get("sdi"), - "provider": 1, - "model_number": "EQ000017", - "sw_version": req[0].get("firmwareVersion"), - } - for attr in saved_attrs: - gw[attr] = {"value": req[0].get(attr)} - return gw async def get_weather(self, location_id: int) -> dict[str, Any]: """This will return the current weather like in the app From dedf4399ab6b7d66d492198e9ba1fc400e49a2dd Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Tue, 1 Sep 2026 09:36:20 -0400 Subject: [PATCH 7/8] Ruff Linting --- pyhilo/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyhilo/api.py b/pyhilo/api.py index c88040e..942b769 100755 --- a/pyhilo/api.py +++ b/pyhilo/api.py @@ -715,7 +715,6 @@ async def get_seasons(self, location_id: int) -> list[dict[str, Any]]: return all_seasons - async def get_weather(self, location_id: int) -> dict[str, Any]: """This will return the current weather like in the app https://api.hiloenergie.com/Automation/v1/api/Locations/XXXX/Weather From 0059e4163da577b7de825b729c3dc03547efcb6d Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:45:00 -0400 Subject: [PATCH 8/8] Docstring update --- pyhilo/graphql.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index 7f76319..1ffe3c9 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -800,6 +800,7 @@ async def _get_access_token(self) -> str: return await self._api.async_get_access_token() def _handle_query_result(self, result: Dict[str, Any]) -> None: + """Handle the result of the GraphQL query for location and devices.""" devices_values: List[Dict[str, Any]] = result["getLocation"]["devices"] for raw_device in devices_values: @@ -815,6 +816,7 @@ def _handle_query_result(self, result: Dict[str, Any]) -> None: self._devices.parse_values_received(attributes) def _build_gateway_dict(self, raw_device: Dict[str, Any]) -> Dict[str, Any]: + """Build a dictionary representing the gateway device from raw GraphQL data.""" hilo_id = raw_device.get("hiloId", "") parts = hilo_id.split(":") mac = parts[3] if len(parts) > 3 else None @@ -846,6 +848,7 @@ def _build_gateway_dict(self, raw_device: Dict[str, Any]) -> Dict[str, Any]: } def _handle_device_subscription_result(self, result: Dict[str, Any]) -> str: + """Handle the result of the GraphQL subscription for device updates.""" device_value: Dict[str, Any] = result["onAnyDeviceUpdated"]["device"] attributes = self.mapper.map_device_subscription_values(device_value) updated_device = self._devices.parse_values_received(attributes) @@ -854,6 +857,7 @@ def _handle_device_subscription_result(self, result: Dict[str, Any]) -> str: return str(device_value.get("hiloId")) def _handle_location_subscription_result(self, result: Dict[str, Any]) -> str: + """Handle the result of the GraphQL subscription for location updates.""" location_value: Dict[str, Any] = result["onAnyLocationUpdated"]["location"] attributes = self.mapper.map_location_subscription_values(location_value) updated_device = self._devices.parse_values_received(attributes)