pyhive-integration 1.0.8.dev9__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.
Files changed (40) hide show
  1. {pyhive_integration-1.0.8.dev9/pyhive_integration.egg-info → pyhive_integration-1.0.9.dev1}/PKG-INFO +1 -1
  2. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1/pyhive_integration.egg-info}/PKG-INFO +1 -1
  3. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/setup.py +1 -1
  4. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/action.py +9 -2
  5. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/alarm.py +9 -2
  6. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/api/hive_api.py +81 -16
  7. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/api/hive_async_api.py +1 -2
  8. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/api/hive_auth.py +11 -0
  9. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/api/hive_auth_async.py +60 -17
  10. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/camera.py +12 -5
  11. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/heating.py +99 -24
  12. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/helper/hive_helper.py +123 -30
  13. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/hive.py +1 -1
  14. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/hotwater.py +27 -14
  15. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/light.py +89 -33
  16. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/plug.py +18 -13
  17. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/sensor.py +16 -11
  18. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/session.py +198 -47
  19. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/LICENSE +0 -0
  20. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/MANIFEST.in +0 -0
  21. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/README.md +0 -0
  22. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/pyhive_integration.egg-info/SOURCES.txt +0 -0
  23. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/pyhive_integration.egg-info/dependency_links.txt +0 -0
  24. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/pyhive_integration.egg-info/requires.txt +0 -0
  25. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/pyhive_integration.egg-info/top_level.txt +0 -0
  26. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/pyproject.toml +0 -0
  27. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/requirements.txt +0 -0
  28. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/requirements_test.txt +0 -0
  29. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/setup.cfg +0 -0
  30. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/__init__.py +0 -0
  31. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/api/__init__.py +0 -0
  32. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/device_attributes.py +0 -0
  33. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/helper/__init__.py +0 -0
  34. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/helper/const.py +0 -0
  35. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/helper/debugger.py +0 -0
  36. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/helper/hive_exceptions.py +0 -0
  37. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/helper/hivedataclasses.py +0 -0
  38. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/helper/map.py +0 -0
  39. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/src/hub.py +0 -0
  40. {pyhive_integration-1.0.8.dev9 → pyhive_integration-1.0.9.dev1}/tests/test_hub.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyhive-integration
3
- Version: 1.0.8.dev9
3
+ Version: 1.0.9.dev1
4
4
  Summary: A Python library to interface with the Hive API
5
5
  Home-page: https://github.com/Pyhive/pyhiveapi
6
6
  Author: KJonline24
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyhive-integration
3
- Version: 1.0.8.dev9
3
+ Version: 1.0.9.dev1
4
4
  Summary: A Python library to interface with the Hive API
5
5
  Home-page: https://github.com/Pyhive/pyhiveapi
6
6
  Author: KJonline24
@@ -17,7 +17,7 @@ def requirements_from_file(filename="requirements.txt"):
17
17
 
18
18
 
19
19
  setup(
20
- version="1.0.8.dev9",
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.devices.update({device["hiveID"]: dev_data})
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.devices.update({device["hiveID"]: dev_data})
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
- if type == "GET":
76
- return requests.get(
77
- url=url, headers=self.headers, data=jsc, timeout=self.timeout
78
- )
79
- if type == "POST":
80
- return requests.post(
81
- url=url, headers=self.headers, data=jsc, timeout=self.timeout
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 (OSError, RuntimeError, ZeroDivisionError):
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
- json_return.update({"original": info.status_code})
140
- json_return.update({"parsed": info.json()})
141
- except (OSError, RuntimeError, ZeroDivisionError):
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
- self.json_return.update({"original": response.status_code})
275
- self.json_return.update({"parsed": response.json()})
276
- except (OSError, RuntimeError, ZeroDivisionError, ConnectionError):
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
- _LOGGER.debug("SRP auth challenge completed successfully.")
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
- auth_params = await self.get_auth_params()
430
- auth_params["DEVICE_KEY"] = self.device_key
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("Token refresh failed: %s - %s", error_code, error_message)
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("Token refresh failed: cannot reach Cognito endpoint.")
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.devices.update({device["hiveID"]: dev_data})
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"]