python-hilo 2025.10.2__tar.gz → 2025.12.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/PKG-INFO +1 -1
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/api.py +6 -4
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/const.py +2 -1
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/devices.py +2 -2
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/event.py +7 -1
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/graphql.py +11 -4
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/websocket.py +4 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyproject.toml +1 -1
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/LICENSE +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/README.md +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/__init__.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/device/__init__.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/device/climate.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/device/graphql_value_mapper.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/device/light.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/device/sensor.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/device/switch.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/exceptions.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/oauth2helper.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/util/__init__.py +0 -0
- {python_hilo-2025.10.2 → python_hilo-2025.12.1}/pyhilo/util/state.py +0 -0
|
@@ -12,6 +12,7 @@ from urllib import parse
|
|
|
12
12
|
from aiohttp import ClientSession
|
|
13
13
|
from aiohttp.client_exceptions import ClientResponseError
|
|
14
14
|
import backoff
|
|
15
|
+
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
|
|
15
16
|
|
|
16
17
|
from pyhilo.const import (
|
|
17
18
|
ANDROID_CLIENT_ENDPOINT,
|
|
@@ -66,7 +67,7 @@ class API:
|
|
|
66
67
|
self,
|
|
67
68
|
*,
|
|
68
69
|
session: ClientSession,
|
|
69
|
-
oauth_session,
|
|
70
|
+
oauth_session: OAuth2Session,
|
|
70
71
|
request_retries: int = REQUEST_RETRY,
|
|
71
72
|
log_traces: bool = False,
|
|
72
73
|
) -> None:
|
|
@@ -97,7 +98,7 @@ class API:
|
|
|
97
98
|
cls,
|
|
98
99
|
*,
|
|
99
100
|
session: ClientSession,
|
|
100
|
-
oauth_session,
|
|
101
|
+
oauth_session: OAuth2Session,
|
|
101
102
|
request_retries: int = REQUEST_RETRY,
|
|
102
103
|
log_traces: bool = False,
|
|
103
104
|
) -> API:
|
|
@@ -382,11 +383,12 @@ class API:
|
|
|
382
383
|
# Create both websocket clients
|
|
383
384
|
# ic-dev21 need to work on this as it can't lint as is, may need to
|
|
384
385
|
# instantiate differently
|
|
385
|
-
|
|
386
|
+
# TODO: fix type ignore after refactor
|
|
387
|
+
self.websocket_devices = WebsocketClient(self.websocket_manager.devicehub) # type: ignore
|
|
386
388
|
|
|
387
389
|
# For backward compatibility during the transition to challengehub websocket
|
|
388
390
|
self.websocket = self.websocket_devices
|
|
389
|
-
self.websocket_challenges = WebsocketClient(self.websocket_manager.challengehub)
|
|
391
|
+
self.websocket_challenges = WebsocketClient(self.websocket_manager.challengehub) # type: ignore
|
|
390
392
|
|
|
391
393
|
async def refresh_ws_token(self) -> None:
|
|
392
394
|
"""Refresh the websocket token."""
|
|
@@ -7,7 +7,7 @@ import aiohttp
|
|
|
7
7
|
LOG: Final = logging.getLogger(__package__)
|
|
8
8
|
DEFAULT_STATE_FILE: Final = "hilo_state.yaml"
|
|
9
9
|
REQUEST_RETRY: Final = 9
|
|
10
|
-
PYHILO_VERSION: Final = "2025.
|
|
10
|
+
PYHILO_VERSION: Final = "2025.12.01"
|
|
11
11
|
# TODO: Find a way to keep previous line in sync with pyproject.toml automatically
|
|
12
12
|
|
|
13
13
|
CONTENT_TYPE_FORM: Final = "application/x-www-form-urlencoded"
|
|
@@ -32,6 +32,7 @@ API_GD_SERVICE_ENDPOINT: Final = f"/GDService/{API_END}"
|
|
|
32
32
|
API_NOTIFICATIONS_ENDPOINT: Final = "/Notifications"
|
|
33
33
|
API_EVENTS_ENDPOINT: Final = "/Notifications"
|
|
34
34
|
API_REGISTRATION_ENDPOINT: Final = f"{API_NOTIFICATIONS_ENDPOINT}/Registrations"
|
|
35
|
+
PLATFORM_HOST: Final = "platform.hiloenergie.com"
|
|
35
36
|
|
|
36
37
|
API_REGISTRATION_HEADERS: Final = {
|
|
37
38
|
"AppId": ANDROID_PKG_NAME,
|
|
@@ -55,7 +55,7 @@ class Devices:
|
|
|
55
55
|
"""Uses the dict from parse_values_received to map the values to devices."""
|
|
56
56
|
updated_devices = []
|
|
57
57
|
for reading in readings:
|
|
58
|
-
device_identifier = reading.device_id
|
|
58
|
+
device_identifier: Union[int, str] = reading.device_id
|
|
59
59
|
if device_identifier == 0:
|
|
60
60
|
device_identifier = reading.hilo_id
|
|
61
61
|
if device := self.find_device(device_identifier):
|
|
@@ -69,7 +69,7 @@ class Devices:
|
|
|
69
69
|
)
|
|
70
70
|
return updated_devices
|
|
71
71
|
|
|
72
|
-
def find_device(self, device_identifier: int | str) -> HiloDevice:
|
|
72
|
+
def find_device(self, device_identifier: int | str) -> HiloDevice | None:
|
|
73
73
|
"""Makes sure the devices received have an identifier, this means some need to be hardcoded
|
|
74
74
|
like the unknown power meter.
|
|
75
75
|
"""
|
|
@@ -27,7 +27,7 @@ class Event:
|
|
|
27
27
|
def __init__(self, **event: dict[str, Any]):
|
|
28
28
|
"""Initialize."""
|
|
29
29
|
self._convert_phases(cast(dict[str, Any], event.get("phases")))
|
|
30
|
-
params: dict[str, Any] = event.get("parameters"
|
|
30
|
+
params: dict[str, Any] = event.get("parameters") or {}
|
|
31
31
|
devices: list[dict[str, Any]] = params.get("devices", [])
|
|
32
32
|
consumption: dict[str, Any] = event.get("consumption", {})
|
|
33
33
|
allowed_wH: int = consumption.get("baselineWh", 0) or 0
|
|
@@ -71,6 +71,12 @@ class Event:
|
|
|
71
71
|
self.used_kWh = round(used_wH / 1000, 2)
|
|
72
72
|
self.last_update = datetime.now(timezone.utc).astimezone()
|
|
73
73
|
|
|
74
|
+
def update_allowed_wh(self, allowed_wH: float) -> None:
|
|
75
|
+
"""This function is used to update the allowed_kWh attribute during a Hilo Challenge Event"""
|
|
76
|
+
LOG.debug("Updating allowed Wh: %s", allowed_wH)
|
|
77
|
+
self.allowed_kWh = round(allowed_wH / 1000, 2)
|
|
78
|
+
self.last_update = datetime.now(timezone.utc).astimezone()
|
|
79
|
+
|
|
74
80
|
def should_check_for_allowed_wh(self) -> bool:
|
|
75
81
|
"""This function is used to authorize subscribing to a specific event in Hilo to receive the allowed_kWh
|
|
76
82
|
that is made available in the pre_heat phase"""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
+
import ssl
|
|
3
4
|
from typing import Any, Dict, List, Optional
|
|
4
5
|
|
|
5
6
|
from gql import Client, gql
|
|
@@ -7,7 +8,7 @@ from gql.transport.aiohttp import AIOHTTPTransport
|
|
|
7
8
|
from gql.transport.websockets import WebsocketsTransport
|
|
8
9
|
|
|
9
10
|
from pyhilo import API
|
|
10
|
-
from pyhilo.const import LOG
|
|
11
|
+
from pyhilo.const import LOG, PLATFORM_HOST
|
|
11
12
|
from pyhilo.device.graphql_value_mapper import GraphqlValueMapper
|
|
12
13
|
from pyhilo.devices import Devices
|
|
13
14
|
|
|
@@ -533,7 +534,7 @@ class GraphQlHelper:
|
|
|
533
534
|
"""This functions calls the digital-twin and requests location id"""
|
|
534
535
|
access_token = await self._get_access_token()
|
|
535
536
|
transport = AIOHTTPTransport(
|
|
536
|
-
url="https://
|
|
537
|
+
url=f"https://{PLATFORM_HOST}/api/digital-twin/v3/graphql",
|
|
537
538
|
headers={"Authorization": f"Bearer {access_token}"},
|
|
538
539
|
)
|
|
539
540
|
client = Client(transport=transport, fetch_schema_from_transport=True)
|
|
@@ -552,11 +553,17 @@ class GraphQlHelper:
|
|
|
552
553
|
|
|
553
554
|
# Setting log level to suppress keepalive messages on gql transport
|
|
554
555
|
logging.getLogger("gql.transport.websockets").setLevel(logging.WARNING)
|
|
556
|
+
|
|
557
|
+
#
|
|
558
|
+
loop = asyncio.get_event_loop()
|
|
559
|
+
ssl_context = await loop.run_in_executor(None, ssl.create_default_context)
|
|
560
|
+
|
|
555
561
|
while True: # Loop to reconnect if the connection is lost
|
|
556
562
|
LOG.debug("subscribe_to_device_updated while true")
|
|
557
563
|
access_token = await self._get_access_token()
|
|
558
564
|
transport = WebsocketsTransport(
|
|
559
|
-
url=f"wss://
|
|
565
|
+
url=f"wss://{PLATFORM_HOST}/api/digital-twin/v3/graphql?access_token={access_token}",
|
|
566
|
+
ssl=ssl_context,
|
|
560
567
|
)
|
|
561
568
|
client = Client(transport=transport, fetch_schema_from_transport=True)
|
|
562
569
|
query = gql(self.SUBSCRIPTION_DEVICE_UPDATED)
|
|
@@ -595,7 +602,7 @@ class GraphQlHelper:
|
|
|
595
602
|
) -> None:
|
|
596
603
|
access_token = await self._get_access_token()
|
|
597
604
|
transport = WebsocketsTransport(
|
|
598
|
-
url=f"wss://
|
|
605
|
+
url=f"wss://{PLATFORM_HOST}/api/digital-twin/v3/graphql?access_token={access_token}"
|
|
599
606
|
)
|
|
600
607
|
client = Client(transport=transport, fetch_schema_from_transport=True)
|
|
601
608
|
query = gql(self.SUBSCRIPTION_LOCATION_UPDATED)
|
|
@@ -310,6 +310,10 @@ class WebsocketClient:
|
|
|
310
310
|
raise CannotConnectError(err) from err
|
|
311
311
|
|
|
312
312
|
LOG.info(f"Connected to websocket server {self._api.endpoint}")
|
|
313
|
+
|
|
314
|
+
# Quick pause to prevent race condition
|
|
315
|
+
await asyncio.sleep(0.05)
|
|
316
|
+
|
|
313
317
|
self._watchdog.trigger()
|
|
314
318
|
for callback in self._connect_callbacks:
|
|
315
319
|
schedule_callback(callback)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|