From 2fcddde3aeaa6b2471f0944d391a61c61773d9be Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Sun, 16 Mar 2025 18:26:02 -0400 Subject: [PATCH 01/12] refactor: remove unused GraphQL code and clean up device mapping logic --- pyhilo/api.py | 19 -- pyhilo/device/graphql_value_mapper.py | 54 ++--- pyhilo/devices.py | 8 +- pyhilo/graphql.py | 300 +++++++++++++++++++++++++- 4 files changed, 324 insertions(+), 57 deletions(-) diff --git a/pyhilo/api.py b/pyhilo/api.py index dac6b3c..444fa74 100755 --- a/pyhilo/api.py +++ b/pyhilo/api.py @@ -12,8 +12,6 @@ from aiohttp import ClientSession from aiohttp.client_exceptions import ClientResponseError import backoff -from gql import Client, gql -from gql.transport.aiohttp import AIOHTTPTransport from pyhilo.const import ( ANDROID_CLIENT_ENDPOINT, @@ -45,7 +43,6 @@ ) from pyhilo.device import DeviceAttribute, HiloDevice, get_device_attributes from pyhilo.exceptions import InvalidCredentialsError, RequestError -from pyhilo.graphql import GraphQlHelper from pyhilo.util.state import ( StateDict, WebsocketDict, @@ -513,22 +510,6 @@ async def get_devices(self, location_id: int) -> list[dict[str, Any]]: devices.append(callback()) return devices - async def call_get_location_query(self, location_hilo_id: string) -> Dict[str, Any]: - access_token = await self.async_get_access_token() - transport = AIOHTTPTransport( - url="https://platform.hiloenergie.com/api/digital-twin/v3/graphql", - headers={"Authorization": f"Bearer {access_token}"}, - ) - client = Client(transport=transport, fetch_schema_from_transport=True) - query = gql(GraphQlHelper.query_get_location()) - - async with client as session: - result = await session.execute( - query, variable_values={"locationHiloId": location_hilo_id} - ) - LOG.info(result) - return result - async def _set_device_attribute( self, device: HiloDevice, diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index 6a52e79..48b2e31 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -1,6 +1,4 @@ -from datetime import datetime, timezone -from typing import Any, Dict, Generator, Union - +from typing import Any, Dict from pyhilo.device import DeviceReading @@ -9,45 +7,50 @@ class GraphqlValueMapper: A class to map GraphQL values to DeviceReading instances. """ - def __init__(self, api: Any): - self._api = api - - def map_values(self, values: Dict[str, Any]) -> list[DeviceReading]: - devices_values: list[any] = values["getLocation"]["devices"] - readings: list[DeviceReading] = [] - for device in devices_values: + def map_query_values(self, values: Dict[str, Any]) -> list[Dict[str, Any]]: + readings: list[Dict[str, Any]] = [] + for device in values: if device.get("deviceType") is not None: reading = self._map_devices_values(device) readings.extend(reading) return readings - def _map_devices_values(self, device: Dict[str, Any]) -> list[DeviceReading]: + def map_subscription_values( + self, device: list[Dict[str, Any]] + ) -> list[Dict[str, Any]]: + readings: list[Dict[str, Any]] = [] + if device.get("deviceType") is not None: + reading = self._map_devices_values(device) + readings.extend(reading) + return readings + + def _map_devices_values(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: attributes: list[Dict[str, Any]] = self._map_basic_device(device) - match device["deviceType"]: - case "Tstat": + match device["deviceType"].lower(): + case "tstat": attributes.extend(self._build_thermostat(device)) - case "CCE": # Water Heater + case "cce": # Water Heater attributes.extend(self._build_water_heater(device)) - case "CCR": # ChargeController + case "ccr": # ChargeController attributes.extend(self._build_charge_controller(device)) - case "HeatingFloor": + case "heatingfloor": attributes.extend(self._build_floor_thermostat(device)) - # case "LowVoltageTstat": - case "ChargingPoint": + # case "lowvoltagetstat": + case "chargingpoint": attributes.extend(self._build_charging_point(device)) - case "Meter": + case "meter": attributes.extend(self._build_smart_meter(device)) - case "Hub": # Gateway + case "hub": # Gateway attributes.extend(self._build_gateway(device)) - case "ColorBulb": + case "colorbulb": attributes.extend(self._build_light(device)) - case "Dimmer": + case "dimmer": attributes.extend(self._build_dimmer(device)) - case "Switch": + case "switch": attributes.extend(self._build_switch(device)) case _: pass - return self._map_to_device_reading(attributes) + return attributes def _map_to_device_reading( self, attributes: list[Dict[str, Any]] @@ -398,7 +401,8 @@ def build_attribute( ) -> Dict[str, Any]: return { "hilo_id": hilo_id, - "device_attribute": self._api.dev_atts(device_attribute, "null"), + "attribute": device_attribute, + "valueType": "null", "value": value, "timeStampUTC": datetime.now(timezone.utc).isoformat(), } diff --git a/pyhilo/devices.py b/pyhilo/devices.py index a4e8523..2b826f7 100644 --- a/pyhilo/devices.py +++ b/pyhilo/devices.py @@ -4,10 +4,9 @@ from pyhilo.const import HILO_DEVICE_TYPES, LOG from pyhilo.device import DeviceReading, HiloDevice from pyhilo.device.climate import Climate # noqa -from pyhilo.device.graphql_value_mapper import GraphqlValueMapper from pyhilo.device.light import Light # noqa from pyhilo.device.sensor import Sensor # noqa -from pyhilo.device.switch import Switch # noqa +from pyhilo.device.switch import Switch class Devices: @@ -119,8 +118,3 @@ async def async_init(self) -> None: self.location_id = location_ids[0] self.location_hilo_id = location_ids[1] await self.update() - # TODO AA - trouver ou placer ça pour que ça fasse du sens, pas sûre que c'est ici - values = await self._api.call_get_location_query(self.location_hilo_id) - mapper = GraphqlValueMapper(self._api) - readings = mapper.map_values(values) - self._map_readings_to_devices(readings) diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index 45b2645..b403271 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -1,6 +1,34 @@ +import asyncio +from typing import Any, Dict +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport +from gql.transport.websockets import WebsocketsTransport +from pyhilo.device.graphql_value_mapper import GraphqlValueMapper +from pyhilo import API +from pyhilo.devices import Devices +from typing import List, Optional + + class GraphQlHelper: - def query_get_location() -> str: - return """query getLocation($locationHiloId: String!) { + """The GraphQl Helper class.""" + + def __init__(self, api: API, devices: Devices): + self._api = api + self._devices = devices + self.access_token = "" + self.mapper: GraphqlValueMapper = GraphqlValueMapper() + + self.subscriptions: List[Optional[asyncio.Task]] = [None] + + async def async_init(self) -> None: + """Initialize the Hilo "GraphQlHelper" class.""" + self.access_token = await self._api.async_get_access_token() + self.subscriptions[0] = asyncio.create_task( + self._subscribe_to_device_updated(self._devices.location_hilo_id) + ) + await self.call_get_location_query(self._devices.location_hilo_id) + + QUERY_GET_LOCATION: str = """query getLocation($locationHiloId: String!) { getLocation(id:$locationHiloId) { hiloId lastUpdate @@ -166,7 +194,7 @@ def query_get_location() -> str: connectionStatus gDState version - probeTemp { + probeTemp { value kind } @@ -200,11 +228,11 @@ def query_get_location() -> str: gDState version zigbeeVersion - ambientTemperature { + ambientTemperature { value kind } - ambientTempSetpoint { + ambientTempSetpoint { value kind } @@ -215,4 +243,264 @@ def query_get_location() -> str: } } } - }""" + }""" + + SUBSCRIPTION_DEVICE_UPDATED: str = """subscription onAnyDeviceUpdated($locationHiloId: String!) { + onAnyDeviceUpdated(locationHiloId: $locationHiloId) { + deviceType + locationHiloId + transmissionTime + operationId + status + device { + ... on Gateway { + connectionStatus + controllerSoftwareVersion + lastConnectionTime + willBeConnectedToSmartMeter + zigBeeChannel + zigBeePairingModeEnhanced + smartMeterZigBeeChannel + smartMeterPairingStatus + } + ... on BasicSmartMeter { + deviceType + hiloId + physicalAddress + connectionStatus + zigBeeChannel + power { + value + kind + } + } + ... on LowVoltageThermostat { + deviceType + hiloId + physicalAddress + coolTempSetpoint { + value + } + fanMode + fanSpeed + mode + currentState + power { + value + kind + } + ambientHumidity + gDState + ambientTemperature { + value + kind + } + ambientTempSetpoint { + value + kind + } + version + zigbeeVersion + connectionStatus + maxAmbientCoolSetPoint { + value + kind + } + minAmbientCoolSetPoint { + value + kind + } + maxAmbientTempSetpoint { + value + kind + } + minAmbientTempSetpoint { + value + kind + } + allowedModes + fanAllowedModes + } + ... on BasicSwitch { + deviceType + hiloId + physicalAddress + connectionStatus + state + power { + value + kind + } + } + ... on BasicLight { + deviceType + hiloId + physicalAddress + connectionStatus + state + hue + level + saturation + colorTemperature + lightType + } + ... on BasicEVCharger { + deviceType + hiloId + physicalAddress + connectionStatus + status + power { + value + kind + } + } + ... on BasicChargeController { + deviceType + hiloId + physicalAddress + connectionStatus + gDState + version + zigbeeVersion + state + power { + value + kind + } + } + ... on HeatingFloorThermostat { + deviceType + hiloId + physicalAddress + connectionStatus + ambientHumidity + gDState + version + zigbeeVersion + thermostatType + physicalAddress + floorMode + power { + value + kind + } + ambientTemperature { + value + kind + } + ambientTempSetpoint { + value + kind + } + maxAmbientTempSetpoint { + value + kind + } + minAmbientTempSetpoint { + value + kind + } + floorLimit { + value + } + } + ... on WaterHeater { + deviceType + hiloId + physicalAddress + connectionStatus + gDState + version + probeTemp { + value + kind + } + zigbeeVersion + state + ccrType + alerts + power { + value + kind + } + } + ... on BasicDimmer { + deviceType + hiloId + physicalAddress + connectionStatus + state + level + power { + value + kind + } + } + ... on BasicThermostat { + deviceType + hiloId + physicalAddress + connectionStatus + ambientHumidity + gDState + version + zigbeeVersion + ambientTemperature { + value + kind + } + ambientTempSetpoint { + value + kind + } + power { + value + kind + } + } + } + } +}""" + + async def call_get_location_query(self, location_hilo_id: str) -> None: + transport = AIOHTTPTransport( + url="https://platform.hiloenergie.com/api/digital-twin/v3/graphql", + headers={"Authorization": f"Bearer {self.access_token}"}, + ) + client = Client(transport=transport, fetch_schema_from_transport=True) + query = gql(self.QUERY_GET_LOCATION) + + async with client as session: + result = await session.execute( + query, variable_values={"locationHiloId": location_hilo_id} + ) + self._handle_query_result(result) + + async def _subscribe_to_device_updated(self, location_hilo_id: str) -> None: + transport = WebsocketsTransport( + url=f"wss://platform.hiloenergie.com/api/digital-twin/v3/graphql?access_token={self.access_token}" + ) + client = Client(transport=transport, fetch_schema_from_transport=True) + query = gql(self.SUBSCRIPTION_DEVICE_UPDATED) + print("Connected to device updated subscription") + try: + async with client as session: + async for result in session.subscribe( + query, variable_values={"locationHiloId": location_hilo_id} + ): + self._handle_subscription_result(result) + except asyncio.CancelledError: + print("Subscription cancelled.") + asyncio.sleep(1) + await self._subscribe_to_device_updated(location_hilo_id) + + def _handle_query_result(self, result: Dict[str, Any]) -> None: + devices_values: list[any] = result["getLocation"]["devices"] + attributes = self.mapper.map_query_values(devices_values) + self._devices.parse_values_received(attributes) + + def _handle_subscription_result(self, result: Dict[str, Any]) -> None: + devices_values: list[any] = result["onAnyDeviceUpdated"]["device"] + attributes = self.mapper.map_subscription_values(devices_values) + self._devices.parse_values_received(attributes) From 3c59967068084a4e984a064b758f7f6cb960e6fe Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Mon, 24 Mar 2025 11:23:16 -0400 Subject: [PATCH 02/12] add missing import --- pyhilo/device/graphql_value_mapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index 48b2e31..41d6591 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -1,5 +1,6 @@ from typing import Any, Dict from pyhilo.device import DeviceReading +from datetime import datetime, timezone class GraphqlValueMapper: From 78b9f10ca9148bdfe5d6174136c6af5af7d8c0ef Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Mon, 24 Mar 2025 19:46:36 -0400 Subject: [PATCH 03/12] Some linting --- pyhilo/device/graphql_value_mapper.py | 3 ++- pyhilo/graphql.py | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index 41d6591..cfd4b99 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -1,6 +1,7 @@ +from datetime import datetime, timezone from typing import Any, Dict + from pyhilo.device import DeviceReading -from datetime import datetime, timezone class GraphqlValueMapper: diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index b403271..4528780 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -1,12 +1,13 @@ import asyncio -from typing import Any, Dict -from gql import gql, Client +from typing import Any, Dict, List, Optional + +from gql import Client, gql from gql.transport.aiohttp import AIOHTTPTransport from gql.transport.websockets import WebsocketsTransport -from pyhilo.device.graphql_value_mapper import GraphqlValueMapper + from pyhilo import API +from pyhilo.device.graphql_value_mapper import GraphqlValueMapper from pyhilo.devices import Devices -from typing import List, Optional class GraphQlHelper: From bef8e54ed455a9fb03f1bb0f65ee6c3f46810420 Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Mon, 24 Mar 2025 19:48:22 -0400 Subject: [PATCH 04/12] change to use callback and add location subscription --- pyhilo/device/__init__.py | 8 +-- pyhilo/device/graphql_value_mapper.py | 24 +++++++-- pyhilo/graphql.py | 76 +++++++++++++++++++++++---- 3 files changed, 88 insertions(+), 20 deletions(-) diff --git a/pyhilo/device/__init__.py b/pyhilo/device/__init__.py index c9435d1..c92095f 100644 --- a/pyhilo/device/__init__.py +++ b/pyhilo/device/__init__.py @@ -87,9 +87,7 @@ def update(self, **kwargs: Dict[str, Union[str, int, Dict]]) -> None: new_val.append(DeviceAttribute("Disconnected", "null")) elif att == "provider": att = "manufacturer" - new_val = HILO_PROVIDERS.get( - int(val), f"Unknown ({val})" - ) # type: ignore + new_val = HILO_PROVIDERS.get(int(val), f"Unknown ({val})") # type: ignore else: if att == "serial": att = "identifier" @@ -234,9 +232,7 @@ def __init__(self, **kwargs: Dict[str, Any]): # attr='intensity', # value_type='%') # } - kwargs["timeStamp"] = from_utc_timestamp( - kwargs.pop("timeStampUTC", "") - ) # type: ignore + kwargs["timeStamp"] = from_utc_timestamp(kwargs.pop("timeStampUTC", "")) # type: ignore self.id = 0 self.value: Union[int, bool, str] = 0 self.device_id = 0 diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index 41d6591..800b812 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -1,5 +1,5 @@ -from typing import Any, Dict -from pyhilo.device import DeviceReading +from typing import Any, Dict, List +from pyhilo.device import DeviceReading, HiloDevice from datetime import datetime, timezone @@ -16,7 +16,7 @@ def map_query_values(self, values: Dict[str, Any]) -> list[Dict[str, Any]]: readings.extend(reading) return readings - def map_subscription_values( + def map_device_subscription_values( self, device: list[Dict[str, Any]] ) -> list[Dict[str, Any]]: readings: list[Dict[str, Any]] = [] @@ -24,8 +24,18 @@ def map_subscription_values( reading = self._map_devices_values(device) readings.extend(reading) return readings - - def _map_devices_values(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: + + def map_location_subscription_values( + self, values: Dict[str, Any] + ) -> list[Dict[str, Any]]: + readings: list[Dict[str, Any]] = [] + for device in values: + if device.get("deviceType") is not None: + reading = self._map_devices_values(device) + readings.extend(reading) + return readings + + def _map_devices_values(self, device: Dict[str, Any], is_unpaired: bool) -> list[Dict[str, Any]]: attributes: list[Dict[str, Any]] = self._map_basic_device(device) match device["deviceType"].lower(): case "tstat": @@ -456,3 +466,7 @@ def _map_to_ccr_mode(self, ccr_mode: int) -> str: return "Manual" case _: return "" + + def _is_device_disconnected(self, device_hilo_id: str, devices: List[HiloDevice]) -> bool: + return next( + (False for d in devices if d.hilo_id == device_hilo_id), True) \ No newline at end of file diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index b403271..50c7a1a 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -23,9 +23,6 @@ def __init__(self, api: API, devices: Devices): async def async_init(self) -> None: """Initialize the Hilo "GraphQlHelper" class.""" self.access_token = await self._api.async_get_access_token() - self.subscriptions[0] = asyncio.create_task( - self._subscribe_to_device_updated(self._devices.location_hilo_id) - ) await self.call_get_location_query(self._devices.location_hilo_id) QUERY_GET_LOCATION: str = """query getLocation($locationHiloId: String!) { @@ -463,6 +460,32 @@ async def async_init(self) -> None: } }""" + SUBSCRIPTION_LOCATION_UPDATED: str = """subscription onAnyLocationUpdated($locationHiloId: String!){ + onAnyLocationUpdated(locationHiloId: $locationHiloId) { + locationHiloId + deviceType + transmissionTime + operationId + location { + ...on Container { + hiloId + devices { + deviceType + hiloId + physicalAddress + connectionStatus + ... on BasicChargeController { + connectionStatus + } + ... on LowVoltageThermostat { + connectionStatus + } + } + } + } + } +}""" + async def call_get_location_query(self, location_hilo_id: str) -> None: transport = AIOHTTPTransport( url="https://platform.hiloenergie.com/api/digital-twin/v3/graphql", @@ -477,30 +500,65 @@ async def call_get_location_query(self, location_hilo_id: str) -> None: ) self._handle_query_result(result) - async def _subscribe_to_device_updated(self, location_hilo_id: str) -> None: + async def subscribe_to_device_updated( + self, location_hilo_id: str, callback: callable = None + ) -> None: transport = WebsocketsTransport( url=f"wss://platform.hiloenergie.com/api/digital-twin/v3/graphql?access_token={self.access_token}" ) client = Client(transport=transport, fetch_schema_from_transport=True) query = gql(self.SUBSCRIPTION_DEVICE_UPDATED) - print("Connected to device updated subscription") try: async with client as session: async for result in session.subscribe( query, variable_values={"locationHiloId": location_hilo_id} ): - self._handle_subscription_result(result) + print(f"Received subscription result {result}") + device_hilo_id = self._handle_subscription_result(result) + callback(device_hilo_id) + except asyncio.CancelledError: + print("Subscription cancelled.") + asyncio.sleep(1) + await self.subscribe_to_device_updated(location_hilo_id) + + async def subscribe_to_location_updated( + self, location_hilo_id: str, callback: callable = None + ) -> None: + transport = WebsocketsTransport( + url=f"wss://platform.hiloenergie.com/api/digital-twin/v3/graphql?access_token={self.access_token}" + ) + client = Client(transport=transport, fetch_schema_from_transport=True) + query = gql(self.SUBSCRIPTION_LOCATION_UPDATED) + try: + async with client as session: + async for result in session.subscribe( + query, variable_values={"locationHiloId": location_hilo_id} + ): + print(f"Received subscription result {result}") + device_hilo_id = self._handle_location_subscription_result(result) + callback(device_hilo_id) except asyncio.CancelledError: print("Subscription cancelled.") asyncio.sleep(1) - await self._subscribe_to_device_updated(location_hilo_id) + await self.subscribe_to_location_updated(location_hilo_id) def _handle_query_result(self, result: Dict[str, Any]) -> None: devices_values: list[any] = result["getLocation"]["devices"] attributes = self.mapper.map_query_values(devices_values) self._devices.parse_values_received(attributes) - def _handle_subscription_result(self, result: Dict[str, Any]) -> None: + def _handle_subscription_result(self, result: Dict[str, Any]) -> str: devices_values: list[any] = result["onAnyDeviceUpdated"]["device"] attributes = self.mapper.map_subscription_values(devices_values) - self._devices.parse_values_received(attributes) + updated_device = self._devices.parse_values_received(attributes) + # callback to update the device in the UI + print(f"Device updated: {updated_device}") + return devices_values.get("hiloId") + + def _handle_location_subscription_result(self, result: Dict[str, Any]) -> str: + devices_values: list[any] = result["onAnyLocationUpdated"]["location"] + attributes = self.mapper.map_subscription_values(devices_values) + updated_device = self._devices.parse_values_received(attributes) + # callback to update the device in the UI + print(f"Device updated: {updated_device}") + return devices_values.get("hiloId") From eccb32e138e5e688b50d994c0a646865b352b50c Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Mon, 24 Mar 2025 19:59:02 -0400 Subject: [PATCH 05/12] Remove some unused imports --- pyhilo/api.py | 2 +- pyhilo/devices.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyhilo/api.py b/pyhilo/api.py index 444fa74..9c03d4f 100755 --- a/pyhilo/api.py +++ b/pyhilo/api.py @@ -6,7 +6,7 @@ import random import string import sys -from typing import Any, Callable, Dict, Union, cast +from typing import Any, Callable, Union, cast from urllib import parse from aiohttp import ClientSession diff --git a/pyhilo/devices.py b/pyhilo/devices.py index 2b826f7..1187098 100644 --- a/pyhilo/devices.py +++ b/pyhilo/devices.py @@ -6,7 +6,7 @@ from pyhilo.device.climate import Climate # noqa from pyhilo.device.light import Light # noqa from pyhilo.device.sensor import Sensor # noqa -from pyhilo.device.switch import Switch +from pyhilo.device.switch import Switch # noqa class Devices: From 5a1fd93534d1475b198bb1b912075d0bc4ebd132 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Mon, 24 Mar 2025 20:05:05 -0400 Subject: [PATCH 06/12] Linting, adding missing import --- pyhilo/device/graphql_value_mapper.py | 21 +++++++++++++-------- pyhilo/graphql.py | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index dffe80d..843f6ae 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -1,6 +1,8 @@ -from pyhilo.device import DeviceReading, HiloDevice from datetime import datetime, timezone -from typing import Any, Dict +from typing import Any, Dict, List + +from pyhilo.device import DeviceReading, HiloDevice + class GraphqlValueMapper: """ @@ -23,7 +25,7 @@ def map_device_subscription_values( reading = self._map_devices_values(device) readings.extend(reading) return readings - + def map_location_subscription_values( self, values: Dict[str, Any] ) -> list[Dict[str, Any]]: @@ -33,8 +35,10 @@ def map_location_subscription_values( reading = self._map_devices_values(device) readings.extend(reading) return readings - - def _map_devices_values(self, device: Dict[str, Any], is_unpaired: bool) -> list[Dict[str, Any]]: + + def _map_devices_values( + self, device: Dict[str, Any], is_unpaired: bool + ) -> list[Dict[str, Any]]: attributes: list[Dict[str, Any]] = self._map_basic_device(device) match device["deviceType"].lower(): case "tstat": @@ -466,6 +470,7 @@ def _map_to_ccr_mode(self, ccr_mode: int) -> str: case _: return "" - def _is_device_disconnected(self, device_hilo_id: str, devices: List[HiloDevice]) -> bool: - return next( - (False for d in devices if d.hilo_id == device_hilo_id), True) \ No newline at end of file + def _is_device_disconnected( + self, device_hilo_id: str, devices: List[HiloDevice] + ) -> bool: + return next((False for d in devices if d.hilo_id == device_hilo_id), True) diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index c9d5ddc..0427cee 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -555,7 +555,7 @@ def _handle_subscription_result(self, result: Dict[str, Any]) -> str: # callback to update the device in the UI print(f"Device updated: {updated_device}") return devices_values.get("hiloId") - + def _handle_location_subscription_result(self, result: Dict[str, Any]) -> str: devices_values: list[any] = result["onAnyLocationUpdated"]["location"] attributes = self.mapper.map_subscription_values(devices_values) From 5ed178ee60266dcae87e7f9fd0f2cb40a3cfddb2 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Mon, 24 Mar 2025 20:14:45 -0400 Subject: [PATCH 07/12] Linting whitespace --- pyhilo/graphql.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index 0427cee..d8ebacb 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -304,23 +304,23 @@ async def async_init(self) -> None: value kind } - minAmbientCoolSetPoint { - value + minAmbientCoolSetPoint { + value kind - } + } maxAmbientTempSetpoint { value kind } - minAmbientTempSetpoint { + minAmbientTempSetpoint { value kind - } - allowedModes - fanAllowedModes + } + allowedModes + fanAllowedModes } ... on BasicSwitch { - deviceType + deviceType hiloId physicalAddress connectionStatus @@ -417,7 +417,7 @@ async def async_init(self) -> None: zigbeeVersion state ccrType - alerts + alerts power { value kind @@ -475,14 +475,14 @@ async def async_init(self) -> None: hiloId physicalAddress connectionStatus - ... on BasicChargeController { - connectionStatus - } - ... on LowVoltageThermostat { - connectionStatus - } + ... on BasicChargeController { + connectionStatus + } + ... on LowVoltageThermostat { + connectionStatus + } } - } + } } } }""" From 34937ce05efa6f6f85c28435d5abe507c0ad0cef Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Mon, 24 Mar 2025 21:22:42 -0400 Subject: [PATCH 08/12] fix erreur --- pyhilo/device/graphql_value_mapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index dffe80d..89723f7 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -1,6 +1,6 @@ from pyhilo.device import DeviceReading, HiloDevice from datetime import datetime, timezone -from typing import Any, Dict +from typing import Any, Dict, List class GraphqlValueMapper: """ @@ -34,7 +34,7 @@ def map_location_subscription_values( readings.extend(reading) return readings - def _map_devices_values(self, device: Dict[str, Any], is_unpaired: bool) -> list[Dict[str, Any]]: + def _map_devices_values(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: attributes: list[Dict[str, Any]] = self._map_basic_device(device) match device["deviceType"].lower(): case "tstat": From f92a4d7f97a1abb91a3570e5b3e51c9372ec8402 Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Wed, 26 Mar 2025 16:59:31 -0400 Subject: [PATCH 09/12] add lowvoltage mapping --- pyhilo/device/graphql_value_mapper.py | 81 ++++++++++++++++++++++++--- pyhilo/graphql.py | 8 +-- 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index 843f6ae..038ca39 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -37,7 +37,7 @@ def map_location_subscription_values( return readings def _map_devices_values( - self, device: Dict[str, Any], is_unpaired: bool + self, device: Dict[str, Any] ) -> list[Dict[str, Any]]: attributes: list[Dict[str, Any]] = self._map_basic_device(device) match device["deviceType"].lower(): @@ -49,7 +49,8 @@ def _map_devices_values( attributes.extend(self._build_charge_controller(device)) case "heatingfloor": attributes.extend(self._build_floor_thermostat(device)) - # case "lowvoltagetstat": + case "lowvoltagetstat": + attributes.extend(self._build_lowvoltage_thermostat(device)) case "chargingpoint": attributes.extend(self._build_charging_point(device)) case "meter": @@ -245,6 +246,75 @@ def _build_floor_thermostat(self, device: Dict[str, Any]) -> list[Dict[str, Any] ) ) return attributes + + def _build_lowvoltage_thermostat(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: + attributes = self._build_thermostat(device) + if device.get("coolTempSetpoint") is not None: + attributes.append( + self.build_attribute( + device["hiloId"], "CoolTemperatureSet", device["coolTempSetpoint"]["value"] + ) + ) + if device.get("fanSpeed") is not None: + attributes.append( + self.build_attribute( + device["hiloId"], "FanSpeed", device["fanSpeed"] + ) + ) + if device.get("minAmbientCoolSetPoint") is not None: + attributes.append( + self.build_attribute( + device["hiloId"], "MinCoolSetpoint", device["minAmbientCoolSetPoint"]["value"] + ) + ) + if device.get("maxAmbientCoolSetPoint") is not None: + attributes.append( + self.build_attribute( + device["hiloId"], "MaxCoolSetpoint", device["maxAmbientCoolSetPoint"]["value"] + ) + ) + if device.get("minAmbientTempSetpoint") is not None: + attributes.append( + self.build_attribute( + device["hiloId"], "MinTempSetpoint", device["minAmbientTempSetpoint"]["value"] + ) + ) + if device.get("maxAmbientTempSetpoint") is not None: + attributes.append( + self.build_attribute( + device["hiloId"], "MaxTempSetpoint", device["maxAmbientTempSetpoint"]["value"] + ) + ) + attributes.extend( + [ + self.build_attribute( + device["allowedModes"], + "Thermostat24VAllowedMode", + self._map_to_floor_mode(device["allowedModes"]), + ), + self.build_attribute( + device["fanAllowedModes"], + "Thermostat24VAllowedFanMode", + self._map_to_thermostat_mode(device["fanAllowedModes"]), + ), + self.build_attribute( + device["fanMode"], + "FanMode", + self._map_to_thermostat_mode(device["fanMode"]), + ), + self.build_attribute( + device["mode"], + "Thermostat24VMode", + self._map_to_thermostat_mode(device["mode"]), + ), + self.build_attribute( + device["currentState"], + "CurrentState", + self._map_to_thermostat_mode(device["currentState"]), + ), + ] + ) + return attributes def _build_water_heater(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: attributes = self._build_charge_controller(device) @@ -468,9 +538,4 @@ def _map_to_ccr_mode(self, ccr_mode: int) -> str: case 3: return "Manual" case _: - return "" - - def _is_device_disconnected( - self, device_hilo_id: str, devices: List[HiloDevice] - ) -> bool: - return next((False for d in devices if d.hilo_id == device_hilo_id), True) + return "" \ No newline at end of file diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index d8ebacb..d7912f2 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -515,7 +515,7 @@ async def subscribe_to_device_updated( query, variable_values={"locationHiloId": location_hilo_id} ): print(f"Received subscription result {result}") - device_hilo_id = self._handle_subscription_result(result) + device_hilo_id = self._handle_device_subscription_result(result) callback(device_hilo_id) except asyncio.CancelledError: print("Subscription cancelled.") @@ -548,9 +548,9 @@ def _handle_query_result(self, result: Dict[str, Any]) -> None: attributes = self.mapper.map_query_values(devices_values) self._devices.parse_values_received(attributes) - def _handle_subscription_result(self, result: Dict[str, Any]) -> str: + def _handle_device_subscription_result(self, result: Dict[str, Any]) -> str: devices_values: list[any] = result["onAnyDeviceUpdated"]["device"] - attributes = self.mapper.map_subscription_values(devices_values) + attributes = self.mapper.map_device_subscription_values(devices_values) updated_device = self._devices.parse_values_received(attributes) # callback to update the device in the UI print(f"Device updated: {updated_device}") @@ -558,7 +558,7 @@ def _handle_subscription_result(self, result: Dict[str, Any]) -> str: def _handle_location_subscription_result(self, result: Dict[str, Any]) -> str: devices_values: list[any] = result["onAnyLocationUpdated"]["location"] - attributes = self.mapper.map_subscription_values(devices_values) + attributes = self.mapper.map_location_subscription_values(devices_values) updated_device = self._devices.parse_values_received(attributes) # callback to update the device in the UI print(f"Device updated: {updated_device}") From 098041c3ddb6060a4b21bff7e5b86f16477879f9 Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Thu, 27 Mar 2025 12:05:32 -0400 Subject: [PATCH 10/12] fix mapping --- pyhilo/device/graphql_value_mapper.py | 65 ++++++++++++++------------- pyhilo/graphql.py | 6 ++- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index 038ca39..4e60e7a 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -36,14 +36,12 @@ def map_location_subscription_values( readings.extend(reading) return readings - def _map_devices_values( - self, device: Dict[str, Any] - ) -> list[Dict[str, Any]]: + def _map_devices_values(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: attributes: list[Dict[str, Any]] = self._map_basic_device(device) match device["deviceType"].lower(): case "tstat": attributes.extend(self._build_thermostat(device)) - case "cce": # Water Heater + case "cee": # Water Heater attributes.extend(self._build_water_heater(device)) case "ccr": # ChargeController attributes.extend(self._build_charge_controller(device)) @@ -246,43 +244,53 @@ def _build_floor_thermostat(self, device: Dict[str, Any]) -> list[Dict[str, Any] ) ) return attributes - - def _build_lowvoltage_thermostat(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: + + def _build_lowvoltage_thermostat( + self, device: Dict[str, Any] + ) -> list[Dict[str, Any]]: attributes = self._build_thermostat(device) if device.get("coolTempSetpoint") is not None: attributes.append( self.build_attribute( - device["hiloId"], "CoolTemperatureSet", device["coolTempSetpoint"]["value"] + device["hiloId"], + "CoolTemperatureSet", + device["coolTempSetpoint"]["value"], ) ) if device.get("fanSpeed") is not None: attributes.append( - self.build_attribute( - device["hiloId"], "FanSpeed", device["fanSpeed"] - ) + self.build_attribute(device["hiloId"], "FanSpeed", device["fanSpeed"]) ) if device.get("minAmbientCoolSetPoint") is not None: attributes.append( self.build_attribute( - device["hiloId"], "MinCoolSetpoint", device["minAmbientCoolSetPoint"]["value"] + device["hiloId"], + "MinCoolSetpoint", + device["minAmbientCoolSetPoint"]["value"], ) ) if device.get("maxAmbientCoolSetPoint") is not None: attributes.append( self.build_attribute( - device["hiloId"], "MaxCoolSetpoint", device["maxAmbientCoolSetPoint"]["value"] + device["hiloId"], + "MaxCoolSetpoint", + device["maxAmbientCoolSetPoint"]["value"], ) ) if device.get("minAmbientTempSetpoint") is not None: attributes.append( self.build_attribute( - device["hiloId"], "MinTempSetpoint", device["minAmbientTempSetpoint"]["value"] + device["hiloId"], + "MinTempSetpoint", + device["minAmbientTempSetpoint"]["value"], ) ) if device.get("maxAmbientTempSetpoint") is not None: attributes.append( self.build_attribute( - device["hiloId"], "MaxTempSetpoint", device["maxAmbientTempSetpoint"]["value"] + device["hiloId"], + "MaxTempSetpoint", + device["maxAmbientTempSetpoint"]["value"], ) ) attributes.extend( @@ -297,17 +305,17 @@ def _build_lowvoltage_thermostat(self, device: Dict[str, Any]) -> list[Dict[str, "Thermostat24VAllowedFanMode", self._map_to_thermostat_mode(device["fanAllowedModes"]), ), - self.build_attribute( + self.build_attribute( device["fanMode"], "FanMode", self._map_to_thermostat_mode(device["fanMode"]), ), - self.build_attribute( + self.build_attribute( device["mode"], "Thermostat24VMode", self._map_to_thermostat_mode(device["mode"]), ), - self.build_attribute( + self.build_attribute( device["currentState"], "CurrentState", self._map_to_thermostat_mode(device["currentState"]), @@ -358,11 +366,10 @@ def _build_charge_controller(self, device: Dict[str, Any]) -> list[Dict[str, Any device["hiloId"], "CcrAllowedModes", device["ccrAllowedModes"] ) ) - attributes.append( - self.build_attribute( - device["hiloId"], "CcrMode", self._map_to_ccr_mode(device["ccrMode"]) + if device.get("ccrMode") is not None: + attributes.append( + self.build_attribute(device["hiloId"], "CcrMode", device["ccrMode"]) ) - ) return attributes def _build_charging_point(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: @@ -410,16 +417,16 @@ def _build_light(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: self.build_attribute( device["hiloId"], "ColorTemperature", - device["colorTemperature"]["value"], + device["colorTemperature"], ) ) if device.get("level") is not None: attributes.append( self.build_attribute( - device["hiloId"], "Intensity", device["level"]["value"] / 100 + device["hiloId"], "Intensity", device["level"] / 100 ) ) - if device.get("lightType") == 1: # LightType.Color + if device.get("lightType").lower() == "color": attributes.append( self.build_attribute(device["hiloId"], "Hue", device.get("hue") or 0) ) @@ -429,7 +436,7 @@ def _build_light(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: ) ) attributes.append( - self.build_attribute(device["hiloId"], "OnOff", device["atate"]) + self.build_attribute(device["hiloId"], "OnOff", device["state"]) ) return attributes @@ -449,10 +456,8 @@ def _map_gd_state(self, device: Dict[str, Any]) -> Dict[str, Any]: ) def _map_drms_state(self, device: Dict[str, Any]) -> Dict[str, Any]: - return ( - self.build_attribute( - device["hiloId"], "DrmsState", device["gDState"] == "Active" - ), + return self.build_attribute( + device["hiloId"], "DrmsState", device["gDState"] == "Active" ) def _map_heating(self, device: Dict[str, Any]) -> Dict[str, Any]: @@ -538,4 +543,4 @@ def _map_to_ccr_mode(self, ccr_mode: int) -> str: case 3: return "Manual" case _: - return "" \ No newline at end of file + return "" diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index d7912f2..fd14612 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -148,6 +148,8 @@ async def async_init(self) -> None: value kind } + ccrMode, + ccrAllowedModes } ... on HeatingFloorThermostat { deviceType @@ -366,6 +368,8 @@ async def async_init(self) -> None: value kind } + ccrMode, + ccrAllowedModes } ... on HeatingFloorThermostat { deviceType @@ -417,7 +421,7 @@ async def async_init(self) -> None: zigbeeVersion state ccrType - alerts + alerts power { value kind From b33c4e4e32ae53384834d488312aeec7809c0352 Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Thu, 27 Mar 2025 17:06:38 -0400 Subject: [PATCH 11/12] fix more mapping --- pyhilo/device/graphql_value_mapper.py | 139 +++++++------------------- pyhilo/graphql.py | 38 +++++++ 2 files changed, 76 insertions(+), 101 deletions(-) diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index 4e60e7a..8ca54aa 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -51,7 +51,7 @@ def _map_devices_values(self, device: Dict[str, Any]) -> list[Dict[str, Any]]: attributes.extend(self._build_lowvoltage_thermostat(device)) case "chargingpoint": attributes.extend(self._build_charging_point(device)) - case "meter": + case "meter": # Smart Meter attributes.extend(self._build_smart_meter(device)) case "hub": # Gateway attributes.extend(self._build_gateway(device)) @@ -189,42 +189,43 @@ def _build_thermostat( self.build_attribute( device["hiloId"], "ThermostatMode", - self._map_to_thermostat_mode(device.get("mode")), + device.get("mode"), ) ) attributes.append(self._map_gd_state(device)) - if withDefaultMinMaxTemp: - attributes.extend( - [ - self.build_attribute(device["hiloId"], "MaxTempSetpoint", 30), - self.build_attribute(device["hiloId"], "MinTempSetpoint", 5), - ] - ) - else: - if device.get("maxAmbientTempSetpoint") is not None: + + if device.get("maxAmbientTempSetpoint") is not None: + attributes.append( self.build_attribute( - device["maxAmbientTempSetpoint"], + device["hiloId"], "MaxTempSetpoint", device["maxAmbientTempSetpoint"]["value"], ) - if device.get("minAmbientTempSetpoint") is not None: + ) + if device.get("minAmbientTempSetpoint") is not None: + attributes.append( self.build_attribute( device["hiloId"], "MinTempSetpoint", device["minAmbientTempSetpoint"]["value"], ) + ) if device.get("maxAmbientTempSetpointLimit") is not None: - self.build_attribute( - device["hiloId"], - "MaxTempSetpointLimit", - device["maxAmbientTempSetpointLimit"]["value"], + attributes.append( + self.build_attribute( + device["hiloId"], + "MaxTempSetpointLimit", + device["maxAmbientTempSetpointLimit"]["value"], + ) ) if device.get("minAmbientTempSetpointLimit") is not None: - self.build_attribute( - device["hiloId"], - "MinTempSetpointLimit", - device["minAmbientTempSetpointLimit"]["value"], + attributes.append( + self.build_attribute( + device["hiloId"], + "MinTempSetpointLimit", + device["minAmbientTempSetpointLimit"]["value"], + ) ) return attributes @@ -234,7 +235,7 @@ def _build_floor_thermostat(self, device: Dict[str, Any]) -> list[Dict[str, Any] self.build_attribute( device["hiloId"], "FloorMode", - self._map_to_floor_mode(device["floorMode"]), + device["floorMode"], ) ) if device.get("floorLimit") is not None: @@ -257,10 +258,6 @@ def _build_lowvoltage_thermostat( device["coolTempSetpoint"]["value"], ) ) - if device.get("fanSpeed") is not None: - attributes.append( - self.build_attribute(device["hiloId"], "FanSpeed", device["fanSpeed"]) - ) if device.get("minAmbientCoolSetPoint") is not None: attributes.append( self.build_attribute( @@ -277,48 +274,37 @@ def _build_lowvoltage_thermostat( device["maxAmbientCoolSetPoint"]["value"], ) ) - if device.get("minAmbientTempSetpoint") is not None: - attributes.append( - self.build_attribute( - device["hiloId"], - "MinTempSetpoint", - device["minAmbientTempSetpoint"]["value"], - ) - ) - if device.get("maxAmbientTempSetpoint") is not None: - attributes.append( - self.build_attribute( - device["hiloId"], - "MaxTempSetpoint", - device["maxAmbientTempSetpoint"]["value"], - ) - ) attributes.extend( [ self.build_attribute( - device["allowedModes"], + device["hiloId"], "Thermostat24VAllowedMode", - self._map_to_floor_mode(device["allowedModes"]), + device["allowedModes"], ), self.build_attribute( - device["fanAllowedModes"], + device["hiloId"], "Thermostat24VAllowedFanMode", - self._map_to_thermostat_mode(device["fanAllowedModes"]), + device["fanAllowedModes"], ), self.build_attribute( - device["fanMode"], + device["hiloId"], "FanMode", - self._map_to_thermostat_mode(device["fanMode"]), + device["fanMode"], ), self.build_attribute( - device["mode"], + device["hiloId"], "Thermostat24VMode", - self._map_to_thermostat_mode(device["mode"]), + device["mode"], ), self.build_attribute( - device["currentState"], + device["hiloId"], "CurrentState", - self._map_to_thermostat_mode(device["currentState"]), + device["currentState"], + ), + attributes.append( + self.build_attribute( + device["hiloId"], "FanSpeed", device.get("fanSpeed") + ) ), ] ) @@ -495,52 +481,3 @@ def build_attribute( "value": value, "timeStampUTC": datetime.now(timezone.utc).isoformat(), } - - def _map_to_floor_mode(self, floor_mode: int) -> str: - match floor_mode: - case 0: - return "Ambient" - case 1: - return "Floor" - case 2: - return "Hybrid" - - def _map_to_thermostat_mode(self, mode: int) -> str: - match mode: - case 0: - return "Unknown" - case 1: - return "Heat" - case 2: - return "Auto" - case 3: - return "AutoHeat" - case 4: - return "EmergencyHeat" - case 5: - return "Cool" - case 6: - return "AutoCool" - case 7: - return "SouthernAway" - case 8: - return "Off" - case 9: - return "Manual" - case 10: - return "AutoBypass" - case _: - return "" - - def _map_to_ccr_mode(self, ccr_mode: int) -> str: - match ccr_mode: - case 0: - return "Unknown" - case 1: - return "Auto" - case 2: - return "Off" - case 3: - return "Manual" - case _: - return "" diff --git a/pyhilo/graphql.py b/pyhilo/graphql.py index fd14612..2cf2aa2 100644 --- a/pyhilo/graphql.py +++ b/pyhilo/graphql.py @@ -236,10 +236,29 @@ async def async_init(self) -> None: value kind } + maxAmbientTempSetpoint { + value + kind + } + minAmbientTempSetpoint { + value + kind + } + maxAmbientTempSetpointLimit { + value + kind + } + minAmbientTempSetpointLimit { + value + kind + } + heatDemand power { value kind } + mode + allowedModes } } } @@ -456,10 +475,29 @@ async def async_init(self) -> None: value kind } + maxAmbientTempSetpoint { + value + kind + } + minAmbientTempSetpoint { + value + kind + } + maxAmbientTempSetpointLimit { + value + kind + } + minAmbientTempSetpointLimit { + value + kind + } + heatDemand power { value kind } + mode + allowedModes } } } From 2dad62303def2ed6924c4eb3a047f6f52c8c87f5 Mon Sep 17 00:00:00 2001 From: Abigail Asselin Date: Fri, 28 Mar 2025 13:27:43 -0400 Subject: [PATCH 12/12] cleanup and fix last mapping --- pyhilo/device/graphql_value_mapper.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyhilo/device/graphql_value_mapper.py b/pyhilo/device/graphql_value_mapper.py index 8ca54aa..ec107f8 100644 --- a/pyhilo/device/graphql_value_mapper.py +++ b/pyhilo/device/graphql_value_mapper.py @@ -1,7 +1,7 @@ from datetime import datetime, timezone -from typing import Any, Dict, List +from typing import Any, Dict -from pyhilo.device import DeviceReading, HiloDevice +from pyhilo.device import DeviceReading class GraphqlValueMapper: @@ -301,10 +301,8 @@ def _build_lowvoltage_thermostat( "CurrentState", device["currentState"], ), - attributes.append( - self.build_attribute( - device["hiloId"], "FanSpeed", device.get("fanSpeed") - ) + self.build_attribute( + device["hiloId"], "FanSpeed", device.get("fanSpeed") ), ] )