pyhive-integration 1.0.8.dev3__tar.gz → 1.0.8.dev5__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.dev3/pyhive_integration.egg-info → pyhive_integration-1.0.8.dev5}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5/pyhive_integration.egg-info}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/setup.py +1 -1
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/api/hive_async_api.py +17 -3
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/api/hive_auth_async.py +1 -1
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/heating.py +8 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/hotwater.py +8 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/light.py +8 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/plug.py +8 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/sensor.py +8 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/session.py +39 -13
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/LICENSE +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/MANIFEST.in +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/README.md +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/pyhive_integration.egg-info/SOURCES.txt +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/pyhive_integration.egg-info/dependency_links.txt +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/pyhive_integration.egg-info/requires.txt +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/pyhive_integration.egg-info/top_level.txt +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/pyproject.toml +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/requirements.txt +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/requirements_test.txt +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/setup.cfg +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/action.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/alarm.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/api/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/api/hive_api.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/api/hive_auth.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/camera.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/device_attributes.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/helper/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/helper/const.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/helper/debugger.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/helper/hive_exceptions.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/helper/hive_helper.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/helper/hivedataclasses.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/helper/map.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/hive.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/hub.py +0 -0
- {pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/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.dev5",
|
|
21
21
|
packages=["apyhiveapi", "apyhiveapi.api", "apyhiveapi.helper"],
|
|
22
22
|
package_dir={"apyhiveapi": "src"},
|
|
23
23
|
package_data={"data": ["*.json"]},
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"""Hive API Module."""
|
|
2
2
|
|
|
3
3
|
# pylint: skip-file
|
|
4
|
+
import asyncio
|
|
4
5
|
import json
|
|
5
6
|
import logging
|
|
7
|
+
import time
|
|
6
8
|
from typing import Optional
|
|
7
9
|
|
|
8
10
|
import requests
|
|
9
11
|
import urllib3
|
|
10
|
-
from aiohttp import ClientResponse, ClientSession, web_exceptions
|
|
12
|
+
from aiohttp import ClientResponse, ClientSession, ClientTimeout, web_exceptions
|
|
11
13
|
from pyquery import PyQuery
|
|
12
14
|
|
|
13
15
|
from ..helper.const import HTTP_FORBIDDEN, HTTP_UNAUTHORIZED
|
|
@@ -82,12 +84,21 @@ class HiveApiAsync:
|
|
|
82
84
|
auth_token[-4:] if len(auth_token) >= 4 else auth_token,
|
|
83
85
|
)
|
|
84
86
|
|
|
87
|
+
timeout = ClientTimeout(total=10)
|
|
88
|
+
req_start = time.monotonic()
|
|
85
89
|
async with self.websession.request(
|
|
86
|
-
method, url, headers=headers, data=data
|
|
90
|
+
method, url, headers=headers, data=data, timeout=timeout
|
|
87
91
|
) as resp:
|
|
88
92
|
resp_body = await resp.text()
|
|
93
|
+
req_duration = time.monotonic() - req_start
|
|
94
|
+
_LOGGER.debug(
|
|
95
|
+
"API %s %s completed in %.2fs — HTTP %s",
|
|
96
|
+
method.upper(),
|
|
97
|
+
url,
|
|
98
|
+
req_duration,
|
|
99
|
+
resp.status,
|
|
100
|
+
)
|
|
89
101
|
if str(resp.status).startswith("20"):
|
|
90
|
-
_LOGGER.debug("API response %s from %s", resp.status, url)
|
|
91
102
|
return resp
|
|
92
103
|
|
|
93
104
|
if resp.status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN):
|
|
@@ -163,6 +174,9 @@ class HiveApiAsync:
|
|
|
163
174
|
resp = await self.request("get", url)
|
|
164
175
|
json_return.update({"original": resp.status})
|
|
165
176
|
json_return.update({"parsed": await resp.json(content_type=None)})
|
|
177
|
+
except asyncio.TimeoutError:
|
|
178
|
+
_LOGGER.warning("Hive API request timed out fetching all nodes.")
|
|
179
|
+
raise
|
|
166
180
|
except (OSError, RuntimeError, ZeroDivisionError):
|
|
167
181
|
await self.error()
|
|
168
182
|
|
|
@@ -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,14 @@ class Climate(HiveHeating):
|
|
|
495
495
|
await self.session.helper.errorCheck(
|
|
496
496
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
497
497
|
)
|
|
498
|
+
if self.session._lastPollSlow:
|
|
499
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
500
|
+
if cached is not None:
|
|
501
|
+
_LOGGER.debug(
|
|
502
|
+
"Returning cached state for offline climate %s (slow poll).",
|
|
503
|
+
device["haName"],
|
|
504
|
+
)
|
|
505
|
+
return cached
|
|
498
506
|
return device
|
|
499
507
|
|
|
500
508
|
async def getScheduleNowNextLater(self, device: dict):
|
|
@@ -263,6 +263,14 @@ class WaterHeater(HiveHotwater):
|
|
|
263
263
|
await self.session.helper.errorCheck(
|
|
264
264
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
265
265
|
)
|
|
266
|
+
if self.session._lastPollSlow:
|
|
267
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
268
|
+
if cached is not None:
|
|
269
|
+
_LOGGER.debug(
|
|
270
|
+
"Returning cached state for offline water heater %s (slow poll).",
|
|
271
|
+
device["haName"],
|
|
272
|
+
)
|
|
273
|
+
return cached
|
|
266
274
|
return device
|
|
267
275
|
|
|
268
276
|
async def getScheduleNowNextLater(self, device: dict):
|
|
@@ -420,6 +420,14 @@ class Light(HiveLight):
|
|
|
420
420
|
await self.session.helper.errorCheck(
|
|
421
421
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
422
422
|
)
|
|
423
|
+
if self.session._lastPollSlow:
|
|
424
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
425
|
+
if cached is not None:
|
|
426
|
+
_LOGGER.debug(
|
|
427
|
+
"Returning cached state for light %s (slow poll).",
|
|
428
|
+
device["haName"],
|
|
429
|
+
)
|
|
430
|
+
return cached
|
|
423
431
|
return device
|
|
424
432
|
|
|
425
433
|
async def turnOn(self, device: dict, brightness: int, color_temp: int, color: list):
|
|
@@ -180,6 +180,14 @@ class Switch(HiveSmartPlug):
|
|
|
180
180
|
await self.session.helper.errorCheck(
|
|
181
181
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
182
182
|
)
|
|
183
|
+
if self.session._lastPollSlow:
|
|
184
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
185
|
+
if cached is not None:
|
|
186
|
+
_LOGGER.debug(
|
|
187
|
+
"Returning cached state for offline switch %s (slow poll).",
|
|
188
|
+
device["haName"],
|
|
189
|
+
)
|
|
190
|
+
return cached
|
|
183
191
|
return device
|
|
184
192
|
|
|
185
193
|
async def getSwitchState(self, device: dict):
|
|
@@ -152,4 +152,12 @@ class Sensor(HiveSensor):
|
|
|
152
152
|
await self.session.helper.errorCheck(
|
|
153
153
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
154
154
|
)
|
|
155
|
+
if self.session._lastPollSlow:
|
|
156
|
+
cached = self.session.devices.get(device["hiveID"])
|
|
157
|
+
if cached is not None:
|
|
158
|
+
_LOGGER.debug(
|
|
159
|
+
"Returning cached state for offline sensor %s (slow poll).",
|
|
160
|
+
device["haName"],
|
|
161
|
+
)
|
|
162
|
+
return cached
|
|
155
163
|
return device
|
|
@@ -107,6 +107,7 @@ class HiveSession:
|
|
|
107
107
|
self.devices = {}
|
|
108
108
|
self.deviceList = {}
|
|
109
109
|
self.hub_id = None
|
|
110
|
+
self._lastPollSlow = False
|
|
110
111
|
|
|
111
112
|
def openFile(self, file: str):
|
|
112
113
|
"""Open a file.
|
|
@@ -358,7 +359,7 @@ class HiveSession:
|
|
|
358
359
|
expiry_time = self.tokens.tokenCreated + (self.tokens.tokenExpiry * 0.95)
|
|
359
360
|
# Refresh at 95% of token lifetime to prevent expiration during API calls
|
|
360
361
|
_LOGGER.debug(
|
|
361
|
-
"
|
|
362
|
+
"Session token expiry time ( Current: %s | Expiry: %s)",
|
|
362
363
|
datetime.now(),
|
|
363
364
|
expiry_time,
|
|
364
365
|
)
|
|
@@ -372,7 +373,7 @@ class HiveSession:
|
|
|
372
373
|
return result
|
|
373
374
|
actual_expiry = self.tokens.tokenCreated + self.tokens.tokenExpiry
|
|
374
375
|
_LOGGER.debug(
|
|
375
|
-
"Token created: %s | Actual expiry: %s | "
|
|
376
|
+
"Session Token created: %s | Actual expiry: %s | "
|
|
376
377
|
"Early refresh (×0.95): %s | Now: %s",
|
|
377
378
|
self.tokens.tokenCreated,
|
|
378
379
|
actual_expiry,
|
|
@@ -385,11 +386,17 @@ class HiveSession:
|
|
|
385
386
|
)
|
|
386
387
|
|
|
387
388
|
if result and "AuthenticationResult" in result:
|
|
388
|
-
_LOGGER.debug("Token refresh successful.")
|
|
389
389
|
await self.updateTokens(result)
|
|
390
|
+
new_expiry = (
|
|
391
|
+
self.tokens.tokenCreated + self.tokens.tokenExpiry
|
|
392
|
+
)
|
|
393
|
+
_LOGGER.debug(
|
|
394
|
+
"Session Token refresh successful. New expiry: %s",
|
|
395
|
+
new_expiry,
|
|
396
|
+
)
|
|
390
397
|
except (HiveRefreshTokenExpired, HiveFailedToRefreshTokens) as exc:
|
|
391
398
|
_LOGGER.warning(
|
|
392
|
-
"Token refresh failed (%s), falling back to device login.",
|
|
399
|
+
"Session Token refresh failed (%s), falling back to device login.",
|
|
393
400
|
type(exc).__name__,
|
|
394
401
|
)
|
|
395
402
|
await self._retryDeviceLogin()
|
|
@@ -519,6 +526,7 @@ class HiveSession:
|
|
|
519
526
|
elif self.tokens is not None:
|
|
520
527
|
await self.hiveRefreshTokens()
|
|
521
528
|
_LOGGER.debug("Fetching all devices from Hive API.")
|
|
529
|
+
api_call_start = time.monotonic()
|
|
522
530
|
try:
|
|
523
531
|
api_resp_d = await self.api.getAll()
|
|
524
532
|
except HiveAuthError:
|
|
@@ -531,6 +539,15 @@ class HiveSession:
|
|
|
531
539
|
api_resp_d = await self.api.getAll()
|
|
532
540
|
except HiveAuthError as retry_err:
|
|
533
541
|
raise HiveReauthRequired from retry_err
|
|
542
|
+
api_call_duration = time.monotonic() - api_call_start
|
|
543
|
+
if api_call_duration > 8:
|
|
544
|
+
_LOGGER.warning(
|
|
545
|
+
"Hive API response took %.1fs — marking poll as slow.",
|
|
546
|
+
api_call_duration,
|
|
547
|
+
)
|
|
548
|
+
self._lastPollSlow = True
|
|
549
|
+
else:
|
|
550
|
+
self._lastPollSlow = False
|
|
534
551
|
if operator.contains(str(api_resp_d["original"]), "20") is False:
|
|
535
552
|
raise HTTPException
|
|
536
553
|
elif api_resp_d["parsed"] is None:
|
|
@@ -580,6 +597,13 @@ class HiveSession:
|
|
|
580
597
|
_LOGGER.error("Reauthentication required, propagating to caller.")
|
|
581
598
|
self.config.lastUpdate = datetime.now()
|
|
582
599
|
raise
|
|
600
|
+
except asyncio.TimeoutError:
|
|
601
|
+
_LOGGER.warning("Hive API request timed out — keeping cached device data.")
|
|
602
|
+
self._lastPollSlow = True
|
|
603
|
+
self.config.lastUpdate = (
|
|
604
|
+
datetime.now() - self.config.scanInterval + timedelta(seconds=30)
|
|
605
|
+
)
|
|
606
|
+
get_nodes_successful = False
|
|
583
607
|
except (
|
|
584
608
|
OSError,
|
|
585
609
|
RuntimeError,
|
|
@@ -682,15 +706,6 @@ class HiveSession:
|
|
|
682
706
|
a = self.data["actions"][action] # noqa: F841
|
|
683
707
|
eval("self." + ACTIONS)
|
|
684
708
|
|
|
685
|
-
_LOGGER.debug(
|
|
686
|
-
"Device discovery found: %d climate, %d light, %d switch, %d sensor, %d camera, %d alarm.",
|
|
687
|
-
len(self.deviceList["climate"]),
|
|
688
|
-
len(self.deviceList["light"]),
|
|
689
|
-
len(self.deviceList["switch"]),
|
|
690
|
-
len(self.deviceList["sensor"]),
|
|
691
|
-
len(self.deviceList["camera"]),
|
|
692
|
-
len(self.deviceList["alarm_control_panel"]),
|
|
693
|
-
)
|
|
694
709
|
hive_type = HIVE_TYPES["Heating"] + HIVE_TYPES["Switch"] + HIVE_TYPES["Light"]
|
|
695
710
|
for aProduct in self.data.products:
|
|
696
711
|
p = self.data.products[aProduct]
|
|
@@ -713,6 +728,17 @@ class HiveSession:
|
|
|
713
728
|
if self.data.products[aProduct]["type"] in hive_type:
|
|
714
729
|
self.config.mode.append(p["id"])
|
|
715
730
|
|
|
731
|
+
_LOGGER.debug(
|
|
732
|
+
"Device discovery found: %d parent, %d binary_sensor, %d climate, %d light, %d sensor, %d switch, %d water_heater",
|
|
733
|
+
len(self.deviceList.get("parent", [])),
|
|
734
|
+
len(self.deviceList.get("binary_sensor", [])),
|
|
735
|
+
len(self.deviceList.get("climate", [])),
|
|
736
|
+
len(self.deviceList.get("light", [])),
|
|
737
|
+
len(self.deviceList.get("sensor", [])),
|
|
738
|
+
len(self.deviceList.get("switch", [])),
|
|
739
|
+
len(self.deviceList.get("water_heater", [])),
|
|
740
|
+
)
|
|
741
|
+
|
|
716
742
|
return self.deviceList
|
|
717
743
|
|
|
718
744
|
@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.dev3 → pyhive_integration-1.0.8.dev5}/src/helper/hive_exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyhive_integration-1.0.8.dev3 → pyhive_integration-1.0.8.dev5}/src/helper/hivedataclasses.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|