pyhive-integration 1.0.8.dev10__tar.gz → 1.0.9.dev1__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.dev10/pyhive_integration.egg-info → pyhive_integration-1.0.9.dev1}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1/pyhive_integration.egg-info}/PKG-INFO +1 -1
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/setup.py +1 -1
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/action.py +9 -2
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/alarm.py +9 -2
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/api/hive_api.py +81 -16
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/api/hive_async_api.py +1 -2
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/api/hive_auth.py +11 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/api/hive_auth_async.py +60 -17
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/camera.py +12 -5
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/heating.py +87 -24
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/helper/hive_helper.py +123 -30
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/hive.py +1 -1
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/hotwater.py +27 -14
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/light.py +89 -33
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/plug.py +18 -13
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/sensor.py +16 -11
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/session.py +196 -45
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/LICENSE +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/MANIFEST.in +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/README.md +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/pyhive_integration.egg-info/SOURCES.txt +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/pyhive_integration.egg-info/dependency_links.txt +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/pyhive_integration.egg-info/requires.txt +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/pyhive_integration.egg-info/top_level.txt +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/pyproject.toml +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/requirements.txt +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/requirements_test.txt +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/setup.cfg +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/api/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/device_attributes.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/helper/__init__.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/helper/const.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/helper/debugger.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/helper/hive_exceptions.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/helper/hivedataclasses.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/helper/map.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/src/hub.py +0 -0
- {pyhive_integration-1.0.8.dev10 → pyhive_integration-1.0.9.dev1}/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.
|
|
20
|
+
version="1.0.9.dev1",
|
|
21
21
|
packages=["apyhiveapi", "apyhiveapi.api", "apyhiveapi.helper"],
|
|
22
22
|
package_dir={"apyhiveapi": "src"},
|
|
23
23
|
package_data={"data": ["*.json"]},
|
|
@@ -32,6 +32,14 @@ class HiveAction:
|
|
|
32
32
|
Returns:
|
|
33
33
|
dict: Updated device.
|
|
34
34
|
"""
|
|
35
|
+
if self.session.shouldUseCachedData():
|
|
36
|
+
cached = self.session.getCachedDevice(device)
|
|
37
|
+
if cached is not None:
|
|
38
|
+
_LOGGER.debug(
|
|
39
|
+
"Returning cached state for action %s (slow/busy poll).",
|
|
40
|
+
device["haName"],
|
|
41
|
+
)
|
|
42
|
+
return cached
|
|
35
43
|
dev_data = {}
|
|
36
44
|
|
|
37
45
|
if device["hiveID"] in self.data["action"]:
|
|
@@ -47,8 +55,7 @@ class HiveAction:
|
|
|
47
55
|
"custom": device.get("custom", None),
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
self.session.
|
|
51
|
-
return self.session.devices[device["hiveID"]]
|
|
58
|
+
return self.session.setCachedDevice(device, dev_data)
|
|
52
59
|
else:
|
|
53
60
|
exists = self.session.data.actions.get("hiveID", False)
|
|
54
61
|
if exists is False:
|
|
@@ -96,6 +96,14 @@ class Alarm(HiveHomeShield):
|
|
|
96
96
|
Returns:
|
|
97
97
|
dict: Updated device.
|
|
98
98
|
"""
|
|
99
|
+
if self.session.shouldUseCachedData():
|
|
100
|
+
cached = self.session.getCachedDevice(device)
|
|
101
|
+
if cached is not None:
|
|
102
|
+
_LOGGER.debug(
|
|
103
|
+
"Returning cached state for alarm %s (slow/busy poll).",
|
|
104
|
+
device["haName"],
|
|
105
|
+
)
|
|
106
|
+
return cached
|
|
99
107
|
device["deviceData"].update(
|
|
100
108
|
{"online": await self.session.attr.onlineOffline(device["device_id"])}
|
|
101
109
|
)
|
|
@@ -125,8 +133,7 @@ class Alarm(HiveHomeShield):
|
|
|
125
133
|
),
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
self.session.
|
|
129
|
-
return self.session.devices[device["hiveID"]]
|
|
136
|
+
return self.session.setCachedDevice(device, dev_data)
|
|
130
137
|
else:
|
|
131
138
|
await self.session.helper.errorCheck(
|
|
132
139
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# pylint: skip-file
|
|
4
4
|
import json
|
|
5
|
+
import logging
|
|
5
6
|
|
|
6
7
|
import requests
|
|
7
8
|
import urllib3
|
|
@@ -9,6 +10,8 @@ from pyquery import PyQuery
|
|
|
9
10
|
|
|
10
11
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
11
12
|
|
|
13
|
+
_LOGGER = logging.getLogger(__name__)
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
class HiveApi:
|
|
14
17
|
"""Hive API Code."""
|
|
@@ -43,6 +46,10 @@ class HiveApi:
|
|
|
43
46
|
|
|
44
47
|
def request(self, type, url, jsc=None, camera=False):
|
|
45
48
|
"""Make API request."""
|
|
49
|
+
_LOGGER.debug("request - Making %s request to: %s", type, url)
|
|
50
|
+
if jsc:
|
|
51
|
+
_LOGGER.debug("request - Request payload: %s", jsc)
|
|
52
|
+
|
|
46
53
|
if self.session is not None:
|
|
47
54
|
if camera:
|
|
48
55
|
self.headers = {
|
|
@@ -72,17 +79,27 @@ class HiveApi:
|
|
|
72
79
|
"authorization": self.token,
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
_LOGGER.debug(
|
|
83
|
+
"request - Request headers: %s",
|
|
84
|
+
{k: v for k, v in self.headers.items() if k.lower() != "authorization"},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
if type == "GET":
|
|
89
|
+
return requests.get(
|
|
90
|
+
url=url, headers=self.headers, data=jsc, timeout=self.timeout
|
|
91
|
+
)
|
|
92
|
+
if type == "POST":
|
|
93
|
+
return requests.post(
|
|
94
|
+
url=url, headers=self.headers, data=jsc, timeout=self.timeout
|
|
95
|
+
)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
_LOGGER.error("Request failed: %s", e)
|
|
98
|
+
raise
|
|
83
99
|
|
|
84
100
|
def refreshTokens(self, tokens={}):
|
|
85
101
|
"""Get new session tokens - DEPRECATED NOW BY AWS TOKEN MANAGEMENT."""
|
|
102
|
+
_LOGGER.debug("refreshTokens - Attempting token refresh (deprecated method)")
|
|
86
103
|
url = self.urls["refresh"]
|
|
87
104
|
if self.session is not None:
|
|
88
105
|
tokens = self.session.tokens.tokenData
|
|
@@ -97,21 +114,31 @@ class HiveApi:
|
|
|
97
114
|
info = self.request("POST", url, jsc)
|
|
98
115
|
data = json.loads(info.text)
|
|
99
116
|
if "token" in data and self.session:
|
|
117
|
+
_LOGGER.debug(
|
|
118
|
+
"refreshTokens - Token refresh successful, updating session"
|
|
119
|
+
)
|
|
100
120
|
self.session.updateTokens(data)
|
|
101
121
|
self.urls.update({"base": data["platform"]["endpoint"]})
|
|
102
122
|
self.urls.update({"camera": data["platform"]["cameraPlatform"]})
|
|
103
123
|
self.json_return.update({"original": info.status_code})
|
|
104
124
|
self.json_return.update({"parsed": info.json()})
|
|
105
|
-
except (OSError, RuntimeError, ZeroDivisionError):
|
|
125
|
+
except (OSError, RuntimeError, ZeroDivisionError, json.JSONDecodeError) as e:
|
|
126
|
+
_LOGGER.error("Token refresh failed: %s", str(e))
|
|
106
127
|
self.error()
|
|
107
128
|
|
|
108
129
|
return self.json_return
|
|
109
130
|
|
|
110
131
|
def getLoginInfo(self):
|
|
111
132
|
"""Get login properties to make the login request."""
|
|
133
|
+
_LOGGER.debug(
|
|
134
|
+
"getLoginInfo - Fetching login info from: %s", self.urls["properties"]
|
|
135
|
+
)
|
|
112
136
|
url = self.urls["properties"]
|
|
113
137
|
try:
|
|
114
138
|
data = requests.get(url=url, verify=False, timeout=self.timeout)
|
|
139
|
+
_LOGGER.debug(
|
|
140
|
+
"getLoginInfo - Login info response status: %s", data.status_code
|
|
141
|
+
)
|
|
115
142
|
html = PyQuery(data.content)
|
|
116
143
|
json_data = json.loads(
|
|
117
144
|
'{"'
|
|
@@ -126,19 +153,35 @@ class HiveApi:
|
|
|
126
153
|
loginData.update({"UPID": json_data["HiveSSOPoolId"]})
|
|
127
154
|
loginData.update({"CLIID": json_data["HiveSSOPublicCognitoClientId"]})
|
|
128
155
|
loginData.update({"REGION": json_data["HiveSSOPoolId"]})
|
|
156
|
+
_LOGGER.debug("getLoginInfo - Login info extracted successfully")
|
|
129
157
|
return loginData
|
|
130
|
-
except (
|
|
158
|
+
except (
|
|
159
|
+
OSError,
|
|
160
|
+
RuntimeError,
|
|
161
|
+
ZeroDivisionError,
|
|
162
|
+
json.JSONDecodeError,
|
|
163
|
+
KeyError,
|
|
164
|
+
) as e:
|
|
165
|
+
_LOGGER.error("Failed to get login info: %s", str(e))
|
|
131
166
|
self.error()
|
|
132
167
|
|
|
133
168
|
def getAll(self):
|
|
134
169
|
"""Build and query all endpoint."""
|
|
170
|
+
_LOGGER.debug("getAll - Fetching all devices/products/actions from Hive API")
|
|
135
171
|
json_return = {}
|
|
136
172
|
url = self.urls["base"] + self.urls["all"]
|
|
137
173
|
try:
|
|
138
174
|
info = self.request("GET", url)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
175
|
+
if info is not None:
|
|
176
|
+
json_return.update({"original": info.status_code})
|
|
177
|
+
json_return.update({"parsed": info.json()})
|
|
178
|
+
_LOGGER.debug(
|
|
179
|
+
"getAll - All data fetch successful, status: %s", info.status_code
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
_LOGGER.error("Failed to get response from all endpoint")
|
|
183
|
+
except (OSError, RuntimeError, ZeroDivisionError, json.JSONDecodeError) as e:
|
|
184
|
+
_LOGGER.error("Failed to fetch all data: %s", str(e))
|
|
142
185
|
self.error()
|
|
143
186
|
|
|
144
187
|
return json_return
|
|
@@ -259,6 +302,12 @@ class HiveApi:
|
|
|
259
302
|
|
|
260
303
|
def setState(self, n_type, n_id, **kwargs):
|
|
261
304
|
"""Set the state of a Device."""
|
|
305
|
+
_LOGGER.debug(
|
|
306
|
+
"setState - Setting state for device %s (type: %s): %s",
|
|
307
|
+
n_id,
|
|
308
|
+
n_type,
|
|
309
|
+
kwargs,
|
|
310
|
+
)
|
|
262
311
|
jsc = (
|
|
263
312
|
"{"
|
|
264
313
|
+ ",".join(
|
|
@@ -271,9 +320,24 @@ class HiveApi:
|
|
|
271
320
|
|
|
272
321
|
try:
|
|
273
322
|
response = self.request("POST", url, jsc)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
323
|
+
if response is not None:
|
|
324
|
+
self.json_return.update({"original": response.status_code})
|
|
325
|
+
self.json_return.update({"parsed": response.json()})
|
|
326
|
+
_LOGGER.debug(
|
|
327
|
+
"setState - State set successfully for %s, status: %s",
|
|
328
|
+
n_id,
|
|
329
|
+
response.status_code,
|
|
330
|
+
)
|
|
331
|
+
else:
|
|
332
|
+
_LOGGER.error("Failed to set state for %s - no response", n_id)
|
|
333
|
+
except (
|
|
334
|
+
OSError,
|
|
335
|
+
RuntimeError,
|
|
336
|
+
ZeroDivisionError,
|
|
337
|
+
ConnectionError,
|
|
338
|
+
json.JSONDecodeError,
|
|
339
|
+
) as e:
|
|
340
|
+
_LOGGER.error("Failed to set state for %s: %s", n_id, str(e))
|
|
277
341
|
self.error()
|
|
278
342
|
|
|
279
343
|
return self.json_return
|
|
@@ -293,6 +357,7 @@ class HiveApi:
|
|
|
293
357
|
|
|
294
358
|
def error(self):
|
|
295
359
|
"""An error has occurred interacting with the Hive API."""
|
|
360
|
+
_LOGGER.error("API error occurred - returning error response")
|
|
296
361
|
self.json_return.update({"original": "Error making API call"})
|
|
297
362
|
self.json_return.update({"parsed": "Error making API call"})
|
|
298
363
|
|
|
@@ -169,7 +169,6 @@ class HiveApiAsync:
|
|
|
169
169
|
"""Build and query all endpoint."""
|
|
170
170
|
json_return = {}
|
|
171
171
|
url = self.urls["all"]
|
|
172
|
-
_LOGGER.debug("Fetching all nodes from Hive API.")
|
|
173
172
|
try:
|
|
174
173
|
resp = await self.request("get", url)
|
|
175
174
|
json_return.update({"original": resp.status})
|
|
@@ -303,7 +302,7 @@ class HiveApiAsync:
|
|
|
303
302
|
|
|
304
303
|
async def setState(self, n_type, n_id, **kwargs):
|
|
305
304
|
"""Set the state of a Device."""
|
|
306
|
-
_LOGGER.debug("Setting state for %s/%s: %s", n_type, n_id, kwargs)
|
|
305
|
+
_LOGGER.debug("setState - Setting state for %s/%s: %s", n_type, n_id, kwargs)
|
|
307
306
|
json_return = {}
|
|
308
307
|
jsc = (
|
|
309
308
|
"{"
|
|
@@ -18,6 +18,7 @@ from ..helper.hive_exceptions import (
|
|
|
18
18
|
HiveInvalidDeviceAuthentication,
|
|
19
19
|
HiveInvalidPassword,
|
|
20
20
|
HiveInvalidUsername,
|
|
21
|
+
HiveReauthRequired,
|
|
21
22
|
)
|
|
22
23
|
from .hive_api import HiveApi
|
|
23
24
|
|
|
@@ -383,6 +384,12 @@ class HiveAuth:
|
|
|
383
384
|
def device_login(self):
|
|
384
385
|
"""Perform device login instead."""
|
|
385
386
|
login_result = self.login()
|
|
387
|
+
|
|
388
|
+
if "AuthenticationResult" in login_result:
|
|
389
|
+
# Login succeeded without a device challenge (e.g. device tracking
|
|
390
|
+
# not enforced). Tokens are already valid, return them directly.
|
|
391
|
+
return login_result
|
|
392
|
+
|
|
386
393
|
auth_params = self.get_auth_params()
|
|
387
394
|
auth_params["DEVICE_KEY"] = self.device_key
|
|
388
395
|
|
|
@@ -405,6 +412,10 @@ class HiveAuth:
|
|
|
405
412
|
except botocore.exceptions.EndpointConnectionError as err:
|
|
406
413
|
if err.__class__.__name__ == "EndpointConnectionError":
|
|
407
414
|
raise HiveApiError from err
|
|
415
|
+
elif login_result.get("ChallengeName") == self.SMS_MFA_CHALLENGE:
|
|
416
|
+
# Account has 2FA enabled and device is not remembered by Cognito.
|
|
417
|
+
# Automatic re-authentication is not possible without user interaction.
|
|
418
|
+
raise HiveReauthRequired
|
|
408
419
|
else:
|
|
409
420
|
raise HiveInvalidDeviceAuthentication
|
|
410
421
|
|
|
@@ -23,6 +23,7 @@ from ..helper.hive_exceptions import (
|
|
|
23
23
|
HiveInvalidDeviceAuthentication,
|
|
24
24
|
HiveInvalidPassword,
|
|
25
25
|
HiveInvalidUsername,
|
|
26
|
+
HiveReauthRequired,
|
|
26
27
|
HiveRefreshTokenExpired,
|
|
27
28
|
)
|
|
28
29
|
from .hive_api import HiveApi
|
|
@@ -182,8 +183,9 @@ class HiveAuthAsync:
|
|
|
182
183
|
)
|
|
183
184
|
return hkdf
|
|
184
185
|
|
|
185
|
-
async def get_auth_params(self):
|
|
186
|
+
async def get_auth_params(self, is_device_login=False):
|
|
186
187
|
"""Get auth params."""
|
|
188
|
+
_LOGGER.debug("get_auth_params - Getting auth params")
|
|
187
189
|
auth_params = {
|
|
188
190
|
"USERNAME": self.username,
|
|
189
191
|
"SRP_A": long_to_hex(self.large_a_value),
|
|
@@ -196,6 +198,11 @@ class HiveAuthAsync:
|
|
|
196
198
|
)
|
|
197
199
|
}
|
|
198
200
|
)
|
|
201
|
+
|
|
202
|
+
if is_device_login:
|
|
203
|
+
auth_params["DEVICE_KEY"] = self.device_key
|
|
204
|
+
|
|
205
|
+
_LOGGER.debug("Auth params: %s", auth_params)
|
|
199
206
|
return auth_params
|
|
200
207
|
|
|
201
208
|
@staticmethod
|
|
@@ -357,7 +364,7 @@ class HiveAuthAsync:
|
|
|
357
364
|
async def login(self):
|
|
358
365
|
"""Login into a Hive account."""
|
|
359
366
|
if self.use_file:
|
|
360
|
-
_LOGGER.debug("Using file-based authentication.")
|
|
367
|
+
_LOGGER.debug("login - Using file-based authentication.")
|
|
361
368
|
return self.file_response
|
|
362
369
|
|
|
363
370
|
if self.client is None:
|
|
@@ -365,7 +372,7 @@ class HiveAuthAsync:
|
|
|
365
372
|
auth_params = await self.get_auth_params()
|
|
366
373
|
response = None
|
|
367
374
|
result = None
|
|
368
|
-
_LOGGER.debug("Initiating SRP auth with Cognito.")
|
|
375
|
+
_LOGGER.debug("login - Initiating SRP auth with Cognito.")
|
|
369
376
|
try:
|
|
370
377
|
response = await self.loop.run_in_executor(
|
|
371
378
|
None,
|
|
@@ -386,7 +393,7 @@ class HiveAuthAsync:
|
|
|
386
393
|
raise HiveApiError from err
|
|
387
394
|
|
|
388
395
|
if response["ChallengeName"] == self.PASSWORD_VERIFIER_CHALLENGE:
|
|
389
|
-
_LOGGER.debug("Processing PASSWORD_VERIFIER challenge.")
|
|
396
|
+
_LOGGER.debug("login - Processing PASSWORD_VERIFIER challenge.")
|
|
390
397
|
challenge_response = await self.process_challenge(
|
|
391
398
|
response["ChallengeParameters"]
|
|
392
399
|
)
|
|
@@ -416,7 +423,19 @@ class HiveAuthAsync:
|
|
|
416
423
|
)
|
|
417
424
|
raise HiveApiError from err
|
|
418
425
|
|
|
419
|
-
|
|
426
|
+
if (
|
|
427
|
+
"AuthenticationResult" in result
|
|
428
|
+
and "NewDeviceMetadata" in result["AuthenticationResult"]
|
|
429
|
+
):
|
|
430
|
+
self.access_token = result["AuthenticationResult"]["AccessToken"]
|
|
431
|
+
self.device_group_key = result["AuthenticationResult"][
|
|
432
|
+
"NewDeviceMetadata"
|
|
433
|
+
]["DeviceGroupKey"]
|
|
434
|
+
self.device_key = result["AuthenticationResult"]["NewDeviceMetadata"][
|
|
435
|
+
"DeviceKey"
|
|
436
|
+
]
|
|
437
|
+
|
|
438
|
+
_LOGGER.debug("login - SRP auth challenge completed successfully.")
|
|
420
439
|
return result
|
|
421
440
|
challenge_name = response["ChallengeName"]
|
|
422
441
|
_LOGGER.error("Unsupported Cognito challenge: %s", challenge_name)
|
|
@@ -424,13 +443,23 @@ class HiveAuthAsync:
|
|
|
424
443
|
|
|
425
444
|
async def device_login(self):
|
|
426
445
|
"""Perform device login instead."""
|
|
427
|
-
_LOGGER.debug("Starting device SRP authentication.")
|
|
446
|
+
_LOGGER.debug("device_login - Starting device SRP authentication.")
|
|
428
447
|
login_result = await self.login()
|
|
429
|
-
|
|
430
|
-
|
|
448
|
+
|
|
449
|
+
if "AuthenticationResult" in login_result:
|
|
450
|
+
# Login succeeded without a device challenge (e.g. device tracking
|
|
451
|
+
# not enforced). Tokens are already valid, return them directly.
|
|
452
|
+
_LOGGER.debug(
|
|
453
|
+
"device_login - Device login succeeded with only "
|
|
454
|
+
+ "username/password challenge: %s",
|
|
455
|
+
login_result,
|
|
456
|
+
)
|
|
457
|
+
return login_result
|
|
458
|
+
|
|
459
|
+
auth_params = await self.get_auth_params(is_device_login=True)
|
|
431
460
|
|
|
432
461
|
if login_result.get("ChallengeName") == self.DEVICE_VERIFIER_CHALLENGE:
|
|
433
|
-
_LOGGER.debug("Processing DEVICE_SRP_AUTH challenge.")
|
|
462
|
+
_LOGGER.debug("device_login - Processing DEVICE_SRP_AUTH challenge.")
|
|
434
463
|
try:
|
|
435
464
|
initial_result = await self.loop.run_in_executor(
|
|
436
465
|
None,
|
|
@@ -458,6 +487,14 @@ class HiveAuthAsync:
|
|
|
458
487
|
if err.__class__.__name__ == "EndpointConnectionError":
|
|
459
488
|
_LOGGER.error("Device login failed: cannot reach endpoint.")
|
|
460
489
|
raise HiveApiError from err
|
|
490
|
+
elif login_result.get("ChallengeName") == self.SMS_MFA_CHALLENGE:
|
|
491
|
+
# Account has 2FA enabled and device is not remembered by Cognito.
|
|
492
|
+
# Automatic re-authentication is not possible without user interaction.
|
|
493
|
+
_LOGGER.error(
|
|
494
|
+
"Device login failed: SMS MFA challenge not supported "
|
|
495
|
+
+ "when device is not remembered."
|
|
496
|
+
)
|
|
497
|
+
raise HiveReauthRequired
|
|
461
498
|
else:
|
|
462
499
|
_LOGGER.error(
|
|
463
500
|
"Device login failed: expected DEVICE_SRP_AUTH challenge, got %s.",
|
|
@@ -465,7 +502,7 @@ class HiveAuthAsync:
|
|
|
465
502
|
)
|
|
466
503
|
raise HiveInvalidDeviceAuthentication
|
|
467
504
|
|
|
468
|
-
_LOGGER.debug("Device authentication completed successfully.")
|
|
505
|
+
_LOGGER.debug("device_login - Device authentication completed successfully.")
|
|
469
506
|
return result
|
|
470
507
|
|
|
471
508
|
async def sms_2fa(
|
|
@@ -477,7 +514,7 @@ class HiveAuthAsync:
|
|
|
477
514
|
session = challenge_parameters.get("Session")
|
|
478
515
|
code = str(entered_code)
|
|
479
516
|
result = None
|
|
480
|
-
_LOGGER.debug("Submitting SMS 2FA code to Cognito.")
|
|
517
|
+
_LOGGER.debug("sms_2fa - Submitting SMS 2FA code to Cognito.")
|
|
481
518
|
try:
|
|
482
519
|
result = await self.loop.run_in_executor(
|
|
483
520
|
None,
|
|
@@ -512,12 +549,12 @@ class HiveAuthAsync:
|
|
|
512
549
|
_LOGGER.error("2FA failed: cannot reach Cognito endpoint.")
|
|
513
550
|
raise HiveApiError from err
|
|
514
551
|
|
|
515
|
-
_LOGGER.debug("2FA authentication completed successfully.")
|
|
552
|
+
_LOGGER.debug("sms_2fa - 2FA authentication completed successfully.")
|
|
516
553
|
return result
|
|
517
554
|
|
|
518
555
|
async def device_registration(self, device_name: str = None):
|
|
519
556
|
"""Register device with Hive."""
|
|
520
|
-
_LOGGER.debug("Registering device with Hive.")
|
|
557
|
+
_LOGGER.debug("device_registration - Registering device with Hive.")
|
|
521
558
|
await self.confirm_device(device_name)
|
|
522
559
|
await self.update_device_status()
|
|
523
560
|
|
|
@@ -588,7 +625,7 @@ class HiveAuthAsync:
|
|
|
588
625
|
"""Refresh Hive Tokens."""
|
|
589
626
|
if self.client is None:
|
|
590
627
|
await self.async_init()
|
|
591
|
-
_LOGGER.debug("Requesting token refresh from Cognito.")
|
|
628
|
+
_LOGGER.debug("refresh_token - Requesting token refresh from Cognito.")
|
|
592
629
|
result = None
|
|
593
630
|
auth_params = {"REFRESH_TOKEN": token}
|
|
594
631
|
if self.device_key is not None:
|
|
@@ -618,14 +655,20 @@ class HiveAuthAsync:
|
|
|
618
655
|
_LOGGER.warning("Refresh token is invalid or expired.")
|
|
619
656
|
raise HiveRefreshTokenExpired from err
|
|
620
657
|
|
|
621
|
-
_LOGGER.error(
|
|
658
|
+
_LOGGER.error(
|
|
659
|
+
"refresh_token - Token refresh failed: %s - %s",
|
|
660
|
+
error_code,
|
|
661
|
+
error_message,
|
|
662
|
+
)
|
|
622
663
|
raise HiveFailedToRefreshTokens from err
|
|
623
664
|
except botocore.exceptions.EndpointConnectionError as err:
|
|
624
665
|
if err.__class__.__name__ == "EndpointConnectionError":
|
|
625
|
-
_LOGGER.error(
|
|
666
|
+
_LOGGER.error(
|
|
667
|
+
"refresh_token - Token refresh failed: cannot reach Cognito endpoint."
|
|
668
|
+
)
|
|
626
669
|
raise HiveApiError from err
|
|
627
670
|
|
|
628
|
-
_LOGGER.debug("Cognito token refresh completed successfully.")
|
|
671
|
+
_LOGGER.debug("refresh_token - Cognito token refresh completed successfully.")
|
|
629
672
|
return result
|
|
630
673
|
|
|
631
674
|
async def forget_device(self, access_token, device_key):
|
|
@@ -94,7 +94,7 @@ class HiveCamera:
|
|
|
94
94
|
device["hiveID"] in self.session.data.devices
|
|
95
95
|
and device["deviceData"]["online"]
|
|
96
96
|
):
|
|
97
|
-
_LOGGER.debug("Setting camera ON for %s.", device["haName"])
|
|
97
|
+
_LOGGER.debug("setCameraOn - Setting camera ON for %s.", device["haName"])
|
|
98
98
|
await self.session.hiveRefreshTokens()
|
|
99
99
|
resp = await self.session.api.setState(mode=mode)
|
|
100
100
|
if resp["original"] == 200:
|
|
@@ -118,7 +118,7 @@ class HiveCamera:
|
|
|
118
118
|
device["hiveID"] in self.session.data.devices
|
|
119
119
|
and device["deviceData"]["online"]
|
|
120
120
|
):
|
|
121
|
-
_LOGGER.debug("Setting camera OFF for %s.", device["haName"])
|
|
121
|
+
_LOGGER.debug("setCameraOff - Setting camera OFF for %s.", device["haName"])
|
|
122
122
|
await self.session.hiveRefreshTokens()
|
|
123
123
|
resp = await self.session.api.setState(mode=mode)
|
|
124
124
|
if resp["original"] == 200:
|
|
@@ -152,6 +152,14 @@ class Camera(HiveCamera):
|
|
|
152
152
|
Returns:
|
|
153
153
|
dict: Updated device.
|
|
154
154
|
"""
|
|
155
|
+
if self.session.shouldUseCachedData():
|
|
156
|
+
cached = self.session.getCachedDevice(device)
|
|
157
|
+
if cached is not None:
|
|
158
|
+
_LOGGER.debug(
|
|
159
|
+
"getCamera - Returning cached state for camera %s (slow/busy poll).",
|
|
160
|
+
device["haName"],
|
|
161
|
+
)
|
|
162
|
+
return cached
|
|
155
163
|
device["deviceData"].update(
|
|
156
164
|
{"online": await self.session.attr.onlineOffline(device["device_id"])}
|
|
157
165
|
)
|
|
@@ -159,7 +167,7 @@ class Camera(HiveCamera):
|
|
|
159
167
|
|
|
160
168
|
if device["deviceData"]["online"]:
|
|
161
169
|
self.session.helper.deviceRecovered(device["device_id"])
|
|
162
|
-
_LOGGER.debug("Updating camera data for %s.", device["haName"])
|
|
170
|
+
_LOGGER.debug("getCamera - Updating camera data for %s.", device["haName"])
|
|
163
171
|
data = self.session.data.devices[device["device_id"]]
|
|
164
172
|
dev_data = {
|
|
165
173
|
"hiveID": device["hiveID"],
|
|
@@ -183,8 +191,7 @@ class Camera(HiveCamera):
|
|
|
183
191
|
),
|
|
184
192
|
}
|
|
185
193
|
|
|
186
|
-
self.session.
|
|
187
|
-
return self.session.devices[device["hiveID"]]
|
|
194
|
+
return self.session.setCachedDevice(device, dev_data)
|
|
188
195
|
else:
|
|
189
196
|
await self.session.helper.errorCheck(
|
|
190
197
|
device["device_id"], "ERROR", device["deviceData"]["online"]
|