python-hilo 2025.11.1__tar.gz → 2025.12.2__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.
Files changed (21) hide show
  1. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/PKG-INFO +1 -1
  2. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/api.py +6 -4
  3. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/const.py +2 -1
  4. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/devices.py +2 -2
  5. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/event.py +9 -2
  6. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/graphql.py +4 -4
  7. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/websocket.py +11 -0
  8. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyproject.toml +1 -1
  9. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/LICENSE +0 -0
  10. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/README.md +0 -0
  11. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/__init__.py +0 -0
  12. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/device/__init__.py +0 -0
  13. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/device/climate.py +0 -0
  14. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/device/graphql_value_mapper.py +0 -0
  15. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/device/light.py +0 -0
  16. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/device/sensor.py +0 -0
  17. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/device/switch.py +0 -0
  18. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/exceptions.py +0 -0
  19. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/oauth2helper.py +0 -0
  20. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/util/__init__.py +0 -0
  21. {python_hilo-2025.11.1 → python_hilo-2025.12.2}/pyhilo/util/state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hilo
3
- Version: 2025.11.1
3
+ Version: 2025.12.2
4
4
  Summary: A Python3, async interface to the Hilo API
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -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
- self.websocket_devices = WebsocketClient(self.websocket_manager.devicehub)
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.11.01"
10
+ PYHILO_VERSION: Final = "2025.12.02"
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"""
@@ -102,8 +108,9 @@ class Event:
102
108
  self.phases_list.append(phase)
103
109
  for phase in self.__annotations__:
104
110
  if phase not in self.phases_list:
111
+ now_with_tz = datetime.now(timezone.utc).astimezone()
105
112
  # On t'aime Carl
106
- setattr(self, phase, from_utc_timestamp("2023-11-15T20:00:00+00:00"))
113
+ setattr(self, phase, now_with_tz)
107
114
 
108
115
  def _create_phases(
109
116
  self, hours: int, phase_name: str, parent_phase: str
@@ -8,7 +8,7 @@ from gql.transport.aiohttp import AIOHTTPTransport
8
8
  from gql.transport.websockets import WebsocketsTransport
9
9
 
10
10
  from pyhilo import API
11
- from pyhilo.const import LOG
11
+ from pyhilo.const import LOG, PLATFORM_HOST
12
12
  from pyhilo.device.graphql_value_mapper import GraphqlValueMapper
13
13
  from pyhilo.devices import Devices
14
14
 
@@ -534,7 +534,7 @@ class GraphQlHelper:
534
534
  """This functions calls the digital-twin and requests location id"""
535
535
  access_token = await self._get_access_token()
536
536
  transport = AIOHTTPTransport(
537
- url="https://platform.hiloenergie.com/api/digital-twin/v3/graphql",
537
+ url=f"https://{PLATFORM_HOST}/api/digital-twin/v3/graphql",
538
538
  headers={"Authorization": f"Bearer {access_token}"},
539
539
  )
540
540
  client = Client(transport=transport, fetch_schema_from_transport=True)
@@ -562,7 +562,7 @@ class GraphQlHelper:
562
562
  LOG.debug("subscribe_to_device_updated while true")
563
563
  access_token = await self._get_access_token()
564
564
  transport = WebsocketsTransport(
565
- url=f"wss://platform.hiloenergie.com/api/digital-twin/v3/graphql?access_token={access_token}",
565
+ url=f"wss://{PLATFORM_HOST}/api/digital-twin/v3/graphql?access_token={access_token}",
566
566
  ssl=ssl_context,
567
567
  )
568
568
  client = Client(transport=transport, fetch_schema_from_transport=True)
@@ -602,7 +602,7 @@ class GraphQlHelper:
602
602
  ) -> None:
603
603
  access_token = await self._get_access_token()
604
604
  transport = WebsocketsTransport(
605
- url=f"wss://platform.hiloenergie.com/api/digital-twin/v3/graphql?access_token={access_token}"
605
+ url=f"wss://{PLATFORM_HOST}/api/digital-twin/v3/graphql?access_token={access_token}"
606
606
  )
607
607
  client = Client(transport=transport, fetch_schema_from_transport=True)
608
608
  query = gql(self.SUBSCRIPTION_LOCATION_UPDATED)
@@ -274,6 +274,10 @@ class WebsocketClient:
274
274
  LOG.debug("Websocket: async_connect() called but already connected")
275
275
  return
276
276
 
277
+ if self._api.session.closed:
278
+ LOG.error("Websocket: Cannot connect, session is closed")
279
+ raise CannotConnectError("Session is closed")
280
+
277
281
  LOG.info("Websocket: Connecting to server %s", self._api.endpoint)
278
282
  if self._api.log_traces:
279
283
  LOG.debug("[TRACE] Websocket URL: %s", self._api.full_ws_url)
@@ -310,6 +314,10 @@ class WebsocketClient:
310
314
  raise CannotConnectError(err) from err
311
315
 
312
316
  LOG.info(f"Connected to websocket server {self._api.endpoint}")
317
+
318
+ # Quick pause to prevent race condition
319
+ await asyncio.sleep(0.05)
320
+
313
321
  self._watchdog.trigger()
314
322
  for callback in self._connect_callbacks:
315
323
  schedule_callback(callback)
@@ -340,6 +348,9 @@ class WebsocketClient:
340
348
  messages = await self._async_receive_json()
341
349
  for msg in messages:
342
350
  self._parse_message(msg)
351
+ except asyncio.CancelledError:
352
+ LOG.info("Websocket: Listen cancelled.")
353
+ raise
343
354
  except ConnectionClosedError as err:
344
355
  LOG.error(f"Websocket: Closed while listening: {err}")
345
356
  LOG.exception(err)
@@ -40,7 +40,7 @@ exclude = ".venv/.*"
40
40
 
41
41
  [tool.poetry]
42
42
  name = "python-hilo"
43
- version = "2025.11.1"
43
+ version = "2025.12.2"
44
44
  description = "A Python3, async interface to the Hilo API"
45
45
  readme = "README.md"
46
46
  authors = ["David Vallee Delisle <me@dvd.dev>"]
File without changes