python-hilo 2025.6.1__tar.gz → 2025.9.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.6.1 → python_hilo-2025.9.1}/PKG-INFO +5 -3
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/__init__.py +0 -2
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/api.py +17 -16
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/const.py +1 -1
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/device/light.py +1 -1
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/device/sensor.py +1 -1
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/device/switch.py +1 -1
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/devices.py +6 -6
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/event.py +1 -1
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/graphql.py +12 -7
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/websocket.py +21 -13
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyproject.toml +2 -2
- python_hilo-2025.6.1/pyhilo/oauth2.py +0 -51
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/LICENSE +0 -0
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/README.md +0 -0
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/device/__init__.py +0 -0
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/device/climate.py +0 -0
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/device/graphql_value_mapper.py +0 -0
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/exceptions.py +0 -0
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/oauth2helper.py +0 -0
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/util/__init__.py +0 -0
- {python_hilo-2025.6.1 → python_hilo-2025.9.1}/pyhilo/util/state.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hilo
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.9.1
|
|
4
4
|
Summary: A Python3, async interface to the Hilo API
|
|
5
5
|
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Author: David Vallee Delisle
|
|
7
8
|
Author-email: me@dvd.dev
|
|
8
9
|
Maintainer: David Vallee Delisle
|
|
@@ -17,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
22
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
23
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
22
24
|
Classifier: Topic :: Home Automation
|
|
@@ -26,7 +28,7 @@ Requires-Dist: aiosignal (>=1.2.0)
|
|
|
26
28
|
Requires-Dist: async-timeout (>=4.0.0)
|
|
27
29
|
Requires-Dist: attrs (>=21.2.0)
|
|
28
30
|
Requires-Dist: backoff (>=1.11.1)
|
|
29
|
-
Requires-Dist: gql (>=3.5.2,<
|
|
31
|
+
Requires-Dist: gql (>=3.5.2,<5.0.0)
|
|
30
32
|
Requires-Dist: python-dateutil (>=2.8.2)
|
|
31
33
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
32
34
|
Requires-Dist: voluptuous (>=0.13.1)
|
|
@@ -6,7 +6,6 @@ from pyhilo.device.switch import Switch
|
|
|
6
6
|
from pyhilo.devices import Devices
|
|
7
7
|
from pyhilo.event import Event
|
|
8
8
|
from pyhilo.exceptions import HiloError, InvalidCredentialsError, WebsocketError
|
|
9
|
-
from pyhilo.oauth2 import AuthCodeWithPKCEImplementation
|
|
10
9
|
from pyhilo.util import from_utc_timestamp, time_diff
|
|
11
10
|
from pyhilo.websocket import WebsocketEvent
|
|
12
11
|
|
|
@@ -18,7 +17,6 @@ __all__ = [
|
|
|
18
17
|
"HiloError",
|
|
19
18
|
"InvalidCredentialsError",
|
|
20
19
|
"WebsocketError",
|
|
21
|
-
"AuthCodeWithPKCEImplementation",
|
|
22
20
|
"from_utc_timestamp",
|
|
23
21
|
"time_diff",
|
|
24
22
|
"WebsocketEvent",
|
|
@@ -140,7 +140,7 @@ class API:
|
|
|
140
140
|
await self._oauth_session.async_ensure_token_valid()
|
|
141
141
|
|
|
142
142
|
access_token = str(self._oauth_session.token["access_token"])
|
|
143
|
-
LOG.debug(
|
|
143
|
+
LOG.debug("Websocket access token is %s", access_token)
|
|
144
144
|
|
|
145
145
|
return str(self._oauth_session.token["access_token"])
|
|
146
146
|
|
|
@@ -246,8 +246,9 @@ class API:
|
|
|
246
246
|
data: dict[str, Any] = {}
|
|
247
247
|
url = parse.urljoin(f"https://{host}", endpoint)
|
|
248
248
|
if self.log_traces:
|
|
249
|
-
LOG.debug(
|
|
250
|
-
LOG.debug(
|
|
249
|
+
LOG.debug("[TRACE] Headers: %s", kwargs["headers"])
|
|
250
|
+
LOG.debug("[TRACE] Async request: %s %s", method, url)
|
|
251
|
+
|
|
251
252
|
async with self.session.request(method, url, **kwargs) as resp:
|
|
252
253
|
if "application/json" in resp.headers.get("content-type", ""):
|
|
253
254
|
try:
|
|
@@ -396,7 +397,7 @@ class API:
|
|
|
396
397
|
"""Retrieves and constructs WebSocket connection parameters from the negotiation endpoint."""
|
|
397
398
|
uri = parse.urlparse(self.ws_url)
|
|
398
399
|
LOG.debug("Getting websocket params")
|
|
399
|
-
LOG.debug(
|
|
400
|
+
LOG.debug("Getting uri %s", uri)
|
|
400
401
|
resp: dict[str, Any] = await self.async_request(
|
|
401
402
|
"post",
|
|
402
403
|
f"{uri.path}negotiate?{uri.query}",
|
|
@@ -407,7 +408,7 @@ class API:
|
|
|
407
408
|
)
|
|
408
409
|
conn_id: str = resp.get("connectionId", "")
|
|
409
410
|
self.full_ws_url = f"{self.ws_url}&id={conn_id}&access_token={self.ws_token}"
|
|
410
|
-
LOG.debug(
|
|
411
|
+
LOG.debug("Getting full ws URL %s", self.full_ws_url)
|
|
411
412
|
transport_dict: list[WebsocketTransportsDict] = resp.get(
|
|
412
413
|
"availableTransports", []
|
|
413
414
|
)
|
|
@@ -441,7 +442,7 @@ class API:
|
|
|
441
442
|
if err.status in (401, 403):
|
|
442
443
|
raise InvalidCredentialsError("Invalid credentials") from err
|
|
443
444
|
raise RequestError(err) from err
|
|
444
|
-
LOG.debug(
|
|
445
|
+
LOG.debug("FB Install data: %s", resp)
|
|
445
446
|
auth_token = resp.get("authToken", {})
|
|
446
447
|
LOG.debug("Calling set_state from fb_install")
|
|
447
448
|
await set_state(
|
|
@@ -479,7 +480,7 @@ class API:
|
|
|
479
480
|
if err.status in (401, 403):
|
|
480
481
|
raise InvalidCredentialsError("Invalid credentials") from err
|
|
481
482
|
raise RequestError(err) from err
|
|
482
|
-
LOG.debug(
|
|
483
|
+
LOG.debug("Android client register: %s", resp)
|
|
483
484
|
msg: str = resp.get("message", "")
|
|
484
485
|
if msg.startswith("Error="):
|
|
485
486
|
LOG.error(f"Android registration error: {msg}")
|
|
@@ -497,14 +498,14 @@ class API:
|
|
|
497
498
|
async def get_location_ids(self) -> tuple[int, str]:
|
|
498
499
|
"""Gets location id from an API call"""
|
|
499
500
|
url = f"{API_AUTOMATION_ENDPOINT}/Locations"
|
|
500
|
-
LOG.debug(
|
|
501
|
+
LOG.debug("LocationId URL is %s", url)
|
|
501
502
|
req: list[dict[str, Any]] = await self.async_request("get", url)
|
|
502
503
|
return (req[0]["id"], req[0]["locationHiloId"])
|
|
503
504
|
|
|
504
505
|
async def get_devices(self, location_id: int) -> list[dict[str, Any]]:
|
|
505
506
|
"""Get list of all devices"""
|
|
506
507
|
url = self._get_url("Devices", location_id)
|
|
507
|
-
LOG.debug(
|
|
508
|
+
LOG.debug("Devices URL is %s", url)
|
|
508
509
|
devices: list[dict[str, Any]] = await self.async_request("get", url)
|
|
509
510
|
devices.append(await self.get_gateway(location_id))
|
|
510
511
|
# Now it's time to add devices coming from external sources like hass
|
|
@@ -521,7 +522,7 @@ class API:
|
|
|
521
522
|
) -> None:
|
|
522
523
|
"""Sets device attributes"""
|
|
523
524
|
url = self._get_url(f"Devices/{device.id}/Attributes", device.location_id)
|
|
524
|
-
LOG.debug(
|
|
525
|
+
LOG.debug("Device Attribute URL is %s", url)
|
|
525
526
|
await self.async_request("put", url, json={key.hilo_attribute: value})
|
|
526
527
|
|
|
527
528
|
async def get_event_notifications(self, location_id: int) -> dict[str, Any]:
|
|
@@ -549,7 +550,7 @@ class API:
|
|
|
549
550
|
"viewed": false
|
|
550
551
|
}"""
|
|
551
552
|
url = self._get_url(None, location_id, events=True)
|
|
552
|
-
LOG.debug(
|
|
553
|
+
LOG.debug("Event Notifications URL is %s", url)
|
|
553
554
|
return cast(dict[str, Any], await self.async_request("get", url))
|
|
554
555
|
|
|
555
556
|
async def get_gd_events(
|
|
@@ -622,7 +623,7 @@ class API:
|
|
|
622
623
|
else:
|
|
623
624
|
url += f"/{event_id}"
|
|
624
625
|
|
|
625
|
-
LOG.debug(
|
|
626
|
+
LOG.debug("get_gd_events URL is %s", url)
|
|
626
627
|
return cast(dict[str, Any], await self.async_request("get", url))
|
|
627
628
|
|
|
628
629
|
async def get_seasons(self, location_id: int) -> dict[str, Any]:
|
|
@@ -645,13 +646,13 @@ class API:
|
|
|
645
646
|
]
|
|
646
647
|
"""
|
|
647
648
|
url = self._get_url("Seasons", location_id, challenge=True)
|
|
648
|
-
LOG.debug(
|
|
649
|
+
LOG.debug("Seasons URL is %s", url)
|
|
649
650
|
return cast(dict[str, Any], await self.async_request("get", url))
|
|
650
651
|
|
|
651
652
|
async def get_gateway(self, location_id: int) -> dict[str, Any]:
|
|
652
653
|
"""Gets info about the Hilo hub (gateway)"""
|
|
653
654
|
url = self._get_url("Gateways/Info", location_id)
|
|
654
|
-
LOG.debug(
|
|
655
|
+
LOG.debug("Gateway URL is %s", url)
|
|
655
656
|
req = await self.async_request("get", url)
|
|
656
657
|
saved_attrs = [
|
|
657
658
|
"zigBeePairingActivated",
|
|
@@ -694,7 +695,7 @@ class API:
|
|
|
694
695
|
]
|
|
695
696
|
"""
|
|
696
697
|
url = self._get_url("Weather", location_id)
|
|
697
|
-
LOG.debug(
|
|
698
|
+
LOG.debug("Weather URL is %s", url)
|
|
698
699
|
response = await self.async_request("get", url)
|
|
699
|
-
LOG.debug(
|
|
700
|
+
LOG.debug("Weather API response: %s", response)
|
|
700
701
|
return cast(dict[str, Any], await self.async_request("get", url))
|
|
@@ -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.9.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"
|
|
@@ -10,7 +10,7 @@ from pyhilo.device import HiloDevice
|
|
|
10
10
|
class Light(HiloDevice):
|
|
11
11
|
def __init__(self, api: API, **kwargs: dict[str, Union[str, int]]):
|
|
12
12
|
super().__init__(api, **kwargs) # type: ignore
|
|
13
|
-
LOG.debug(
|
|
13
|
+
LOG.debug("Setting up Light device: %s", self.name)
|
|
14
14
|
|
|
15
15
|
@property
|
|
16
16
|
def brightness(self) -> float:
|
|
@@ -10,7 +10,7 @@ from pyhilo.device import HiloDevice
|
|
|
10
10
|
class Sensor(HiloDevice):
|
|
11
11
|
def __init__(self, api: API, **kwargs: dict[str, Union[str, int]]):
|
|
12
12
|
super().__init__(api, **kwargs) # type: ignore
|
|
13
|
-
LOG.debug(
|
|
13
|
+
LOG.debug("Setting up Sensor device: %s", self.name)
|
|
14
14
|
|
|
15
15
|
@property
|
|
16
16
|
def state(self) -> str:
|
|
@@ -10,7 +10,7 @@ from pyhilo.device import HiloDevice
|
|
|
10
10
|
class Switch(HiloDevice):
|
|
11
11
|
def __init__(self, api: API, **kwargs: dict[str, Union[str, int]]):
|
|
12
12
|
super().__init__(api, **kwargs) # type: ignore
|
|
13
|
-
LOG.debug(
|
|
13
|
+
LOG.debug("Setting up Switch device: %s", self.name)
|
|
14
14
|
|
|
15
15
|
@property
|
|
16
16
|
def state(self) -> str:
|
|
@@ -38,7 +38,7 @@ class Devices:
|
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
def parse_values_received(self, values: list[dict[str, Any]]) -> list[HiloDevice]:
|
|
41
|
-
"""Places value received in a dict while removing null attributes,
|
|
41
|
+
"""Places value received in a dict while removing null attributes,
|
|
42
42
|
this returns values to be mapped to devices.
|
|
43
43
|
"""
|
|
44
44
|
readings = []
|
|
@@ -60,7 +60,7 @@ class Devices:
|
|
|
60
60
|
device_identifier = reading.hilo_id
|
|
61
61
|
if device := self.find_device(device_identifier):
|
|
62
62
|
device.update_readings(reading)
|
|
63
|
-
LOG.debug(
|
|
63
|
+
LOG.debug("%s Received %s", device, reading)
|
|
64
64
|
if device not in updated_devices:
|
|
65
65
|
updated_devices.append(device)
|
|
66
66
|
else:
|
|
@@ -96,24 +96,24 @@ class Devices:
|
|
|
96
96
|
fresh_devices = await self._api.get_devices(self.location_id)
|
|
97
97
|
generated_devices = []
|
|
98
98
|
for raw_device in fresh_devices:
|
|
99
|
-
LOG.debug(
|
|
99
|
+
LOG.debug("Generating device %s", raw_device)
|
|
100
100
|
dev = self.generate_device(raw_device)
|
|
101
101
|
generated_devices.append(dev)
|
|
102
102
|
if dev not in self.devices:
|
|
103
103
|
self.devices.append(dev)
|
|
104
104
|
for device in self.devices:
|
|
105
105
|
if device not in generated_devices:
|
|
106
|
-
LOG.debug(
|
|
106
|
+
LOG.debug("Device unpaired %s", device)
|
|
107
107
|
# Don't do anything with unpaired device for now.
|
|
108
108
|
# self.devices.remove(device)
|
|
109
109
|
|
|
110
110
|
async def update_devicelist_from_signalr(
|
|
111
111
|
self, values: list[dict[str, Any]]
|
|
112
112
|
) -> list[HiloDevice]:
|
|
113
|
-
#ic-dev21 not sure if this is dead code?
|
|
113
|
+
# ic-dev21 not sure if this is dead code?
|
|
114
114
|
new_devices = []
|
|
115
115
|
for raw_device in values:
|
|
116
|
-
LOG.debug(
|
|
116
|
+
LOG.debug("Generating device %s", raw_device)
|
|
117
117
|
dev = self.generate_device(raw_device)
|
|
118
118
|
if dev not in self.devices:
|
|
119
119
|
self.devices.append(dev)
|
|
@@ -67,7 +67,7 @@ class Event:
|
|
|
67
67
|
|
|
68
68
|
def update_wh(self, used_wH: float) -> None:
|
|
69
69
|
"""This function is used to update the used_kWh attribute during a Hilo Challenge Event"""
|
|
70
|
-
LOG.debug(
|
|
70
|
+
LOG.debug("Updating Wh: %s", used_wH)
|
|
71
71
|
self.used_kWh = round(used_wH / 1000, 2)
|
|
72
72
|
self.last_update = datetime.now(timezone.utc).astimezone()
|
|
73
73
|
|
|
@@ -562,23 +562,28 @@ class GraphQlHelper:
|
|
|
562
562
|
query, variable_values={"locationHiloId": location_hilo_id}
|
|
563
563
|
):
|
|
564
564
|
LOG.debug(
|
|
565
|
-
|
|
565
|
+
"subscribe_to_device_updated: Received subscription result %s",
|
|
566
|
+
result,
|
|
566
567
|
)
|
|
567
568
|
device_hilo_id = self._handle_device_subscription_result(result)
|
|
568
569
|
if callback:
|
|
569
570
|
callback(device_hilo_id)
|
|
570
571
|
except Exception as e:
|
|
571
572
|
LOG.debug(
|
|
572
|
-
|
|
573
|
+
"subscribe_to_device_updated: Connection lost: %s. Reconnecting in 5 seconds...",
|
|
574
|
+
e,
|
|
573
575
|
)
|
|
574
576
|
await asyncio.sleep(5)
|
|
575
577
|
try:
|
|
576
578
|
await self.call_get_location_query(location_hilo_id)
|
|
577
|
-
LOG.debug(
|
|
579
|
+
LOG.debug(
|
|
580
|
+
"subscribe_to_device_updated, call_get_location_query success"
|
|
581
|
+
)
|
|
578
582
|
|
|
579
583
|
except Exception as e2:
|
|
580
584
|
LOG.error(
|
|
581
|
-
|
|
585
|
+
"subscribe_to_device_updated, exception while reconnecting, retrying: %s",
|
|
586
|
+
e2,
|
|
582
587
|
)
|
|
583
588
|
|
|
584
589
|
async def subscribe_to_location_updated(
|
|
@@ -595,7 +600,7 @@ class GraphQlHelper:
|
|
|
595
600
|
async for result in session.subscribe(
|
|
596
601
|
query, variable_values={"locationHiloId": location_hilo_id}
|
|
597
602
|
):
|
|
598
|
-
LOG.debug(
|
|
603
|
+
LOG.debug("Received subscription result %s", result)
|
|
599
604
|
device_hilo_id = self._handle_location_subscription_result(result)
|
|
600
605
|
callback(device_hilo_id)
|
|
601
606
|
except asyncio.CancelledError:
|
|
@@ -618,7 +623,7 @@ class GraphQlHelper:
|
|
|
618
623
|
attributes = self.mapper.map_device_subscription_values(devices_values)
|
|
619
624
|
updated_device = self._devices.parse_values_received(attributes)
|
|
620
625
|
# callback to update the device in the UI
|
|
621
|
-
LOG.debug(
|
|
626
|
+
LOG.debug("Device updated: %s", updated_device)
|
|
622
627
|
return devices_values.get("hiloId")
|
|
623
628
|
|
|
624
629
|
def _handle_location_subscription_result(self, result: Dict[str, Any]) -> str:
|
|
@@ -626,5 +631,5 @@ class GraphQlHelper:
|
|
|
626
631
|
attributes = self.mapper.map_location_subscription_values(devices_values)
|
|
627
632
|
updated_device = self._devices.parse_values_received(attributes)
|
|
628
633
|
# callback to update the device in the UI
|
|
629
|
-
LOG.debug(
|
|
634
|
+
LOG.debug("Device updated: %s", updated_device)
|
|
630
635
|
return devices_values.get("hiloId")
|
|
@@ -214,18 +214,22 @@ class WebsocketClient:
|
|
|
214
214
|
|
|
215
215
|
if self._api.log_traces:
|
|
216
216
|
LOG.debug(
|
|
217
|
-
|
|
217
|
+
"[TRACE] Sending data to websocket %s : %s",
|
|
218
|
+
self._api.endpoint,
|
|
219
|
+
json.dumps(payload),
|
|
218
220
|
)
|
|
219
221
|
# Hilo added a control character (chr(30)) at the end of each payload they send.
|
|
220
222
|
# They also expect this char to be there at the end of every payload we send them.
|
|
221
|
-
LOG.debug(
|
|
223
|
+
LOG.debug("WebsocketClient _async_send_json payload: %s", payload)
|
|
222
224
|
await self._client.send_str(json.dumps(payload) + chr(30))
|
|
223
225
|
|
|
224
226
|
def _parse_message(self, msg: dict[str, Any]) -> None:
|
|
225
227
|
"""Parse an incoming message."""
|
|
226
228
|
if self._api.log_traces:
|
|
227
229
|
LOG.debug(
|
|
228
|
-
|
|
230
|
+
"[TRACE] Received message on websocket(_parse_message) %s: %s",
|
|
231
|
+
self._api.endpoint,
|
|
232
|
+
msg,
|
|
229
233
|
)
|
|
230
234
|
if msg.get("type") == SignalRMsgType.PING:
|
|
231
235
|
schedule_callback(self._async_pong)
|
|
@@ -272,7 +276,7 @@ class WebsocketClient:
|
|
|
272
276
|
|
|
273
277
|
LOG.info("Websocket: Connecting to server %s", self._api.endpoint)
|
|
274
278
|
if self._api.log_traces:
|
|
275
|
-
LOG.debug(
|
|
279
|
+
LOG.debug("[TRACE] Websocket URL: %s", self._api.full_ws_url)
|
|
276
280
|
headers = {
|
|
277
281
|
"Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits",
|
|
278
282
|
"Pragma": "no-cache",
|
|
@@ -396,9 +400,13 @@ class WebsocketClient:
|
|
|
396
400
|
except asyncio.TimeoutError:
|
|
397
401
|
return
|
|
398
402
|
self._ready_event.clear()
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
403
|
+
LOG.debug(
|
|
404
|
+
"async_invoke invoke argument: %s, invocationId: %s, target: %s, type: %s",
|
|
405
|
+
arg,
|
|
406
|
+
inv_id,
|
|
407
|
+
target,
|
|
408
|
+
type,
|
|
409
|
+
)
|
|
402
410
|
await self._async_send_json(
|
|
403
411
|
{
|
|
404
412
|
"arguments": arg,
|
|
@@ -481,9 +489,9 @@ class WebsocketManager:
|
|
|
481
489
|
Returns:
|
|
482
490
|
Tuple containing the websocket URL and access token
|
|
483
491
|
"""
|
|
484
|
-
LOG.debug(
|
|
492
|
+
LOG.debug("Getting websocket url for %s", config.endpoint)
|
|
485
493
|
url = f"{config.endpoint}/negotiate"
|
|
486
|
-
LOG.debug(
|
|
494
|
+
LOG.debug("Negotiate URL is %s", url)
|
|
487
495
|
|
|
488
496
|
resp = await self.async_request("post", url)
|
|
489
497
|
ws_url = resp.get("url")
|
|
@@ -513,8 +521,8 @@ class WebsocketManager:
|
|
|
513
521
|
config: The websocket configuration to get parameters for
|
|
514
522
|
"""
|
|
515
523
|
uri = parse.urlparse(config.url)
|
|
516
|
-
LOG.debug(
|
|
517
|
-
LOG.debug(
|
|
524
|
+
LOG.debug("Getting websocket params for %s", config.endpoint)
|
|
525
|
+
LOG.debug("Getting uri %s", uri)
|
|
518
526
|
|
|
519
527
|
resp = await self.async_request(
|
|
520
528
|
"post",
|
|
@@ -529,7 +537,7 @@ class WebsocketManager:
|
|
|
529
537
|
config.full_ws_url = (
|
|
530
538
|
f"{config.url}&id={config.connection_id}&access_token={config.token}"
|
|
531
539
|
)
|
|
532
|
-
LOG.debug(
|
|
540
|
+
LOG.debug("Getting full ws URL %s", config.full_ws_url)
|
|
533
541
|
|
|
534
542
|
transport_dict = resp.get("availableTransports", [])
|
|
535
543
|
websocket_dict = {
|
|
@@ -544,5 +552,5 @@ class WebsocketManager:
|
|
|
544
552
|
if config.endpoint == AUTOMATION_DEVICEHUB_ENDPOINT
|
|
545
553
|
else "websocketChallenges"
|
|
546
554
|
)
|
|
547
|
-
LOG.debug(
|
|
555
|
+
LOG.debug("Calling set_state %s_params", state_key)
|
|
548
556
|
await self._set_state(self._state_yaml, state_key, websocket_dict)
|
|
@@ -40,7 +40,7 @@ exclude = ".venv/.*"
|
|
|
40
40
|
|
|
41
41
|
[tool.poetry]
|
|
42
42
|
name = "python-hilo"
|
|
43
|
-
version = "2025.
|
|
43
|
+
version = "2025.9.1"
|
|
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>"]
|
|
@@ -74,7 +74,7 @@ python-dateutil = ">=2.8.2"
|
|
|
74
74
|
python = "^3.9.0"
|
|
75
75
|
voluptuous = ">=0.13.1"
|
|
76
76
|
websockets = ">=8.1,<16.0"
|
|
77
|
-
gql = "
|
|
77
|
+
gql = ">=3.5.2,<5.0.0"
|
|
78
78
|
pyyaml = "^6.0.2"
|
|
79
79
|
|
|
80
80
|
[poetry.group.dev.dependencies]
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"""Custom OAuth2 implementation."""
|
|
2
|
-
|
|
3
|
-
from typing import Any, cast
|
|
4
|
-
|
|
5
|
-
from homeassistant.core import HomeAssistant
|
|
6
|
-
from homeassistant.helpers.config_entry_oauth2_flow import LocalOAuth2Implementation
|
|
7
|
-
|
|
8
|
-
from pyhilo.const import AUTH_AUTHORIZE, AUTH_CLIENT_ID, AUTH_TOKEN, DOMAIN
|
|
9
|
-
from pyhilo.oauth2helper import OAuth2Helper
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class AuthCodeWithPKCEImplementation(LocalOAuth2Implementation): # type: ignore[misc]
|
|
13
|
-
"""Custom OAuth2 implementation."""
|
|
14
|
-
|
|
15
|
-
def __init__(
|
|
16
|
-
self,
|
|
17
|
-
hass: HomeAssistant,
|
|
18
|
-
) -> None:
|
|
19
|
-
"""Initialize AuthCodeWithPKCEImplementation."""
|
|
20
|
-
super().__init__(
|
|
21
|
-
hass,
|
|
22
|
-
DOMAIN,
|
|
23
|
-
AUTH_CLIENT_ID,
|
|
24
|
-
"",
|
|
25
|
-
AUTH_AUTHORIZE,
|
|
26
|
-
AUTH_TOKEN,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
self.oauth_helper = OAuth2Helper()
|
|
30
|
-
|
|
31
|
-
# ... Override AbstractOAuth2Implementation details
|
|
32
|
-
@property
|
|
33
|
-
def name(self) -> str:
|
|
34
|
-
"""Name of the implementation."""
|
|
35
|
-
return "Hilo"
|
|
36
|
-
|
|
37
|
-
@property
|
|
38
|
-
def extra_authorize_data(self) -> dict:
|
|
39
|
-
"""Extra data that needs to be appended to the authorize url."""
|
|
40
|
-
return self.oauth_helper.get_authorize_parameters()
|
|
41
|
-
|
|
42
|
-
async def async_resolve_external_data(self, external_data: Any) -> dict:
|
|
43
|
-
"""Resolve the authorization code to tokens."""
|
|
44
|
-
return cast(
|
|
45
|
-
dict,
|
|
46
|
-
await self._token_request(
|
|
47
|
-
self.oauth_helper.get_token_request_parameters(
|
|
48
|
-
external_data["code"], external_data["state"]["redirect_uri"]
|
|
49
|
-
)
|
|
50
|
-
),
|
|
51
|
-
)
|
|
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
|