pyhive-integration 1.0.8.dev2__tar.gz → 1.0.8.dev4__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.
- {pyhive_integration-1.0.8.dev2/pyhive_integration.egg-info → pyhive_integration-1.0.8.dev4}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4/pyhive_integration.egg-info}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/setup.py +1 -1
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/api/hive_async_api.py +7 -2
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/api/hive_auth_async.py +1 -1
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/heating.py +7 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/hotwater.py +7 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/light.py +7 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/plug.py +7 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/sensor.py +7 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/session.py +36 -13
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/LICENSE +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/MANIFEST.in +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/README.md +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/pyhive_integration.egg-info/SOURCES.txt +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/pyhive_integration.egg-info/dependency_links.txt +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/pyhive_integration.egg-info/requires.txt +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/pyhive_integration.egg-info/top_level.txt +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/pyproject.toml +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/requirements.txt +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/requirements_test.txt +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/setup.cfg +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/action.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/alarm.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/api/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/api/hive_api.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/api/hive_auth.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/camera.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/device_attributes.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/helper/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/helper/const.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/helper/debugger.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/helper/hive_exceptions.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/helper/hive_helper.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/helper/hivedataclasses.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/helper/map.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/hive.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/hub.py +0 -0
- {pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/tests/test_hub.py +0 -0
|
@@ -17,7 +17,7 @@ def requirements_from_file(filename="requirements.txt"):
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
setup(
|
|
20
|
-
version="1.0.8.
|
|
20
|
+
version="1.0.8.dev4",
|
|
21
21
|
packages=["apyhiveapi", "apyhiveapi.api", "apyhiveapi.helper"],
|
|
22
22
|
package_dir={"apyhiveapi": "src"},
|
|
23
23
|
package_data={"data": ["*.json"]},
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""Hive API Module."""
|
|
2
2
|
|
|
3
3
|
# pylint: skip-file
|
|
4
|
+
import asyncio
|
|
4
5
|
import json
|
|
5
6
|
import logging
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
8
9
|
import requests
|
|
9
10
|
import urllib3
|
|
10
|
-
from aiohttp import ClientResponse, ClientSession, web_exceptions
|
|
11
|
+
from aiohttp import ClientResponse, ClientSession, ClientTimeout, web_exceptions
|
|
11
12
|
from pyquery import PyQuery
|
|
12
13
|
|
|
13
14
|
from ..helper.const import HTTP_FORBIDDEN, HTTP_UNAUTHORIZED
|
|
@@ -82,8 +83,9 @@ class HiveApiAsync:
|
|
|
82
83
|
auth_token[-4:] if len(auth_token) >= 4 else auth_token,
|
|
83
84
|
)
|
|
84
85
|
|
|
86
|
+
timeout = ClientTimeout(total=10)
|
|
85
87
|
async with self.websession.request(
|
|
86
|
-
method, url, headers=headers, data=data
|
|
88
|
+
method, url, headers=headers, data=data, timeout=timeout
|
|
87
89
|
) as resp:
|
|
88
90
|
resp_body = await resp.text()
|
|
89
91
|
if str(resp.status).startswith("20"):
|
|
@@ -163,6 +165,9 @@ class HiveApiAsync:
|
|
|
163
165
|
resp = await self.request("get", url)
|
|
164
166
|
json_return.update({"original": resp.status})
|
|
165
167
|
json_return.update({"parsed": await resp.json(content_type=None)})
|
|
168
|
+
except asyncio.TimeoutError:
|
|
169
|
+
_LOGGER.warning("Hive API request timed out fetching all nodes.")
|
|
170
|
+
raise
|
|
166
171
|
except (OSError, RuntimeError, ZeroDivisionError):
|
|
167
172
|
await self.error()
|
|
168
173
|
|
|
@@ -625,7 +625,7 @@ class HiveAuthAsync:
|
|
|
625
625
|
_LOGGER.error("Token refresh failed: cannot reach Cognito endpoint.")
|
|
626
626
|
raise HiveApiError from err
|
|
627
627
|
|
|
628
|
-
_LOGGER.debug("
|
|
628
|
+
_LOGGER.debug("Cognito token refresh completed successfully.")
|
|
629
629
|
return result
|
|
630
630
|
|
|
631
631
|
async def forget_device(self, access_token, device_key):
|
|
@@ -495,6 +495,13 @@ class Climate(HiveHeating):
|
|
|
495
495
|
await self.session.helper.errorCheck(
|
|
496
496
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
497
497
|
)
|
|
498
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
499
|
+
if cached is not None:
|
|
500
|
+
_LOGGER.debug(
|
|
501
|
+
"Returning cached state for offline climate %s.",
|
|
502
|
+
device["haName"],
|
|
503
|
+
)
|
|
504
|
+
return cached
|
|
498
505
|
return device
|
|
499
506
|
|
|
500
507
|
async def getScheduleNowNextLater(self, device: dict):
|
|
@@ -263,6 +263,13 @@ class WaterHeater(HiveHotwater):
|
|
|
263
263
|
await self.session.helper.errorCheck(
|
|
264
264
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
265
265
|
)
|
|
266
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
267
|
+
if cached is not None:
|
|
268
|
+
_LOGGER.debug(
|
|
269
|
+
"Returning cached state for offline water heater %s.",
|
|
270
|
+
device["haName"],
|
|
271
|
+
)
|
|
272
|
+
return cached
|
|
266
273
|
return device
|
|
267
274
|
|
|
268
275
|
async def getScheduleNowNextLater(self, device: dict):
|
|
@@ -420,6 +420,13 @@ class Light(HiveLight):
|
|
|
420
420
|
await self.session.helper.errorCheck(
|
|
421
421
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
422
422
|
)
|
|
423
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
424
|
+
if cached is not None:
|
|
425
|
+
_LOGGER.debug(
|
|
426
|
+
"Returning cached state for offline light %s.",
|
|
427
|
+
device["haName"],
|
|
428
|
+
)
|
|
429
|
+
return cached
|
|
423
430
|
return device
|
|
424
431
|
|
|
425
432
|
async def turnOn(self, device: dict, brightness: int, color_temp: int, color: list):
|
|
@@ -180,6 +180,13 @@ class Switch(HiveSmartPlug):
|
|
|
180
180
|
await self.session.helper.errorCheck(
|
|
181
181
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
182
182
|
)
|
|
183
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
184
|
+
if cached is not None:
|
|
185
|
+
_LOGGER.debug(
|
|
186
|
+
"Returning cached state for offline switch %s.",
|
|
187
|
+
device["haName"],
|
|
188
|
+
)
|
|
189
|
+
return cached
|
|
183
190
|
return device
|
|
184
191
|
|
|
185
192
|
async def getSwitchState(self, device: dict):
|
|
@@ -152,4 +152,11 @@ class Sensor(HiveSensor):
|
|
|
152
152
|
await self.session.helper.errorCheck(
|
|
153
153
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
154
154
|
)
|
|
155
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
156
|
+
if cached is not None:
|
|
157
|
+
_LOGGER.debug(
|
|
158
|
+
"Returning cached state for offline sensor %s.",
|
|
159
|
+
device["haName"],
|
|
160
|
+
)
|
|
161
|
+
return cached
|
|
155
162
|
return device
|
|
@@ -357,6 +357,11 @@ class HiveSession:
|
|
|
357
357
|
else:
|
|
358
358
|
expiry_time = self.tokens.tokenCreated + (self.tokens.tokenExpiry * 0.95)
|
|
359
359
|
# Refresh at 95% of token lifetime to prevent expiration during API calls
|
|
360
|
+
_LOGGER.debug(
|
|
361
|
+
"Session token expiry time ( Current: %s | Expiry: %s)",
|
|
362
|
+
datetime.now(),
|
|
363
|
+
expiry_time,
|
|
364
|
+
)
|
|
360
365
|
if datetime.now() >= expiry_time:
|
|
361
366
|
async with self._refreshLock:
|
|
362
367
|
# Re-check after acquiring lock — another caller may have already refreshed
|
|
@@ -365,10 +370,14 @@ class HiveSession:
|
|
|
365
370
|
)
|
|
366
371
|
if datetime.now() < expiry_time:
|
|
367
372
|
return result
|
|
373
|
+
actual_expiry = self.tokens.tokenCreated + self.tokens.tokenExpiry
|
|
368
374
|
_LOGGER.debug(
|
|
369
|
-
"Token
|
|
375
|
+
"Session Token created: %s | Actual expiry: %s | "
|
|
376
|
+
"Early refresh (×0.95): %s | Now: %s",
|
|
370
377
|
self.tokens.tokenCreated,
|
|
371
|
-
|
|
378
|
+
actual_expiry,
|
|
379
|
+
expiry_time,
|
|
380
|
+
datetime.now(),
|
|
372
381
|
)
|
|
373
382
|
try:
|
|
374
383
|
result = await self.auth.refresh_token(
|
|
@@ -376,11 +385,17 @@ class HiveSession:
|
|
|
376
385
|
)
|
|
377
386
|
|
|
378
387
|
if result and "AuthenticationResult" in result:
|
|
379
|
-
_LOGGER.debug("Token refresh successful.")
|
|
380
388
|
await self.updateTokens(result)
|
|
389
|
+
new_expiry = (
|
|
390
|
+
self.tokens.tokenCreated + self.tokens.tokenExpiry
|
|
391
|
+
)
|
|
392
|
+
_LOGGER.debug(
|
|
393
|
+
"Session Token refresh successful. New expiry: %s",
|
|
394
|
+
new_expiry,
|
|
395
|
+
)
|
|
381
396
|
except (HiveRefreshTokenExpired, HiveFailedToRefreshTokens) as exc:
|
|
382
397
|
_LOGGER.warning(
|
|
383
|
-
"Token refresh failed (%s), falling back to device login.",
|
|
398
|
+
"Session Token refresh failed (%s), falling back to device login.",
|
|
384
399
|
type(exc).__name__,
|
|
385
400
|
)
|
|
386
401
|
await self._retryDeviceLogin()
|
|
@@ -571,6 +586,12 @@ class HiveSession:
|
|
|
571
586
|
_LOGGER.error("Reauthentication required, propagating to caller.")
|
|
572
587
|
self.config.lastUpdate = datetime.now()
|
|
573
588
|
raise
|
|
589
|
+
except asyncio.TimeoutError:
|
|
590
|
+
_LOGGER.warning("Hive API request timed out — keeping cached device data.")
|
|
591
|
+
self.config.lastUpdate = (
|
|
592
|
+
datetime.now() - self.config.scanInterval + timedelta(seconds=30)
|
|
593
|
+
)
|
|
594
|
+
get_nodes_successful = False
|
|
574
595
|
except (
|
|
575
596
|
OSError,
|
|
576
597
|
RuntimeError,
|
|
@@ -673,15 +694,6 @@ class HiveSession:
|
|
|
673
694
|
a = self.data["actions"][action] # noqa: F841
|
|
674
695
|
eval("self." + ACTIONS)
|
|
675
696
|
|
|
676
|
-
_LOGGER.debug(
|
|
677
|
-
"Device discovery found: %d climate, %d light, %d switch, %d sensor, %d camera, %d alarm.",
|
|
678
|
-
len(self.deviceList["climate"]),
|
|
679
|
-
len(self.deviceList["light"]),
|
|
680
|
-
len(self.deviceList["switch"]),
|
|
681
|
-
len(self.deviceList["sensor"]),
|
|
682
|
-
len(self.deviceList["camera"]),
|
|
683
|
-
len(self.deviceList["alarm_control_panel"]),
|
|
684
|
-
)
|
|
685
697
|
hive_type = HIVE_TYPES["Heating"] + HIVE_TYPES["Switch"] + HIVE_TYPES["Light"]
|
|
686
698
|
for aProduct in self.data.products:
|
|
687
699
|
p = self.data.products[aProduct]
|
|
@@ -704,6 +716,17 @@ class HiveSession:
|
|
|
704
716
|
if self.data.products[aProduct]["type"] in hive_type:
|
|
705
717
|
self.config.mode.append(p["id"])
|
|
706
718
|
|
|
719
|
+
_LOGGER.debug(
|
|
720
|
+
"Device discovery found: %d parent, %d binary_sensor, %d climate, %d light, %d sensor, %d switch, %d water_heater",
|
|
721
|
+
len(self.deviceList.get("parent", [])),
|
|
722
|
+
len(self.deviceList.get("binary_sensor", [])),
|
|
723
|
+
len(self.deviceList.get("climate", [])),
|
|
724
|
+
len(self.deviceList.get("light", [])),
|
|
725
|
+
len(self.deviceList.get("sensor", [])),
|
|
726
|
+
len(self.deviceList.get("switch", [])),
|
|
727
|
+
len(self.deviceList.get("water_heater", [])),
|
|
728
|
+
)
|
|
729
|
+
|
|
707
730
|
return self.deviceList
|
|
708
731
|
|
|
709
732
|
@staticmethod
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/helper/hive_exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyhive_integration-1.0.8.dev2 → pyhive_integration-1.0.8.dev4}/src/helper/hivedataclasses.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|