python-hilo 2024.3.1__tar.gz → 2024.6.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-2024.3.1 → python_hilo-2024.6.1}/PKG-INFO +3 -2
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/api.py +33 -12
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/const.py +6 -1
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/util/state.py +10 -7
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyproject.toml +5 -4
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/LICENSE +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/README.md +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/__init__.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/device/__init__.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/device/climate.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/device/light.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/device/sensor.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/device/switch.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/devices.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/event.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/exceptions.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/oauth2.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/util/__init__.py +0 -0
- {python_hilo-2024.3.1 → python_hilo-2024.6.1}/pyhilo/websocket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-hilo
|
|
3
|
-
Version: 2024.
|
|
3
|
+
Version: 2024.6.1
|
|
4
4
|
Summary: A Python3, async interface to the Hilo API
|
|
5
5
|
Home-page: https://github.com/dvd-dev/python-hilo
|
|
6
6
|
License: MIT
|
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
20
20
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
21
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
22
22
|
Classifier: Topic :: Home Automation
|
|
23
|
+
Requires-Dist: aiofiles (>=23.2.1)
|
|
23
24
|
Requires-Dist: aiohttp (>=3.8.0)
|
|
24
25
|
Requires-Dist: aiosignal (>=1.2.0)
|
|
25
26
|
Requires-Dist: async-timeout (>=4.0.0)
|
|
@@ -28,7 +29,7 @@ Requires-Dist: backoff (>=1.11.1)
|
|
|
28
29
|
Requires-Dist: python-dateutil (>=2.8.2)
|
|
29
30
|
Requires-Dist: ruyaml (>=0.91.0)
|
|
30
31
|
Requires-Dist: voluptuous (>=0.13.1)
|
|
31
|
-
Requires-Dist: websockets (>=8.1,<
|
|
32
|
+
Requires-Dist: websockets (>=8.1,<13.0)
|
|
32
33
|
Project-URL: Repository, https://github.com/dvd-dev/python-hilo
|
|
33
34
|
Description-Content-Type: text/markdown
|
|
34
35
|
|
|
@@ -45,6 +45,7 @@ from pyhilo.const import (
|
|
|
45
45
|
from pyhilo.device import DeviceAttribute, HiloDevice, get_device_attributes
|
|
46
46
|
from pyhilo.exceptions import InvalidCredentialsError, RequestError
|
|
47
47
|
from pyhilo.util.state import (
|
|
48
|
+
StateDict,
|
|
48
49
|
WebsocketDict,
|
|
49
50
|
WebsocketTransportsDict,
|
|
50
51
|
get_state,
|
|
@@ -75,7 +76,7 @@ class API:
|
|
|
75
76
|
self._backoff_refresh_lock_ws = asyncio.Lock()
|
|
76
77
|
self._request_retries = request_retries
|
|
77
78
|
self._state_yaml: str = DEFAULT_STATE_FILE
|
|
78
|
-
self.state =
|
|
79
|
+
self.state: StateDict = {}
|
|
79
80
|
self.async_request = self._wrap_request_method(self._request_retries)
|
|
80
81
|
self.device_attributes = get_device_attributes()
|
|
81
82
|
self.session: ClientSession = session
|
|
@@ -152,14 +153,14 @@ class API:
|
|
|
152
153
|
else attribute,
|
|
153
154
|
)
|
|
154
155
|
|
|
155
|
-
def _get_fid_state(self) -> bool:
|
|
156
|
+
async def _get_fid_state(self) -> bool:
|
|
156
157
|
"""Looks up the cached state to define the firebase attributes
|
|
157
158
|
on the API instances.
|
|
158
159
|
|
|
159
160
|
:return: Whether or not we have cached firebase state
|
|
160
161
|
:rtype: bool
|
|
161
162
|
"""
|
|
162
|
-
self.state = get_state(self._state_yaml)
|
|
163
|
+
self.state = await get_state(self._state_yaml)
|
|
163
164
|
fb_state = self.state.get("firebase", {})
|
|
164
165
|
if fb_fid := fb_state.get("fid"):
|
|
165
166
|
self._fb_fid = fb_fid
|
|
@@ -171,14 +172,14 @@ class API:
|
|
|
171
172
|
return True
|
|
172
173
|
return False
|
|
173
174
|
|
|
174
|
-
def _get_android_state(self) -> bool:
|
|
175
|
+
async def _get_android_state(self) -> bool:
|
|
175
176
|
"""Looks up the cached state to define the android device token
|
|
176
177
|
on the API instances.
|
|
177
178
|
|
|
178
179
|
:return: Whether or not we have cached android state
|
|
179
180
|
:rtype: bool
|
|
180
181
|
"""
|
|
181
|
-
self.state = get_state(self._state_yaml)
|
|
182
|
+
self.state = await get_state(self._state_yaml)
|
|
182
183
|
android_state = self.state.get("android", {})
|
|
183
184
|
if token := android_state.get("token"):
|
|
184
185
|
self._device_token = token
|
|
@@ -187,18 +188,18 @@ class API:
|
|
|
187
188
|
|
|
188
189
|
async def _get_device_token(self) -> None:
|
|
189
190
|
"""Retrieves the android token if it's not cached."""
|
|
190
|
-
if not self._get_android_state():
|
|
191
|
+
if not await self._get_android_state():
|
|
191
192
|
await self.android_register()
|
|
192
193
|
|
|
193
194
|
async def _get_fid(self) -> None:
|
|
194
195
|
"""Retrieves the firebase state if it's not cached."""
|
|
195
|
-
if not self._get_fid_state():
|
|
196
|
+
if not await self._get_fid_state():
|
|
196
197
|
self._fb_id = "".join(
|
|
197
198
|
random.SystemRandom().choice(string.ascii_letters + string.digits)
|
|
198
199
|
for _ in range(FB_ID_LEN)
|
|
199
200
|
)
|
|
200
201
|
await self.fb_install(self._fb_id)
|
|
201
|
-
self._get_fid_state()
|
|
202
|
+
await self._get_fid_state()
|
|
202
203
|
|
|
203
204
|
async def _async_request(
|
|
204
205
|
self, method: str, endpoint: str, host: str = API_HOSTNAME, **kwargs: Any
|
|
@@ -366,7 +367,7 @@ class API:
|
|
|
366
367
|
resp = await self.async_request("post", url)
|
|
367
368
|
ws_url = resp.get("url")
|
|
368
369
|
ws_token = resp.get("accessToken")
|
|
369
|
-
set_state(
|
|
370
|
+
await set_state(
|
|
370
371
|
self._state_yaml,
|
|
371
372
|
"websocket",
|
|
372
373
|
{
|
|
@@ -397,7 +398,7 @@ class API:
|
|
|
397
398
|
"available_transports": transport_dict,
|
|
398
399
|
"full_ws_url": self.full_ws_url,
|
|
399
400
|
}
|
|
400
|
-
set_state(self._state_yaml, "websocket", websocket_dict)
|
|
401
|
+
await set_state(self._state_yaml, "websocket", websocket_dict)
|
|
401
402
|
|
|
402
403
|
async def fb_install(self, fb_id: str) -> None:
|
|
403
404
|
LOG.debug("Posting firebase install")
|
|
@@ -422,7 +423,7 @@ class API:
|
|
|
422
423
|
raise RequestError(err) from err
|
|
423
424
|
LOG.debug(f"FB Install data: {resp}")
|
|
424
425
|
auth_token = resp.get("authToken", {})
|
|
425
|
-
set_state(
|
|
426
|
+
await set_state(
|
|
426
427
|
self._state_yaml,
|
|
427
428
|
"firebase",
|
|
428
429
|
{
|
|
@@ -463,7 +464,7 @@ class API:
|
|
|
463
464
|
LOG.error(f"Android registration error: {msg}")
|
|
464
465
|
raise RequestError
|
|
465
466
|
token = msg.split("=")[-1]
|
|
466
|
-
set_state(
|
|
467
|
+
await set_state(
|
|
467
468
|
self._state_yaml,
|
|
468
469
|
"android",
|
|
469
470
|
{
|
|
@@ -614,6 +615,7 @@ class API:
|
|
|
614
615
|
]
|
|
615
616
|
"""
|
|
616
617
|
url = self._get_url("Seasons", location_id, challenge=True)
|
|
618
|
+
LOG.debug(f"Seasons URL is {url}")
|
|
617
619
|
return cast(dict[str, Any], await self.async_request("get", url))
|
|
618
620
|
|
|
619
621
|
async def get_gateway(self, location_id: int) -> dict[str, Any]:
|
|
@@ -645,3 +647,22 @@ class API:
|
|
|
645
647
|
for attr in saved_attrs:
|
|
646
648
|
gw[attr] = {"value": req[0].get(attr)}
|
|
647
649
|
return gw
|
|
650
|
+
|
|
651
|
+
async def get_weather(self, location_id: int) -> dict[str, Any]:
|
|
652
|
+
"""This will return the current weather like in the app
|
|
653
|
+
https://api.hiloenergie.com/Automation/v1/api/Locations/XXXX/Weather
|
|
654
|
+
[
|
|
655
|
+
{
|
|
656
|
+
"temperature": -9.0,
|
|
657
|
+
"time":"0001-01-01T00:00:00Z",
|
|
658
|
+
"condition":"Foggy",
|
|
659
|
+
"icon":0,
|
|
660
|
+
"humidity":92.0
|
|
661
|
+
}
|
|
662
|
+
]
|
|
663
|
+
"""
|
|
664
|
+
url = self._get_url("Weather", location_id)
|
|
665
|
+
LOG.debug(f"Weather URL is {url}")
|
|
666
|
+
response = await self.async_request("get", url)
|
|
667
|
+
LOG.debug(f"Weather API response: {response}")
|
|
668
|
+
return cast(dict[str, Any], await self.async_request("get", url))
|
|
@@ -8,7 +8,7 @@ import homeassistant.core
|
|
|
8
8
|
LOG: Final = logging.getLogger(__package__)
|
|
9
9
|
DEFAULT_STATE_FILE: Final = "hilo_state.yaml"
|
|
10
10
|
REQUEST_RETRY: Final = 9
|
|
11
|
-
PYHILO_VERSION: Final = "
|
|
11
|
+
PYHILO_VERSION: Final = "2024.06.01"
|
|
12
12
|
# TODO: Find a way to keep previous line in sync with pyproject.toml automatically
|
|
13
13
|
|
|
14
14
|
CONTENT_TYPE_FORM: Final = "application/x-www-form-urlencoded"
|
|
@@ -239,6 +239,7 @@ HILO_PROVIDERS: Final = {
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
JASCO_MODELS: Final = [
|
|
242
|
+
"43080",
|
|
242
243
|
"43082",
|
|
243
244
|
"43076",
|
|
244
245
|
"43078",
|
|
@@ -247,12 +248,14 @@ JASCO_MODELS: Final = [
|
|
|
247
248
|
"9063",
|
|
248
249
|
"45678",
|
|
249
250
|
"42405",
|
|
251
|
+
"43094",
|
|
250
252
|
"43095",
|
|
251
253
|
"45853",
|
|
252
254
|
]
|
|
253
255
|
|
|
254
256
|
JASCO_OUTLETS: Final = [
|
|
255
257
|
"42405",
|
|
258
|
+
"43094",
|
|
256
259
|
"43095",
|
|
257
260
|
"43100",
|
|
258
261
|
"45853",
|
|
@@ -260,5 +263,7 @@ JASCO_OUTLETS: Final = [
|
|
|
260
263
|
|
|
261
264
|
UNMONITORED_DEVICES: Final = [
|
|
262
265
|
"43076",
|
|
266
|
+
"43080",
|
|
267
|
+
"43094",
|
|
263
268
|
"43100",
|
|
264
269
|
]
|
|
@@ -2,6 +2,7 @@ from datetime import datetime
|
|
|
2
2
|
from os.path import isfile
|
|
3
3
|
from typing import Any, Optional, Type, TypedDict, TypeVar, Union
|
|
4
4
|
|
|
5
|
+
import aiofiles
|
|
5
6
|
import ruyaml as yaml
|
|
6
7
|
|
|
7
8
|
from pyhilo.const import LOG
|
|
@@ -71,7 +72,7 @@ def __get_defaults__(cls: Type[T]) -> dict[str, Any]:
|
|
|
71
72
|
return new_dict # type: ignore
|
|
72
73
|
|
|
73
74
|
|
|
74
|
-
def get_state(state_yaml: str) -> StateDict:
|
|
75
|
+
async def get_state(state_yaml: str) -> StateDict:
|
|
75
76
|
"""Read in state yaml.
|
|
76
77
|
:param state_yaml: filename where to read the state
|
|
77
78
|
:type state_yaml: ``str``
|
|
@@ -79,13 +80,14 @@ def get_state(state_yaml: str) -> StateDict:
|
|
|
79
80
|
"""
|
|
80
81
|
if not isfile(state_yaml):
|
|
81
82
|
return __get_defaults__(StateDict) # type: ignore
|
|
82
|
-
with open(state_yaml) as yaml_file:
|
|
83
|
+
async with aiofiles.open(state_yaml, mode="r") as yaml_file:
|
|
83
84
|
LOG.debug("Loading state from yaml")
|
|
84
|
-
|
|
85
|
+
content = await yaml_file.read()
|
|
86
|
+
state_yaml_payload: StateDict = yaml.safe_load(content)
|
|
85
87
|
return state_yaml_payload
|
|
86
88
|
|
|
87
89
|
|
|
88
|
-
def set_state(
|
|
90
|
+
async def set_state(
|
|
89
91
|
state_yaml: str,
|
|
90
92
|
key: str,
|
|
91
93
|
state: Union[
|
|
@@ -101,9 +103,10 @@ def set_state(
|
|
|
101
103
|
:type state: ``StateDict``
|
|
102
104
|
:rtype: ``StateDict``
|
|
103
105
|
"""
|
|
104
|
-
current_state = get_state(state_yaml) or {}
|
|
106
|
+
current_state = await get_state(state_yaml) or {}
|
|
105
107
|
merged_state: dict[str, Any] = {key: {**current_state.get(key, {}), **state}} # type: ignore
|
|
106
108
|
new_state: dict[str, Any] = {**current_state, **merged_state}
|
|
107
|
-
with open(state_yaml, "w") as yaml_file:
|
|
109
|
+
async with aiofiles.open(state_yaml, mode="w") as yaml_file:
|
|
108
110
|
LOG.debug("Saving state to yaml file")
|
|
109
|
-
yaml.dump(new_state
|
|
111
|
+
content = yaml.dump(new_state)
|
|
112
|
+
await yaml_file.write(content)
|
|
@@ -27,7 +27,7 @@ disallow_untyped_defs = true
|
|
|
27
27
|
follow_imports = "silent"
|
|
28
28
|
ignore_missing_imports = true
|
|
29
29
|
no_implicit_optional = true
|
|
30
|
-
python_version = "3.
|
|
30
|
+
python_version = "3.11"
|
|
31
31
|
show_error_codes = true
|
|
32
32
|
strict_equality = true
|
|
33
33
|
warn_incomplete_stub = true
|
|
@@ -40,7 +40,7 @@ exclude = ".venv/.*"
|
|
|
40
40
|
|
|
41
41
|
[tool.poetry]
|
|
42
42
|
name = "python-hilo"
|
|
43
|
-
version = "2024.
|
|
43
|
+
version = "2024.6.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>"]
|
|
@@ -65,6 +65,7 @@ classifiers = [
|
|
|
65
65
|
|
|
66
66
|
[tool.poetry.dependencies]
|
|
67
67
|
aiohttp = ">=3.8.0"
|
|
68
|
+
aiofiles = ">=23.2.1"
|
|
68
69
|
aiosignal = ">=1.2.0"
|
|
69
70
|
async-timeout = ">=4.0.0"
|
|
70
71
|
attrs = ">=21.2.0"
|
|
@@ -73,7 +74,7 @@ python-dateutil = ">=2.8.2"
|
|
|
73
74
|
ruyaml = ">=0.91.0"
|
|
74
75
|
python = "^3.9.0"
|
|
75
76
|
voluptuous = ">=0.13.1"
|
|
76
|
-
websockets = ">=8.1,<
|
|
77
|
+
websockets = ">=8.1,<13.0"
|
|
77
78
|
|
|
78
79
|
[tool.poetry.dev-dependencies]
|
|
79
80
|
Sphinx = "^7.1.2"
|
|
@@ -82,7 +83,7 @@ asynctest = "^0.13.0"
|
|
|
82
83
|
pre-commit = "^3.2.2"
|
|
83
84
|
pytest = "^8.0.0"
|
|
84
85
|
pytest-aiohttp = "^1.0.4"
|
|
85
|
-
pytest-cov = "^
|
|
86
|
+
pytest-cov = "^5.0.0"
|
|
86
87
|
sphinx-rtd-theme = "^2.0.0"
|
|
87
88
|
types-pytz = "^2024.1.0"
|
|
88
89
|
|
|
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
|
|
File without changes
|