pyezvizapi 1.0.1.6__py3-none-any.whl → 1.0.1.8__py3-none-any.whl
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.
Potentially problematic release.
This version of pyezvizapi might be problematic. Click here for more details.
- pyezvizapi/__init__.py +15 -2
- pyezvizapi/__main__.py +406 -283
- pyezvizapi/camera.py +488 -118
- pyezvizapi/cas.py +36 -43
- pyezvizapi/client.py +798 -1342
- pyezvizapi/constants.py +9 -2
- pyezvizapi/exceptions.py +9 -9
- pyezvizapi/light_bulb.py +80 -31
- pyezvizapi/models.py +103 -0
- pyezvizapi/mqtt.py +490 -133
- pyezvizapi/test_cam_rtsp.py +95 -109
- pyezvizapi/test_mqtt.py +135 -0
- pyezvizapi/utils.py +28 -2
- {pyezvizapi-1.0.1.6.dist-info → pyezvizapi-1.0.1.8.dist-info}/METADATA +2 -2
- pyezvizapi-1.0.1.8.dist-info/RECORD +21 -0
- pyezvizapi-1.0.1.6.dist-info/RECORD +0 -19
- {pyezvizapi-1.0.1.6.dist-info → pyezvizapi-1.0.1.8.dist-info}/WHEEL +0 -0
- {pyezvizapi-1.0.1.6.dist-info → pyezvizapi-1.0.1.8.dist-info}/entry_points.txt +0 -0
- {pyezvizapi-1.0.1.6.dist-info → pyezvizapi-1.0.1.8.dist-info}/licenses/LICENSE +0 -0
- {pyezvizapi-1.0.1.6.dist-info → pyezvizapi-1.0.1.8.dist-info}/licenses/LICENSE.md +0 -0
- {pyezvizapi-1.0.1.6.dist-info → pyezvizapi-1.0.1.8.dist-info}/top_level.txt +0 -0
pyezvizapi/client.py
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from collections.abc import Callable
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
import hashlib
|
|
7
8
|
import json
|
|
8
9
|
import logging
|
|
9
|
-
from typing import Any
|
|
10
|
+
from typing import Any, ClassVar, TypedDict, cast
|
|
10
11
|
import urllib.parse
|
|
11
12
|
from uuid import uuid4
|
|
12
13
|
|
|
@@ -79,14 +80,95 @@ from .exceptions import (
|
|
|
79
80
|
PyEzvizError,
|
|
80
81
|
)
|
|
81
82
|
from .light_bulb import EzvizLightBulb
|
|
83
|
+
from .models import EzvizDeviceRecord, build_device_records_map
|
|
84
|
+
from .mqtt import MQTTClient
|
|
82
85
|
from .utils import convert_to_dict, deep_merge
|
|
83
86
|
|
|
84
87
|
_LOGGER = logging.getLogger(__name__)
|
|
85
88
|
|
|
86
89
|
|
|
90
|
+
class ClientToken(TypedDict, total=False):
|
|
91
|
+
"""Typed shape for the Ezviz client token."""
|
|
92
|
+
|
|
93
|
+
session_id: str | None
|
|
94
|
+
rf_session_id: str | None
|
|
95
|
+
username: str | None
|
|
96
|
+
api_url: str
|
|
97
|
+
service_urls: dict[str, Any]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class MetaDict(TypedDict, total=False):
|
|
101
|
+
"""Shape of the common 'meta' object used by the Ezviz API."""
|
|
102
|
+
|
|
103
|
+
code: int
|
|
104
|
+
message: str
|
|
105
|
+
moreInfo: Any
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ApiOkResponse(TypedDict, total=False):
|
|
109
|
+
"""Container for API responses that include a top-level 'meta'."""
|
|
110
|
+
|
|
111
|
+
meta: MetaDict
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ResultCodeResponse(TypedDict, total=False):
|
|
115
|
+
"""Legacy-style API response using 'resultCode'."""
|
|
116
|
+
|
|
117
|
+
resultCode: str | int
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class StorageStatusResponse(ResultCodeResponse, total=False):
|
|
121
|
+
"""Response for storage status queries."""
|
|
122
|
+
|
|
123
|
+
storageStatus: Any
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class CamKeyResponse(ResultCodeResponse, total=False):
|
|
127
|
+
"""Response for camera encryption key retrieval."""
|
|
128
|
+
|
|
129
|
+
encryptkey: str
|
|
130
|
+
resultDes: str
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class SystemInfoResponse(TypedDict, total=False):
|
|
134
|
+
"""System info response including configuration details."""
|
|
135
|
+
|
|
136
|
+
systemConfigInfo: dict[str, Any]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class PagelistPageInfo(TypedDict, total=False):
|
|
140
|
+
"""Pagination info with 'hasNext' flag."""
|
|
141
|
+
|
|
142
|
+
hasNext: bool
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class PagelistResponse(ApiOkResponse, total=False):
|
|
146
|
+
"""Pagelist response wrapper; other keys are dynamic per filter."""
|
|
147
|
+
|
|
148
|
+
page: PagelistPageInfo
|
|
149
|
+
# other keys are dynamic; callers select via json_key
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class UserIdResponse(ApiOkResponse, total=False):
|
|
153
|
+
"""User ID response holding device token info used by restricted APIs."""
|
|
154
|
+
|
|
155
|
+
deviceTokenInfo: Any
|
|
156
|
+
|
|
157
|
+
|
|
87
158
|
class EzvizClient:
|
|
88
159
|
"""Initialize api client object."""
|
|
89
160
|
|
|
161
|
+
# Supported categories for load_devices gating
|
|
162
|
+
SUPPORTED_CATEGORIES: ClassVar[list[str]] = [
|
|
163
|
+
DeviceCatagories.COMMON_DEVICE_CATEGORY.value,
|
|
164
|
+
DeviceCatagories.CAMERA_DEVICE_CATEGORY.value,
|
|
165
|
+
DeviceCatagories.BATTERY_CAMERA_DEVICE_CATEGORY.value,
|
|
166
|
+
DeviceCatagories.DOORBELL_DEVICE_CATEGORY.value,
|
|
167
|
+
DeviceCatagories.BASE_STATION_DEVICE_CATEGORY.value,
|
|
168
|
+
DeviceCatagories.CAT_EYE_CATEGORY.value,
|
|
169
|
+
DeviceCatagories.LIGHTING.value,
|
|
170
|
+
]
|
|
171
|
+
|
|
90
172
|
def __init__(
|
|
91
173
|
self,
|
|
92
174
|
account: str | None = None,
|
|
@@ -102,20 +184,24 @@ class EzvizClient:
|
|
|
102
184
|
) # Ezviz API sends md5 of password
|
|
103
185
|
self._session = requests.session()
|
|
104
186
|
self._session.headers.update(REQUEST_HEADER)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
187
|
+
if token and token.get("session_id"):
|
|
188
|
+
self._session.headers["sessionId"] = str(token["session_id"]) # ensure str
|
|
189
|
+
self._token: ClientToken = cast(
|
|
190
|
+
ClientToken,
|
|
191
|
+
token or {
|
|
192
|
+
"session_id": None,
|
|
193
|
+
"rf_session_id": None,
|
|
194
|
+
"username": None,
|
|
195
|
+
"api_url": url,
|
|
196
|
+
},
|
|
197
|
+
)
|
|
112
198
|
self._timeout = timeout
|
|
113
199
|
self._cameras: dict[str, Any] = {}
|
|
114
200
|
self._light_bulbs: dict[str, Any] = {}
|
|
201
|
+
self.mqtt_client: MQTTClient | None = None
|
|
115
202
|
|
|
116
203
|
def _login(self, smscode: int | None = None) -> dict[Any, Any]:
|
|
117
204
|
"""Login to Ezviz API."""
|
|
118
|
-
|
|
119
205
|
# Region code to url.
|
|
120
206
|
if len(self._token["api_url"].split(".")) == 1:
|
|
121
207
|
self._token["api_url"] = "apii" + self._token["api_url"] + ".ezvizlife.com"
|
|
@@ -170,12 +256,16 @@ class EzvizClient:
|
|
|
170
256
|
|
|
171
257
|
self._token["service_urls"] = self.get_service_urls()
|
|
172
258
|
|
|
173
|
-
return self._token
|
|
259
|
+
return cast(dict[Any, Any], self._token)
|
|
174
260
|
|
|
175
261
|
if json_result["meta"]["code"] == 1100:
|
|
176
262
|
self._token["api_url"] = json_result["loginArea"]["apiDomain"]
|
|
177
|
-
_LOGGER.warning(
|
|
178
|
-
|
|
263
|
+
_LOGGER.warning(
|
|
264
|
+
"region_incorrect: serial=%s code=%s msg=%s",
|
|
265
|
+
"unknown",
|
|
266
|
+
1100,
|
|
267
|
+
self._token["api_url"],
|
|
268
|
+
)
|
|
179
269
|
return self.login()
|
|
180
270
|
|
|
181
271
|
if json_result["meta"]["code"] == 1012:
|
|
@@ -198,75 +288,230 @@ class EzvizClient:
|
|
|
198
288
|
|
|
199
289
|
raise PyEzvizError(f"Login error: {json_result['meta']}")
|
|
200
290
|
|
|
201
|
-
|
|
202
|
-
|
|
291
|
+
# ---- Internal HTTP helpers -------------------------------------------------
|
|
292
|
+
|
|
293
|
+
def _http_request(
|
|
294
|
+
self,
|
|
295
|
+
method: str,
|
|
296
|
+
url: str,
|
|
297
|
+
*,
|
|
298
|
+
params: dict | None = None,
|
|
299
|
+
data: dict | str | None = None,
|
|
300
|
+
json_body: dict | None = None,
|
|
301
|
+
retry_401: bool = True,
|
|
302
|
+
max_retries: int = 0,
|
|
303
|
+
) -> requests.Response:
|
|
304
|
+
"""Perform an HTTP request with optional 401 retry via re-login.
|
|
305
|
+
|
|
306
|
+
Centralizes the common 401→login→retry pattern without altering
|
|
307
|
+
individual endpoint behavior. Returns the Response for the caller to
|
|
308
|
+
parse and validate according to its API contract.
|
|
309
|
+
"""
|
|
203
310
|
try:
|
|
204
|
-
req = self._session.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
311
|
+
req = self._session.request(
|
|
312
|
+
method=method,
|
|
313
|
+
url=url,
|
|
314
|
+
params=params,
|
|
315
|
+
data=data,
|
|
316
|
+
json=json_body,
|
|
210
317
|
timeout=self._timeout,
|
|
211
318
|
)
|
|
212
|
-
|
|
213
319
|
req.raise_for_status()
|
|
214
|
-
|
|
215
320
|
except requests.HTTPError as err:
|
|
321
|
+
if retry_401 and err.response is not None and err.response.status_code == 401:
|
|
322
|
+
if max_retries >= MAX_RETRIES:
|
|
323
|
+
raise HTTPError from err
|
|
324
|
+
# Re-login and retry once
|
|
325
|
+
self.login()
|
|
326
|
+
return self._http_request(
|
|
327
|
+
method,
|
|
328
|
+
url,
|
|
329
|
+
params=params,
|
|
330
|
+
data=data,
|
|
331
|
+
json_body=json_body,
|
|
332
|
+
retry_401=retry_401,
|
|
333
|
+
max_retries=max_retries + 1,
|
|
334
|
+
)
|
|
216
335
|
raise HTTPError from err
|
|
336
|
+
else:
|
|
337
|
+
return req
|
|
217
338
|
|
|
339
|
+
@staticmethod
|
|
340
|
+
def _parse_json(resp: requests.Response) -> dict:
|
|
341
|
+
"""Parse JSON or raise a friendly error."""
|
|
218
342
|
try:
|
|
219
|
-
|
|
220
|
-
|
|
343
|
+
return cast(dict, resp.json())
|
|
221
344
|
except ValueError as err:
|
|
222
345
|
raise PyEzvizError(
|
|
223
|
-
"Impossible to decode response: "
|
|
224
|
-
+ str(err)
|
|
225
|
-
+ "\nResponse was: "
|
|
226
|
-
+ str(req.text)
|
|
346
|
+
"Impossible to decode response: " + str(err) + "\nResponse was: " + str(resp.text)
|
|
227
347
|
) from err
|
|
228
348
|
|
|
229
|
-
|
|
230
|
-
|
|
349
|
+
@staticmethod
|
|
350
|
+
def _is_ok(payload: dict) -> bool:
|
|
351
|
+
"""Return True if payload indicates success for both API styles."""
|
|
352
|
+
meta = payload.get("meta")
|
|
353
|
+
if isinstance(meta, dict) and meta.get("code") == 200:
|
|
354
|
+
return True
|
|
355
|
+
rc = payload.get("resultCode")
|
|
356
|
+
return rc in (0, "0")
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
359
|
+
def _meta_code(payload: dict) -> int | None:
|
|
360
|
+
"""Safely extract meta.code as an int, or None if missing/invalid."""
|
|
361
|
+
code = (payload.get("meta") or {}).get("code")
|
|
362
|
+
if isinstance(code, (int, str)):
|
|
363
|
+
try:
|
|
364
|
+
return int(code)
|
|
365
|
+
except (TypeError, ValueError):
|
|
366
|
+
return None
|
|
367
|
+
return None
|
|
231
368
|
|
|
232
|
-
|
|
369
|
+
@staticmethod
|
|
370
|
+
def _meta_ok(payload: dict) -> bool:
|
|
371
|
+
"""Return True if meta.code equals 200."""
|
|
372
|
+
return EzvizClient._meta_code(payload) == 200
|
|
233
373
|
|
|
234
|
-
|
|
235
|
-
|
|
374
|
+
@staticmethod
|
|
375
|
+
def _response_code(payload: dict) -> int | str | None:
|
|
376
|
+
"""Return a best-effort code from a response for logging.
|
|
236
377
|
|
|
237
|
-
if
|
|
238
|
-
|
|
378
|
+
Prefers modern ``meta.code`` if present; falls back to legacy
|
|
379
|
+
``resultCode`` or a top-level ``status`` field when available.
|
|
380
|
+
Returns None if no code-like field is found.
|
|
381
|
+
"""
|
|
382
|
+
# Prefer modern meta.code
|
|
383
|
+
mc = EzvizClient._meta_code(payload)
|
|
384
|
+
if mc is not None:
|
|
385
|
+
return mc
|
|
386
|
+
if "resultCode" in payload:
|
|
387
|
+
return payload.get("resultCode")
|
|
388
|
+
if "status" in payload:
|
|
389
|
+
return payload.get("status")
|
|
390
|
+
return None
|
|
239
391
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
url=f"https://{self._token['api_url']}{API_ENDPOINT_SERVER_INFO}",
|
|
243
|
-
timeout=self._timeout,
|
|
244
|
-
)
|
|
245
|
-
req.raise_for_status()
|
|
392
|
+
def _ensure_ok(self, payload: dict, message: str) -> None:
|
|
393
|
+
"""Raise PyEzvizError with context if response is not OK.
|
|
246
394
|
|
|
247
|
-
|
|
248
|
-
|
|
395
|
+
Accepts both API styles: new (meta.code == 200) and legacy (resultCode == 0).
|
|
396
|
+
"""
|
|
397
|
+
if not self._is_ok(payload):
|
|
398
|
+
raise PyEzvizError(f"{message}: Got {payload})")
|
|
399
|
+
|
|
400
|
+
def _send_prepared(
|
|
401
|
+
self,
|
|
402
|
+
prepared: requests.PreparedRequest,
|
|
403
|
+
*,
|
|
404
|
+
retry_401: bool = True,
|
|
405
|
+
max_retries: int = 0,
|
|
406
|
+
) -> requests.Response:
|
|
407
|
+
"""Send a prepared request with optional 401 retry.
|
|
249
408
|
|
|
409
|
+
Useful for endpoints requiring special URL encoding or manual preparation.
|
|
410
|
+
"""
|
|
411
|
+
try:
|
|
412
|
+
req = self._session.send(request=prepared, timeout=self._timeout)
|
|
413
|
+
req.raise_for_status()
|
|
250
414
|
except requests.HTTPError as err:
|
|
415
|
+
if retry_401 and err.response is not None and err.response.status_code == 401:
|
|
416
|
+
if max_retries >= MAX_RETRIES:
|
|
417
|
+
raise HTTPError from err
|
|
418
|
+
self.login()
|
|
419
|
+
return self._send_prepared(prepared, retry_401=retry_401, max_retries=max_retries + 1)
|
|
251
420
|
raise HTTPError from err
|
|
421
|
+
return req
|
|
252
422
|
|
|
253
|
-
|
|
254
|
-
json_output = req.json()
|
|
423
|
+
# ---- Small helpers --------------------------------------------------------------
|
|
255
424
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
+ str(err)
|
|
260
|
-
+ "\nResponse was: "
|
|
261
|
-
+ str(req.text)
|
|
262
|
-
) from err
|
|
425
|
+
def _url(self, path: str) -> str:
|
|
426
|
+
"""Build a full API URL for the given path."""
|
|
427
|
+
return f"https://{self._token['api_url']}{path}"
|
|
263
428
|
|
|
264
|
-
|
|
265
|
-
|
|
429
|
+
def _request_json(
|
|
430
|
+
self,
|
|
431
|
+
method: str,
|
|
432
|
+
path: str,
|
|
433
|
+
*,
|
|
434
|
+
params: dict | None = None,
|
|
435
|
+
data: dict | str | None = None,
|
|
436
|
+
json_body: dict | None = None,
|
|
437
|
+
retry_401: bool = True,
|
|
438
|
+
max_retries: int = 0,
|
|
439
|
+
) -> dict:
|
|
440
|
+
"""Perform request and parse JSON in one step."""
|
|
441
|
+
resp = self._http_request(
|
|
442
|
+
method,
|
|
443
|
+
self._url(path),
|
|
444
|
+
params=params,
|
|
445
|
+
data=data,
|
|
446
|
+
json_body=json_body,
|
|
447
|
+
retry_401=retry_401,
|
|
448
|
+
max_retries=max_retries,
|
|
449
|
+
)
|
|
450
|
+
return self._parse_json(resp)
|
|
451
|
+
|
|
452
|
+
def _retry_json(
|
|
453
|
+
self,
|
|
454
|
+
producer: Callable[[], dict],
|
|
455
|
+
*,
|
|
456
|
+
attempts: int,
|
|
457
|
+
should_retry: Callable[[dict], bool],
|
|
458
|
+
log: str,
|
|
459
|
+
serial: str | None = None,
|
|
460
|
+
) -> dict:
|
|
461
|
+
"""Run a JSON-producing callable with retry policy.
|
|
266
462
|
|
|
267
|
-
|
|
268
|
-
|
|
463
|
+
Calls ``producer`` up to ``attempts + 1`` times. After each call, the
|
|
464
|
+
result is passed to ``should_retry``; if it returns True and attempts
|
|
465
|
+
remain, a retry is performed and a concise warning is logged. If it
|
|
466
|
+
returns False, the payload is returned to the caller.
|
|
269
467
|
|
|
468
|
+
Raises:
|
|
469
|
+
PyEzvizError: If retries are exhausted without a successful payload.
|
|
470
|
+
"""
|
|
471
|
+
total = max(0, attempts)
|
|
472
|
+
for attempt in range(total + 1):
|
|
473
|
+
payload = producer()
|
|
474
|
+
if not should_retry(payload):
|
|
475
|
+
return payload
|
|
476
|
+
if attempt < total:
|
|
477
|
+
# Prefer modern meta.code; fall back to legacy resultCode
|
|
478
|
+
code = self._response_code(payload)
|
|
479
|
+
_LOGGER.warning(
|
|
480
|
+
"http_retry: serial=%s code=%s msg=%s",
|
|
481
|
+
serial or "unknown",
|
|
482
|
+
code,
|
|
483
|
+
log,
|
|
484
|
+
)
|
|
485
|
+
raise PyEzvizError(f"{log}: exceeded retries")
|
|
486
|
+
|
|
487
|
+
def send_mfa_code(self) -> bool:
|
|
488
|
+
"""Send verification code."""
|
|
489
|
+
json_output = self._request_json(
|
|
490
|
+
"POST",
|
|
491
|
+
API_ENDPOINT_SEND_CODE,
|
|
492
|
+
data={"from": self.account, "bizType": "TERMINAL_BIND"},
|
|
493
|
+
retry_401=False,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
if not self._meta_ok(json_output):
|
|
497
|
+
raise PyEzvizError(f"Could not request MFA code: Got {json_output})")
|
|
498
|
+
|
|
499
|
+
return True
|
|
500
|
+
|
|
501
|
+
def get_service_urls(self) -> Any:
|
|
502
|
+
"""Get Ezviz service urls."""
|
|
503
|
+
if not self._token["session_id"]:
|
|
504
|
+
raise PyEzvizError("No Login token present!")
|
|
505
|
+
|
|
506
|
+
try:
|
|
507
|
+
json_output = self._request_json("GET", API_ENDPOINT_SERVER_INFO)
|
|
508
|
+
except requests.ConnectionError as err: # pragma: no cover - keep behavior
|
|
509
|
+
raise InvalidURL("A Invalid URL or Proxy error occurred") from err
|
|
510
|
+
if not self._meta_ok(json_output):
|
|
511
|
+
raise PyEzvizError(f"Error getting Service URLs: {json_output}")
|
|
512
|
+
|
|
513
|
+
service_urls = json_output.get("systemConfigInfo", {})
|
|
514
|
+
service_urls["sysConf"] = str(service_urls.get("sysConf", "")).split("|")
|
|
270
515
|
return service_urls
|
|
271
516
|
|
|
272
517
|
def _api_get_pagelist(
|
|
@@ -279,7 +524,6 @@ class EzvizClient:
|
|
|
279
524
|
max_retries: int = 0,
|
|
280
525
|
) -> Any:
|
|
281
526
|
"""Get data from pagelist API."""
|
|
282
|
-
|
|
283
527
|
if max_retries > MAX_RETRIES:
|
|
284
528
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
285
529
|
|
|
@@ -293,43 +537,21 @@ class EzvizClient:
|
|
|
293
537
|
"filter": page_filter,
|
|
294
538
|
}
|
|
295
539
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
except requests.HTTPError as err:
|
|
306
|
-
if err.response.status_code == 401:
|
|
307
|
-
# session is wrong, need to relogin
|
|
308
|
-
self.login()
|
|
309
|
-
return self._api_get_pagelist(
|
|
310
|
-
page_filter, json_key, group_id, limit, offset, max_retries + 1
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
raise HTTPError from err
|
|
314
|
-
|
|
315
|
-
try:
|
|
316
|
-
json_output = req.json()
|
|
317
|
-
|
|
318
|
-
except ValueError as err:
|
|
319
|
-
raise PyEzvizError(
|
|
320
|
-
"Impossible to decode response: "
|
|
321
|
-
+ str(err)
|
|
322
|
-
+ "\nResponse was: "
|
|
323
|
-
+ str(req.text)
|
|
324
|
-
) from err
|
|
325
|
-
|
|
326
|
-
if json_output["meta"]["code"] != 200:
|
|
327
|
-
# session is wrong, need to relogin
|
|
540
|
+
json_output = self._request_json(
|
|
541
|
+
"GET",
|
|
542
|
+
API_ENDPOINT_PAGELIST,
|
|
543
|
+
params=params,
|
|
544
|
+
retry_401=True,
|
|
545
|
+
max_retries=max_retries,
|
|
546
|
+
)
|
|
547
|
+
if self._meta_code(json_output) != 200:
|
|
548
|
+
# session is wrong, need to relogin and retry
|
|
328
549
|
self.login()
|
|
329
550
|
_LOGGER.warning(
|
|
330
|
-
"
|
|
331
|
-
|
|
332
|
-
json_output,
|
|
551
|
+
"http_retry: serial=%s code=%s msg=%s",
|
|
552
|
+
"unknown",
|
|
553
|
+
self._meta_code(json_output),
|
|
554
|
+
"pagelist_relogin",
|
|
333
555
|
)
|
|
334
556
|
return self._api_get_pagelist(
|
|
335
557
|
page_filter, json_key, group_id, limit, offset, max_retries + 1
|
|
@@ -352,9 +574,6 @@ class EzvizClient:
|
|
|
352
574
|
|
|
353
575
|
def get_alarminfo(self, serial: str, limit: int = 1, max_retries: int = 0) -> dict:
|
|
354
576
|
"""Get data from alarm info API for camera serial."""
|
|
355
|
-
if max_retries > MAX_RETRIES:
|
|
356
|
-
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
357
|
-
|
|
358
577
|
params: dict[str, int | str] = {
|
|
359
578
|
"deviceSerials": serial,
|
|
360
579
|
"queryType": -1,
|
|
@@ -362,44 +581,21 @@ class EzvizClient:
|
|
|
362
581
|
"stype": -1,
|
|
363
582
|
}
|
|
364
583
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
584
|
+
json_output = self._retry_json(
|
|
585
|
+
lambda: self._request_json(
|
|
586
|
+
"GET",
|
|
587
|
+
API_ENDPOINT_ALARMINFO_GET,
|
|
368
588
|
params=params,
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return self.get_alarminfo(serial, limit, max_retries + 1)
|
|
379
|
-
|
|
380
|
-
raise HTTPError from err
|
|
381
|
-
|
|
382
|
-
try:
|
|
383
|
-
json_output: dict = req.json()
|
|
384
|
-
|
|
385
|
-
except ValueError as err:
|
|
386
|
-
raise PyEzvizError(
|
|
387
|
-
"Impossible to decode response: "
|
|
388
|
-
+ str(err)
|
|
389
|
-
+ "\nResponse was: "
|
|
390
|
-
+ str(req.text)
|
|
391
|
-
) from err
|
|
392
|
-
|
|
393
|
-
if json_output["meta"]["code"] != 200:
|
|
394
|
-
if json_output["meta"]["code"] == 500:
|
|
395
|
-
_LOGGER.debug(
|
|
396
|
-
"Retry getting alarm info, server returned busy: %s",
|
|
397
|
-
json_output,
|
|
398
|
-
)
|
|
399
|
-
return self.get_alarminfo(serial, limit, max_retries + 1)
|
|
400
|
-
|
|
589
|
+
retry_401=True,
|
|
590
|
+
max_retries=0,
|
|
591
|
+
),
|
|
592
|
+
attempts=max_retries,
|
|
593
|
+
should_retry=lambda p: self._meta_code(p) == 500,
|
|
594
|
+
log="alarm_info_server_busy",
|
|
595
|
+
serial=serial,
|
|
596
|
+
)
|
|
597
|
+
if self._meta_code(json_output) != 200:
|
|
401
598
|
raise PyEzvizError(f"Could not get data from alarm api: Got {json_output})")
|
|
402
|
-
|
|
403
599
|
return json_output
|
|
404
600
|
|
|
405
601
|
def get_device_messages_list(
|
|
@@ -417,7 +613,7 @@ class EzvizClient:
|
|
|
417
613
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
418
614
|
|
|
419
615
|
params: dict[str, int | str | None] = {
|
|
420
|
-
"serials
|
|
616
|
+
"serials": serials,
|
|
421
617
|
"stype": s_type,
|
|
422
618
|
"limit": limit,
|
|
423
619
|
"date": date,
|
|
@@ -425,41 +621,14 @@ class EzvizClient:
|
|
|
425
621
|
"tags": tags,
|
|
426
622
|
}
|
|
427
623
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
except requests.HTTPError as err:
|
|
438
|
-
if err.response.status_code == 401:
|
|
439
|
-
# session is wrong, need to relogin
|
|
440
|
-
self.login()
|
|
441
|
-
return self.get_device_messages_list(
|
|
442
|
-
serials, s_type, limit, date, end_time, tags, max_retries + 1
|
|
443
|
-
)
|
|
444
|
-
|
|
445
|
-
raise HTTPError from err
|
|
446
|
-
|
|
447
|
-
try:
|
|
448
|
-
json_output: dict = req.json()
|
|
449
|
-
|
|
450
|
-
except ValueError as err:
|
|
451
|
-
raise PyEzvizError(
|
|
452
|
-
"Impossible to decode response: "
|
|
453
|
-
+ str(err)
|
|
454
|
-
+ "\nResponse was: "
|
|
455
|
-
+ str(req.text)
|
|
456
|
-
) from err
|
|
457
|
-
|
|
458
|
-
if json_output["meta"]["code"] != 200:
|
|
459
|
-
raise PyEzvizError(
|
|
460
|
-
f"Could not get unified message list: Got {json_output})"
|
|
461
|
-
)
|
|
462
|
-
|
|
624
|
+
json_output = self._request_json(
|
|
625
|
+
"GET",
|
|
626
|
+
API_ENDPOINT_UNIFIEDMSG_LIST_GET,
|
|
627
|
+
params=params,
|
|
628
|
+
retry_401=True,
|
|
629
|
+
max_retries=max_retries,
|
|
630
|
+
)
|
|
631
|
+
self._ensure_ok(json_output, "Could not get unified message list")
|
|
463
632
|
return json_output
|
|
464
633
|
|
|
465
634
|
def switch_status(
|
|
@@ -473,40 +642,15 @@ class EzvizClient:
|
|
|
473
642
|
"""Camera features are represented as switches. Switch them on or off."""
|
|
474
643
|
if max_retries > MAX_RETRIES:
|
|
475
644
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
req.raise_for_status()
|
|
484
|
-
|
|
485
|
-
except requests.HTTPError as err:
|
|
486
|
-
if err.response.status_code == 401:
|
|
487
|
-
# session is wrong, need to relogin
|
|
488
|
-
self.login()
|
|
489
|
-
return self.switch_status(serial, status_type, enable, max_retries + 1)
|
|
490
|
-
|
|
491
|
-
raise HTTPError from err
|
|
492
|
-
|
|
493
|
-
try:
|
|
494
|
-
json_output = req.json()
|
|
495
|
-
|
|
496
|
-
except ValueError as err:
|
|
497
|
-
raise PyEzvizError(
|
|
498
|
-
"Impossible to decode response: "
|
|
499
|
-
+ str(err)
|
|
500
|
-
+ "\nResponse was: "
|
|
501
|
-
+ str(req.text)
|
|
502
|
-
) from err
|
|
503
|
-
|
|
504
|
-
if json_output["meta"]["code"] != 200:
|
|
505
|
-
raise PyEzvizError(f"Could not set the switch: Got {json_output})")
|
|
506
|
-
|
|
645
|
+
json_output = self._request_json(
|
|
646
|
+
"PUT",
|
|
647
|
+
f"{API_ENDPOINT_DEVICES}{serial}/{channel_no}/{enable}/{status_type}{API_ENDPOINT_SWITCH_STATUS}",
|
|
648
|
+
retry_401=True,
|
|
649
|
+
max_retries=max_retries,
|
|
650
|
+
)
|
|
651
|
+
self._ensure_ok(json_output, "Could not set the switch")
|
|
507
652
|
if self._cameras.get(serial):
|
|
508
653
|
self._cameras[serial]["switches"][status_type] = bool(enable)
|
|
509
|
-
|
|
510
654
|
return True
|
|
511
655
|
|
|
512
656
|
def switch_status_other(
|
|
@@ -524,43 +668,14 @@ class EzvizClient:
|
|
|
524
668
|
if max_retries > MAX_RETRIES:
|
|
525
669
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
526
670
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
},
|
|
536
|
-
)
|
|
537
|
-
|
|
538
|
-
req.raise_for_status()
|
|
539
|
-
|
|
540
|
-
except requests.HTTPError as err:
|
|
541
|
-
if err.response.status_code == 401:
|
|
542
|
-
# session is wrong, need to relogin
|
|
543
|
-
self.login()
|
|
544
|
-
return self.switch_status_other(
|
|
545
|
-
serial, status_type, enable, channel_number, max_retries + 1
|
|
546
|
-
)
|
|
547
|
-
|
|
548
|
-
raise HTTPError from err
|
|
549
|
-
|
|
550
|
-
try:
|
|
551
|
-
json_output = req.json()
|
|
552
|
-
|
|
553
|
-
except ValueError as err:
|
|
554
|
-
raise PyEzvizError(
|
|
555
|
-
"Impossible to decode response: "
|
|
556
|
-
+ str(err)
|
|
557
|
-
+ "\nResponse was: "
|
|
558
|
-
+ str(req.text)
|
|
559
|
-
) from err
|
|
560
|
-
|
|
561
|
-
if json_output["meta"]["code"] != 200:
|
|
562
|
-
raise PyEzvizError(f"Could not set the switch: Got {json_output})")
|
|
563
|
-
|
|
671
|
+
json_output = self._request_json(
|
|
672
|
+
"PUT",
|
|
673
|
+
f"{API_ENDPOINT_DEVICES}{serial}{API_ENDPOINT_SWITCH_OTHER}",
|
|
674
|
+
params={"channelNo": channel_number, "enable": enable, "switchType": status_type},
|
|
675
|
+
retry_401=True,
|
|
676
|
+
max_retries=max_retries,
|
|
677
|
+
)
|
|
678
|
+
self._ensure_ok(json_output, "Could not set the switch")
|
|
564
679
|
return True
|
|
565
680
|
|
|
566
681
|
def set_camera_defence(
|
|
@@ -573,57 +688,22 @@ class EzvizClient:
|
|
|
573
688
|
max_retries: int = 0,
|
|
574
689
|
) -> bool:
|
|
575
690
|
"""Enable/Disable motion detection on camera."""
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
except requests.HTTPError as err:
|
|
594
|
-
if err.response.status_code == 401:
|
|
595
|
-
# session is wrong, need to relogin
|
|
596
|
-
self.login()
|
|
597
|
-
return self.set_camera_defence(serial, enable, max_retries + 1)
|
|
598
|
-
|
|
599
|
-
raise HTTPError from err
|
|
600
|
-
|
|
601
|
-
try:
|
|
602
|
-
json_output = req.json()
|
|
603
|
-
|
|
604
|
-
except ValueError as err:
|
|
605
|
-
raise PyEzvizError(
|
|
606
|
-
"Impossible to decode response: "
|
|
607
|
-
+ str(err)
|
|
608
|
-
+ "\nResponse was: "
|
|
609
|
-
+ str(req.text)
|
|
610
|
-
) from err
|
|
611
|
-
|
|
612
|
-
if json_output["meta"]["code"] != 200:
|
|
613
|
-
if json_output["meta"]["code"] == 504:
|
|
614
|
-
_LOGGER.warning(
|
|
615
|
-
"Arm or disarm for camera %s timed out. Retrying %s of %s",
|
|
616
|
-
serial,
|
|
617
|
-
max_retries,
|
|
618
|
-
MAX_RETRIES,
|
|
619
|
-
)
|
|
620
|
-
return self.set_camera_defence(serial, enable, max_retries + 1)
|
|
621
|
-
|
|
622
|
-
raise PyEzvizError(
|
|
623
|
-
f"Could not arm or disarm Camera {serial}: Got {json_output})"
|
|
624
|
-
)
|
|
625
|
-
|
|
626
|
-
return True
|
|
691
|
+
json_output = self._retry_json(
|
|
692
|
+
lambda: self._request_json(
|
|
693
|
+
"PUT",
|
|
694
|
+
f"{API_ENDPOINT_DEVICES}{serial}/{channel_no}{API_ENDPOINT_CHANGE_DEFENCE_STATUS}",
|
|
695
|
+
data={"type": arm_type, "status": enable, "actor": actor},
|
|
696
|
+
retry_401=True,
|
|
697
|
+
max_retries=0,
|
|
698
|
+
),
|
|
699
|
+
attempts=max_retries,
|
|
700
|
+
should_retry=lambda p: self._meta_code(p) == 504,
|
|
701
|
+
log="arm_disarm_timeout",
|
|
702
|
+
serial=serial,
|
|
703
|
+
)
|
|
704
|
+
if self._meta_code(json_output) != 200:
|
|
705
|
+
raise PyEzvizError(f"Could not arm or disarm Camera {serial}: Got {json_output})")
|
|
706
|
+
return True
|
|
627
707
|
|
|
628
708
|
def set_battery_camera_work_mode(self, serial: str, value: int) -> bool:
|
|
629
709
|
"""Set battery camera work mode."""
|
|
@@ -676,36 +756,9 @@ class EzvizClient:
|
|
|
676
756
|
).prepare()
|
|
677
757
|
req_prep.url = full_url + "?" + params_str
|
|
678
758
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
timeout=self._timeout,
|
|
683
|
-
)
|
|
684
|
-
|
|
685
|
-
req.raise_for_status()
|
|
686
|
-
|
|
687
|
-
except requests.HTTPError as err:
|
|
688
|
-
if err.response.status_code == 401:
|
|
689
|
-
# session is wrong, need to relogin
|
|
690
|
-
self.login()
|
|
691
|
-
return self.set_device_config_by_key(
|
|
692
|
-
serial, value, key, max_retries + 1
|
|
693
|
-
)
|
|
694
|
-
|
|
695
|
-
raise HTTPError from err
|
|
696
|
-
|
|
697
|
-
try:
|
|
698
|
-
json_output = req.json()
|
|
699
|
-
|
|
700
|
-
except ValueError as err:
|
|
701
|
-
raise PyEzvizError(
|
|
702
|
-
"Impossible to decode response: "
|
|
703
|
-
+ str(err)
|
|
704
|
-
+ "\nResponse was: "
|
|
705
|
-
+ str(req.text)
|
|
706
|
-
) from err
|
|
707
|
-
|
|
708
|
-
if json_output["meta"]["code"] != 200:
|
|
759
|
+
req = self._send_prepared(req_prep, retry_401=True, max_retries=max_retries)
|
|
760
|
+
json_output = self._parse_json(req)
|
|
761
|
+
if not self._meta_ok(json_output):
|
|
709
762
|
raise PyEzvizError(f"Could not set config key '${key}': Got {json_output})")
|
|
710
763
|
|
|
711
764
|
return True
|
|
@@ -737,205 +790,69 @@ class EzvizClient:
|
|
|
737
790
|
method="PUT", url=full_url, headers=headers, data=payload
|
|
738
791
|
).prepare()
|
|
739
792
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
timeout=self._timeout,
|
|
744
|
-
)
|
|
745
|
-
|
|
746
|
-
req.raise_for_status()
|
|
747
|
-
|
|
748
|
-
except requests.HTTPError as err:
|
|
749
|
-
if err.response.status_code == 401:
|
|
750
|
-
# session is wrong, need to relogin
|
|
751
|
-
self.login()
|
|
752
|
-
return self.set_device_feature_by_key(
|
|
753
|
-
serial, product_id, value, key, max_retries + 1
|
|
754
|
-
)
|
|
755
|
-
|
|
756
|
-
raise HTTPError from err
|
|
757
|
-
|
|
758
|
-
try:
|
|
759
|
-
json_output = req.json()
|
|
760
|
-
|
|
761
|
-
except ValueError as err:
|
|
762
|
-
raise PyEzvizError(
|
|
763
|
-
"Impossible to decode response: "
|
|
764
|
-
+ str(err)
|
|
765
|
-
+ "\nResponse was: "
|
|
766
|
-
+ str(req.text)
|
|
767
|
-
) from err
|
|
768
|
-
|
|
769
|
-
if json_output["meta"]["code"] != 200:
|
|
793
|
+
req = self._send_prepared(req_prep, retry_401=True, max_retries=max_retries)
|
|
794
|
+
json_output = self._parse_json(req)
|
|
795
|
+
if not self._meta_ok(json_output):
|
|
770
796
|
raise PyEzvizError(
|
|
771
|
-
f"Could not set iot-feature key '
|
|
797
|
+
f"Could not set iot-feature key '{key}': Got {json_output})"
|
|
772
798
|
)
|
|
773
799
|
|
|
774
800
|
return True
|
|
775
801
|
|
|
776
802
|
def upgrade_device(self, serial: str, max_retries: int = 0) -> bool:
|
|
777
803
|
"""Upgrade device firmware."""
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
)
|
|
786
|
-
|
|
787
|
-
req.raise_for_status()
|
|
788
|
-
|
|
789
|
-
except requests.HTTPError as err:
|
|
790
|
-
if err.response.status_code == 401:
|
|
791
|
-
# session is wrong, need to relogin
|
|
792
|
-
self.login()
|
|
793
|
-
return self.upgrade_device(serial, max_retries + 1)
|
|
794
|
-
|
|
795
|
-
raise HTTPError from err
|
|
796
|
-
|
|
797
|
-
try:
|
|
798
|
-
json_output = req.json()
|
|
799
|
-
|
|
800
|
-
except ValueError as err:
|
|
801
|
-
raise PyEzvizError(
|
|
802
|
-
"Impossible to decode response: "
|
|
803
|
-
+ str(err)
|
|
804
|
-
+ "\nResponse was: "
|
|
805
|
-
+ str(req.text)
|
|
806
|
-
) from err
|
|
807
|
-
|
|
808
|
-
if json_output["meta"]["code"] != 200:
|
|
809
|
-
raise PyEzvizError(
|
|
810
|
-
f"Could not initiate firmware upgrade: Got {json_output})"
|
|
811
|
-
)
|
|
812
|
-
|
|
804
|
+
json_output = self._request_json(
|
|
805
|
+
"PUT",
|
|
806
|
+
f"{API_ENDPOINT_UPGRADE_DEVICE}{serial}/0/upgrade",
|
|
807
|
+
retry_401=True,
|
|
808
|
+
max_retries=max_retries,
|
|
809
|
+
)
|
|
810
|
+
self._ensure_ok(json_output, "Could not initiate firmware upgrade")
|
|
813
811
|
return True
|
|
814
812
|
|
|
815
813
|
def get_storage_status(self, serial: str, max_retries: int = 0) -> Any:
|
|
816
814
|
"""Get device storage status."""
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
req = self._session.post(
|
|
822
|
-
url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICE_STORAGE_STATUS}",
|
|
815
|
+
json_output = self._retry_json(
|
|
816
|
+
lambda: self._request_json(
|
|
817
|
+
"POST",
|
|
818
|
+
API_ENDPOINT_DEVICE_STORAGE_STATUS,
|
|
823
819
|
data={"subSerial": serial},
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
return self.get_storage_status(serial, max_retries + 1)
|
|
834
|
-
|
|
835
|
-
raise HTTPError from err
|
|
836
|
-
|
|
837
|
-
try:
|
|
838
|
-
json_output = req.json()
|
|
839
|
-
|
|
840
|
-
except ValueError as err:
|
|
841
|
-
raise PyEzvizError(
|
|
842
|
-
"Impossible to decode response: "
|
|
843
|
-
+ str(err)
|
|
844
|
-
+ "\nResponse was: "
|
|
845
|
-
+ str(req.text)
|
|
846
|
-
) from err
|
|
847
|
-
|
|
848
|
-
if json_output["resultCode"] != "0":
|
|
849
|
-
if json_output["resultCode"] == "-1":
|
|
850
|
-
_LOGGER.warning(
|
|
851
|
-
"Can't get storage status from device %s, retrying %s of %s",
|
|
852
|
-
serial,
|
|
853
|
-
max_retries,
|
|
854
|
-
MAX_RETRIES,
|
|
855
|
-
)
|
|
856
|
-
return self.get_storage_status(serial, max_retries + 1)
|
|
820
|
+
retry_401=True,
|
|
821
|
+
max_retries=0,
|
|
822
|
+
),
|
|
823
|
+
attempts=max_retries,
|
|
824
|
+
should_retry=lambda p: str(p.get("resultCode")) == "-1",
|
|
825
|
+
log="storage_status_unreachable",
|
|
826
|
+
serial=serial,
|
|
827
|
+
)
|
|
828
|
+
if str(json_output.get("resultCode")) != "0":
|
|
857
829
|
raise PyEzvizError(
|
|
858
830
|
f"Could not get device storage status: Got {json_output})"
|
|
859
831
|
)
|
|
860
|
-
|
|
861
|
-
return json_output["storageStatus"]
|
|
832
|
+
return json_output.get("storageStatus")
|
|
862
833
|
|
|
863
834
|
def sound_alarm(self, serial: str, enable: int = 1, max_retries: int = 0) -> bool:
|
|
864
835
|
"""Sound alarm on a device."""
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
},
|
|
874
|
-
timeout=self._timeout,
|
|
875
|
-
)
|
|
876
|
-
|
|
877
|
-
req.raise_for_status()
|
|
878
|
-
|
|
879
|
-
except requests.HTTPError as err:
|
|
880
|
-
if err.response.status_code == 401:
|
|
881
|
-
# session is wrong, need to relogin
|
|
882
|
-
self.login()
|
|
883
|
-
return self.sound_alarm(serial, enable, max_retries + 1)
|
|
884
|
-
|
|
885
|
-
raise HTTPError from err
|
|
886
|
-
|
|
887
|
-
try:
|
|
888
|
-
json_output = req.json()
|
|
889
|
-
|
|
890
|
-
except ValueError as err:
|
|
891
|
-
raise PyEzvizError(
|
|
892
|
-
"Impossible to decode response: "
|
|
893
|
-
+ str(err)
|
|
894
|
-
+ "\nResponse was: "
|
|
895
|
-
+ str(req.text)
|
|
896
|
-
) from err
|
|
897
|
-
|
|
898
|
-
if json_output["meta"]["code"] != 200:
|
|
899
|
-
raise PyEzvizError(f"Could not set the alarm sound: Got {json_output})")
|
|
900
|
-
|
|
836
|
+
json_output = self._request_json(
|
|
837
|
+
"PUT",
|
|
838
|
+
f"{API_ENDPOINT_DEVICES}{serial}/0{API_ENDPOINT_SWITCH_SOUND_ALARM}",
|
|
839
|
+
data={"enable": enable},
|
|
840
|
+
retry_401=True,
|
|
841
|
+
max_retries=max_retries,
|
|
842
|
+
)
|
|
843
|
+
self._ensure_ok(json_output, "Could not set the alarm sound")
|
|
901
844
|
return True
|
|
902
845
|
|
|
903
846
|
def get_user_id(self, max_retries: int = 0) -> Any:
|
|
904
847
|
"""Get Ezviz userid, used by restricted api endpoints."""
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
)
|
|
914
|
-
req.raise_for_status()
|
|
915
|
-
|
|
916
|
-
except requests.HTTPError as err:
|
|
917
|
-
if err.response.status_code == 401:
|
|
918
|
-
# session is wrong, need to relogin
|
|
919
|
-
self.login()
|
|
920
|
-
return self.get_user_id(max_retries + 1)
|
|
921
|
-
|
|
922
|
-
raise HTTPError from err
|
|
923
|
-
|
|
924
|
-
try:
|
|
925
|
-
json_output = req.json()
|
|
926
|
-
|
|
927
|
-
except ValueError as err:
|
|
928
|
-
raise PyEzvizError(
|
|
929
|
-
"Impossible to decode response: "
|
|
930
|
-
+ str(err)
|
|
931
|
-
+ "\nResponse was: "
|
|
932
|
-
+ str(req.text)
|
|
933
|
-
) from err
|
|
934
|
-
|
|
935
|
-
if json_output["meta"]["code"] != 200:
|
|
936
|
-
raise PyEzvizError(f"Could get user id, Got: {json_output})")
|
|
937
|
-
|
|
938
|
-
return json_output["deviceTokenInfo"]
|
|
848
|
+
json_output = self._request_json(
|
|
849
|
+
"GET",
|
|
850
|
+
API_ENDPOINT_USER_ID,
|
|
851
|
+
retry_401=True,
|
|
852
|
+
max_retries=max_retries,
|
|
853
|
+
)
|
|
854
|
+
self._ensure_ok(json_output, "Could not get user id")
|
|
855
|
+
return json_output.get("deviceTokenInfo")
|
|
939
856
|
|
|
940
857
|
def set_video_enc(
|
|
941
858
|
self,
|
|
@@ -956,51 +873,22 @@ class EzvizClient:
|
|
|
956
873
|
if new_password and not enable == 2:
|
|
957
874
|
raise PyEzvizError("New password is only required when changing password.")
|
|
958
875
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
except requests.HTTPError as err:
|
|
977
|
-
if err.response.status_code == 401:
|
|
978
|
-
# session is wrong, need to relogin
|
|
979
|
-
self.login()
|
|
980
|
-
return self.set_video_enc(
|
|
981
|
-
serial,
|
|
982
|
-
enable,
|
|
983
|
-
camera_verification_code,
|
|
984
|
-
new_password,
|
|
985
|
-
old_password,
|
|
986
|
-
max_retries + 1,
|
|
987
|
-
)
|
|
988
|
-
|
|
989
|
-
raise HTTPError from err
|
|
990
|
-
|
|
991
|
-
try:
|
|
992
|
-
json_output = req.json()
|
|
993
|
-
|
|
994
|
-
except ValueError as err:
|
|
995
|
-
raise PyEzvizError(
|
|
996
|
-
"Impossible to decode response: "
|
|
997
|
-
+ str(err)
|
|
998
|
-
+ "\nResponse was: "
|
|
999
|
-
+ str(req.text)
|
|
1000
|
-
) from err
|
|
1001
|
-
|
|
1002
|
-
if json_output["meta"]["code"] != 200:
|
|
1003
|
-
raise PyEzvizError(f"Could not set video encryption: Got {json_output})")
|
|
876
|
+
json_output = self._request_json(
|
|
877
|
+
"PUT",
|
|
878
|
+
f"{API_ENDPOINT_DEVICES}{API_ENDPOINT_VIDEO_ENCRYPT}",
|
|
879
|
+
data={
|
|
880
|
+
"deviceSerial": serial,
|
|
881
|
+
"isEncrypt": enable,
|
|
882
|
+
"oldPassword": old_password,
|
|
883
|
+
"password": new_password,
|
|
884
|
+
"featureCode": FEATURE_CODE,
|
|
885
|
+
"validateCode": camera_verification_code,
|
|
886
|
+
"msgType": -1,
|
|
887
|
+
},
|
|
888
|
+
retry_401=True,
|
|
889
|
+
max_retries=max_retries,
|
|
890
|
+
)
|
|
891
|
+
self._ensure_ok(json_output, "Could not set video encryption")
|
|
1004
892
|
|
|
1005
893
|
return True
|
|
1006
894
|
|
|
@@ -1015,54 +903,21 @@ class EzvizClient:
|
|
|
1015
903
|
if max_retries > MAX_RETRIES:
|
|
1016
904
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
1017
905
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
)
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
if err.response.status_code == 401:
|
|
1033
|
-
# session is wrong, need to relogin
|
|
1034
|
-
self.login()
|
|
1035
|
-
return self.reboot_camera(
|
|
1036
|
-
serial,
|
|
1037
|
-
delay,
|
|
1038
|
-
operation,
|
|
1039
|
-
max_retries + 1,
|
|
1040
|
-
)
|
|
1041
|
-
|
|
1042
|
-
raise HTTPError from err
|
|
1043
|
-
|
|
1044
|
-
try:
|
|
1045
|
-
json_output = req.json()
|
|
1046
|
-
|
|
1047
|
-
except ValueError as err:
|
|
1048
|
-
raise PyEzvizError(
|
|
1049
|
-
"Impossible to decode response: "
|
|
1050
|
-
+ str(err)
|
|
1051
|
-
+ "\nResponse was: "
|
|
1052
|
-
+ str(req.text)
|
|
1053
|
-
) from err
|
|
1054
|
-
|
|
1055
|
-
if json_output["resultCode"] != "0":
|
|
1056
|
-
if json_output["resultCode"] == "-1":
|
|
1057
|
-
_LOGGER.warning(
|
|
1058
|
-
"Unable to reboot camera, camera %s is unreachable, retrying %s of %s",
|
|
1059
|
-
serial,
|
|
1060
|
-
max_retries,
|
|
1061
|
-
MAX_RETRIES,
|
|
1062
|
-
)
|
|
1063
|
-
return self.reboot_camera(serial, delay, operation, max_retries + 1)
|
|
906
|
+
json_output = self._retry_json(
|
|
907
|
+
lambda: self._request_json(
|
|
908
|
+
"POST",
|
|
909
|
+
f"{API_ENDPOINT_DEVICE_SYS_OPERATION}{serial}",
|
|
910
|
+
data={"oper": operation, "deviceSerial": serial, "delay": delay},
|
|
911
|
+
retry_401=True,
|
|
912
|
+
max_retries=0,
|
|
913
|
+
),
|
|
914
|
+
attempts=max_retries,
|
|
915
|
+
should_retry=lambda p: str(p.get("resultCode")) == "-1",
|
|
916
|
+
log="reboot_unreachable",
|
|
917
|
+
serial=serial,
|
|
918
|
+
)
|
|
919
|
+
if str(json_output.get("resultCode")) not in ("0", 0):
|
|
1064
920
|
raise PyEzvizError(f"Could not reboot device {json_output})")
|
|
1065
|
-
|
|
1066
921
|
return True
|
|
1067
922
|
|
|
1068
923
|
def set_offline_notification(
|
|
@@ -1076,100 +931,43 @@ class EzvizClient:
|
|
|
1076
931
|
if max_retries > MAX_RETRIES:
|
|
1077
932
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
1078
933
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
timeout=self._timeout,
|
|
934
|
+
attempts = max(0, max_retries)
|
|
935
|
+
for attempt in range(attempts + 1):
|
|
936
|
+
json_output = self._request_json(
|
|
937
|
+
"POST",
|
|
938
|
+
API_ENDPOINT_OFFLINE_NOTIFY,
|
|
939
|
+
data={"reqType": req_type, "serial": serial, "status": enable},
|
|
940
|
+
retry_401=True,
|
|
941
|
+
max_retries=0,
|
|
1088
942
|
)
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
if err.response.status_code == 401:
|
|
1094
|
-
# session is wrong, need to relogin
|
|
1095
|
-
self.login()
|
|
1096
|
-
return self.set_offline_notification(
|
|
1097
|
-
serial,
|
|
1098
|
-
enable,
|
|
1099
|
-
req_type,
|
|
1100
|
-
max_retries + 1,
|
|
1101
|
-
)
|
|
1102
|
-
|
|
1103
|
-
raise HTTPError from err
|
|
1104
|
-
|
|
1105
|
-
try:
|
|
1106
|
-
json_output = req.json()
|
|
1107
|
-
|
|
1108
|
-
except ValueError as err:
|
|
1109
|
-
raise PyEzvizError(
|
|
1110
|
-
"Impossible to decode response: "
|
|
1111
|
-
+ str(err)
|
|
1112
|
-
+ "\nResponse was: "
|
|
1113
|
-
+ str(req.text)
|
|
1114
|
-
) from err
|
|
1115
|
-
|
|
1116
|
-
if json_output["resultCode"] != "0":
|
|
1117
|
-
if json_output["resultCode"] == "-1":
|
|
943
|
+
result = str(json_output.get("resultCode"))
|
|
944
|
+
if result == "0":
|
|
945
|
+
return True
|
|
946
|
+
if result == "-1" and attempt < attempts:
|
|
1118
947
|
_LOGGER.warning(
|
|
1119
|
-
"Unable to set offline notification, camera %s is unreachable, retrying %s
|
|
948
|
+
"Unable to set offline notification, camera %s is unreachable, retrying %s/%s",
|
|
1120
949
|
serial,
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
)
|
|
1124
|
-
return self.set_offline_notification(
|
|
1125
|
-
serial, enable, req_type, max_retries + 1
|
|
950
|
+
attempt + 1,
|
|
951
|
+
attempts,
|
|
1126
952
|
)
|
|
953
|
+
continue
|
|
1127
954
|
raise PyEzvizError(f"Could not set offline notification {json_output})")
|
|
955
|
+
raise PyEzvizError("Could not set offline notification: exceeded retries")
|
|
1128
956
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
if max_retries > MAX_RETRIES:
|
|
1135
|
-
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
1136
|
-
|
|
1137
|
-
try:
|
|
1138
|
-
req = self._session.get(
|
|
1139
|
-
url=f"https://{self._token['api_url']}{API_ENDPOINT_GROUP_DEFENCE_MODE}",
|
|
1140
|
-
params={
|
|
1141
|
-
"groupId": -1,
|
|
1142
|
-
},
|
|
1143
|
-
timeout=self._timeout,
|
|
1144
|
-
)
|
|
1145
|
-
|
|
1146
|
-
req.raise_for_status()
|
|
1147
|
-
|
|
1148
|
-
except requests.HTTPError as err:
|
|
1149
|
-
if err.response.status_code == 401:
|
|
1150
|
-
# session is wrong, need to relogin
|
|
1151
|
-
self.login()
|
|
1152
|
-
return self.get_group_defence_mode(max_retries + 1)
|
|
1153
|
-
|
|
1154
|
-
raise HTTPError from err
|
|
1155
|
-
|
|
1156
|
-
try:
|
|
1157
|
-
json_output = req.json()
|
|
1158
|
-
|
|
1159
|
-
except ValueError as err:
|
|
1160
|
-
raise PyEzvizError(
|
|
1161
|
-
"Impossible to decode response: "
|
|
1162
|
-
+ str(err)
|
|
1163
|
-
+ "\nResponse was: "
|
|
1164
|
-
+ str(req.text)
|
|
1165
|
-
) from err
|
|
1166
|
-
|
|
1167
|
-
if json_output["meta"]["code"] != 200:
|
|
1168
|
-
raise PyEzvizError(
|
|
1169
|
-
f"Could not get group defence status: Got {json_output})"
|
|
1170
|
-
)
|
|
957
|
+
def get_group_defence_mode(self, max_retries: int = 0) -> Any:
|
|
958
|
+
"""Get group arm status. The alarm arm/disarm concept on 1st page of app."""
|
|
959
|
+
if max_retries > MAX_RETRIES:
|
|
960
|
+
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
1171
961
|
|
|
1172
|
-
|
|
962
|
+
json_output = self._request_json(
|
|
963
|
+
"GET",
|
|
964
|
+
API_ENDPOINT_GROUP_DEFENCE_MODE,
|
|
965
|
+
params={"groupId": -1},
|
|
966
|
+
retry_401=True,
|
|
967
|
+
max_retries=max_retries,
|
|
968
|
+
)
|
|
969
|
+
self._ensure_ok(json_output, "Could not get group defence status")
|
|
970
|
+
return json_output.get("mode")
|
|
1173
971
|
|
|
1174
972
|
# Not tested
|
|
1175
973
|
def cancel_alarm_device(self, serial: str, max_retries: int = 0) -> bool:
|
|
@@ -1177,92 +975,85 @@ class EzvizClient:
|
|
|
1177
975
|
if max_retries > MAX_RETRIES:
|
|
1178
976
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
1179
977
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
except requests.HTTPError as err:
|
|
1190
|
-
if err.response.status_code == 401:
|
|
1191
|
-
# session is wrong, need to relogin
|
|
1192
|
-
self.login()
|
|
1193
|
-
return self.sound_alarm(serial, max_retries + 1)
|
|
1194
|
-
|
|
1195
|
-
raise HTTPError from err
|
|
1196
|
-
|
|
1197
|
-
try:
|
|
1198
|
-
json_output = req.json()
|
|
1199
|
-
|
|
1200
|
-
except ValueError as err:
|
|
1201
|
-
raise PyEzvizError(
|
|
1202
|
-
"Impossible to decode response: "
|
|
1203
|
-
+ str(err)
|
|
1204
|
-
+ "\nResponse was: "
|
|
1205
|
-
+ str(req.text)
|
|
1206
|
-
) from err
|
|
978
|
+
json_output = self._request_json(
|
|
979
|
+
"POST",
|
|
980
|
+
API_ENDPOINT_CANCEL_ALARM,
|
|
981
|
+
data={"subSerial": serial},
|
|
982
|
+
retry_401=True,
|
|
983
|
+
max_retries=max_retries,
|
|
984
|
+
)
|
|
985
|
+
self._ensure_ok(json_output, "Could not cancel alarm siren")
|
|
986
|
+
return True
|
|
1207
987
|
|
|
1208
|
-
|
|
1209
|
-
|
|
988
|
+
def load_devices(self, refresh: bool = True) -> dict[Any, Any]:
|
|
989
|
+
"""Build status maps for cameras and light bulbs.
|
|
1210
990
|
|
|
1211
|
-
|
|
991
|
+
refresh: if True, camera.status() may perform network fetches (e.g. alarms).
|
|
992
|
+
Returns a combined mapping of serial -> status dict for both cameras and bulbs.
|
|
993
|
+
"""
|
|
994
|
+
# Reset caches to reflect the current device roster
|
|
995
|
+
self._cameras.clear()
|
|
996
|
+
self._light_bulbs.clear()
|
|
1212
997
|
|
|
1213
|
-
|
|
1214
|
-
|
|
998
|
+
# Build lightweight records for clean gating/selection
|
|
999
|
+
records = cast(dict[str, EzvizDeviceRecord], self.get_device_records(None))
|
|
1000
|
+
supported_categories = self.SUPPORTED_CATEGORIES
|
|
1215
1001
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
DeviceCatagories.COMMON_DEVICE_CATEGORY.value,
|
|
1219
|
-
DeviceCatagories.CAMERA_DEVICE_CATEGORY.value,
|
|
1220
|
-
DeviceCatagories.BATTERY_CAMERA_DEVICE_CATEGORY.value,
|
|
1221
|
-
DeviceCatagories.DOORBELL_DEVICE_CATEGORY.value,
|
|
1222
|
-
DeviceCatagories.BASE_STATION_DEVICE_CATEGORY.value,
|
|
1223
|
-
DeviceCatagories.CAT_EYE_CATEGORY.value,
|
|
1224
|
-
DeviceCatagories.LIGHTING.value,
|
|
1225
|
-
]
|
|
1226
|
-
|
|
1227
|
-
for device, data in devices.items():
|
|
1228
|
-
if data["deviceInfos"]["deviceCategory"] in supported_categories:
|
|
1002
|
+
for device, rec in records.items():
|
|
1003
|
+
if rec.device_category in supported_categories:
|
|
1229
1004
|
# Add support for connected HikVision cameras
|
|
1230
1005
|
if (
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
and not data["deviceInfos"]["hik"]
|
|
1006
|
+
rec.device_category == DeviceCatagories.COMMON_DEVICE_CATEGORY.value
|
|
1007
|
+
and not (rec.raw.get("deviceInfos") or {}).get("hik")
|
|
1234
1008
|
):
|
|
1235
1009
|
continue
|
|
1236
1010
|
|
|
1237
|
-
if
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1011
|
+
if rec.device_category == DeviceCatagories.LIGHTING.value:
|
|
1012
|
+
try:
|
|
1013
|
+
# Create a light bulb object
|
|
1014
|
+
self._light_bulbs[device] = EzvizLightBulb(
|
|
1015
|
+
self, device, dict(rec.raw)
|
|
1016
|
+
).status()
|
|
1017
|
+
except (PyEzvizError, KeyError, TypeError, ValueError) as err: # pragma: no cover - defensive
|
|
1018
|
+
_LOGGER.warning(
|
|
1019
|
+
"load_device_failed: serial=%s code=%s msg=%s",
|
|
1020
|
+
device,
|
|
1021
|
+
"load_error",
|
|
1022
|
+
str(err),
|
|
1023
|
+
)
|
|
1245
1024
|
else:
|
|
1246
|
-
|
|
1247
|
-
|
|
1025
|
+
try:
|
|
1026
|
+
# Create camera object
|
|
1027
|
+
cam = EzvizCamera(self, device, dict(rec.raw))
|
|
1028
|
+
self._cameras[device] = cam.status(refresh=refresh)
|
|
1029
|
+
except (PyEzvizError, KeyError, TypeError, ValueError) as err: # pragma: no cover - defensive
|
|
1030
|
+
_LOGGER.warning(
|
|
1031
|
+
"load_device_failed: serial=%s code=%s msg=%s",
|
|
1032
|
+
device,
|
|
1033
|
+
"load_error",
|
|
1034
|
+
str(err),
|
|
1035
|
+
)
|
|
1248
1036
|
|
|
1249
1037
|
return {**self._cameras, **self._light_bulbs}
|
|
1250
1038
|
|
|
1251
|
-
def load_cameras(self) -> dict[Any, Any]:
|
|
1252
|
-
"""Load and return all
|
|
1039
|
+
def load_cameras(self, refresh: bool = True) -> dict[Any, Any]:
|
|
1040
|
+
"""Load and return all camera status mappings.
|
|
1253
1041
|
|
|
1254
|
-
|
|
1042
|
+
refresh: pass-through to load_devices() to control network fetches.
|
|
1043
|
+
"""
|
|
1044
|
+
self.load_devices(refresh=refresh)
|
|
1255
1045
|
return self._cameras
|
|
1256
1046
|
|
|
1257
|
-
def load_light_bulbs(self) -> dict[Any, Any]:
|
|
1258
|
-
"""Load light
|
|
1047
|
+
def load_light_bulbs(self, refresh: bool = True) -> dict[Any, Any]:
|
|
1048
|
+
"""Load and return all light bulb status mappings.
|
|
1259
1049
|
|
|
1260
|
-
|
|
1050
|
+
refresh: pass-through to load_devices().
|
|
1051
|
+
"""
|
|
1052
|
+
self.load_devices(refresh=refresh)
|
|
1261
1053
|
return self._light_bulbs
|
|
1262
1054
|
|
|
1263
1055
|
def get_device_infos(self, serial: str | None = None) -> dict[Any, Any]:
|
|
1264
1056
|
"""Load all devices and build dict per device serial."""
|
|
1265
|
-
|
|
1266
1057
|
devices = self._get_page_list()
|
|
1267
1058
|
result: dict[str, Any] = {}
|
|
1268
1059
|
_res_id = "NONE"
|
|
@@ -1312,7 +1103,20 @@ class EzvizClient:
|
|
|
1312
1103
|
if not serial:
|
|
1313
1104
|
return result
|
|
1314
1105
|
|
|
1315
|
-
return result.get(serial, {})
|
|
1106
|
+
return cast(dict[Any, Any], result.get(serial, {}))
|
|
1107
|
+
|
|
1108
|
+
def get_device_records(
|
|
1109
|
+
self, serial: str | None = None
|
|
1110
|
+
) -> dict[str, EzvizDeviceRecord] | EzvizDeviceRecord | dict[Any, Any]:
|
|
1111
|
+
"""Return devices as EzvizDeviceRecord mapping (or single record).
|
|
1112
|
+
|
|
1113
|
+
Falls back to raw when a specific serial is requested but not found.
|
|
1114
|
+
"""
|
|
1115
|
+
devices = self.get_device_infos()
|
|
1116
|
+
records = build_device_records_map(devices)
|
|
1117
|
+
if serial is None:
|
|
1118
|
+
return records
|
|
1119
|
+
return records.get(serial) or devices.get(serial, {})
|
|
1316
1120
|
|
|
1317
1121
|
def ptz_control(
|
|
1318
1122
|
self, command: str, serial: str, action: str, speed: int = 5
|
|
@@ -1323,37 +1127,26 @@ class EzvizClient:
|
|
|
1323
1127
|
if action is None:
|
|
1324
1128
|
raise PyEzvizError("Trying to call ptzControl without action")
|
|
1325
1129
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
req.raise_for_status()
|
|
1341
|
-
|
|
1342
|
-
except requests.HTTPError as err:
|
|
1343
|
-
raise HTTPError from err
|
|
1344
|
-
|
|
1345
|
-
try:
|
|
1346
|
-
json_output = req.json()
|
|
1347
|
-
|
|
1348
|
-
except ValueError as err:
|
|
1349
|
-
raise PyEzvizError(
|
|
1350
|
-
"Impossible to decode response: "
|
|
1351
|
-
+ str(err)
|
|
1352
|
-
+ "\nResponse was: "
|
|
1353
|
-
+ str(req.text)
|
|
1354
|
-
) from err
|
|
1130
|
+
json_output = self._request_json(
|
|
1131
|
+
"PUT",
|
|
1132
|
+
f"{API_ENDPOINT_DEVICES}{serial}{API_ENDPOINT_PTZCONTROL}",
|
|
1133
|
+
data={
|
|
1134
|
+
"command": command,
|
|
1135
|
+
"action": action,
|
|
1136
|
+
"channelNo": 1,
|
|
1137
|
+
"speed": speed,
|
|
1138
|
+
"uuid": str(uuid4()),
|
|
1139
|
+
"serial": serial,
|
|
1140
|
+
},
|
|
1141
|
+
retry_401=False,
|
|
1142
|
+
)
|
|
1355
1143
|
|
|
1356
|
-
_LOGGER.debug(
|
|
1144
|
+
_LOGGER.debug(
|
|
1145
|
+
"http_debug: serial=%s code=%s msg=%s",
|
|
1146
|
+
serial,
|
|
1147
|
+
self._meta_code(json_output),
|
|
1148
|
+
"ptz_control",
|
|
1149
|
+
)
|
|
1357
1150
|
|
|
1358
1151
|
return True
|
|
1359
1152
|
|
|
@@ -1380,13 +1173,11 @@ class EzvizClient:
|
|
|
1380
1173
|
"resultDes": str # Status message in chinese
|
|
1381
1174
|
}
|
|
1382
1175
|
"""
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
req = self._session.post(
|
|
1389
|
-
url=f"https://{self._token['api_url']}{API_ENDPOINT_CAM_ENCRYPTKEY}",
|
|
1176
|
+
attempts = max(0, max_retries)
|
|
1177
|
+
for attempt in range(attempts + 1):
|
|
1178
|
+
json_output = self._request_json(
|
|
1179
|
+
"POST",
|
|
1180
|
+
API_ENDPOINT_CAM_ENCRYPTKEY,
|
|
1390
1181
|
data={
|
|
1391
1182
|
"checkcode": smscode,
|
|
1392
1183
|
"serial": serial,
|
|
@@ -1396,50 +1187,30 @@ class EzvizClient:
|
|
|
1396
1187
|
"featureCode": FEATURE_CODE,
|
|
1397
1188
|
"sessionId": self._token["session_id"],
|
|
1398
1189
|
},
|
|
1399
|
-
|
|
1190
|
+
retry_401=True,
|
|
1191
|
+
max_retries=0,
|
|
1400
1192
|
)
|
|
1401
1193
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
if
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
return
|
|
1409
|
-
|
|
1410
|
-
raise HTTPError from err
|
|
1411
|
-
|
|
1412
|
-
try:
|
|
1413
|
-
json_output = req.json()
|
|
1414
|
-
|
|
1415
|
-
except ValueError as err:
|
|
1416
|
-
raise PyEzvizError(
|
|
1417
|
-
"Impossible to decode response: "
|
|
1418
|
-
+ str(err)
|
|
1419
|
-
+ "\nResponse was: "
|
|
1420
|
-
+ str(req.text)
|
|
1421
|
-
) from err
|
|
1422
|
-
|
|
1423
|
-
if json_output["resultCode"] == "20002":
|
|
1424
|
-
raise EzvizAuthVerificationCode(f"MFA code required: Got {json_output})")
|
|
1425
|
-
|
|
1426
|
-
if json_output["resultCode"] == 2009:
|
|
1427
|
-
raise DeviceException(f"Device not reachable: Got {json_output})")
|
|
1428
|
-
|
|
1429
|
-
if json_output["resultCode"] != "0":
|
|
1430
|
-
if json_output["resultCode"] == "-1":
|
|
1194
|
+
code = str(json_output.get("resultCode"))
|
|
1195
|
+
if code == "20002":
|
|
1196
|
+
raise EzvizAuthVerificationCode(f"MFA code required: Got {json_output})")
|
|
1197
|
+
if code == "2009":
|
|
1198
|
+
raise DeviceException(f"Device not reachable: Got {json_output})")
|
|
1199
|
+
if code == "0":
|
|
1200
|
+
return json_output.get("encryptkey")
|
|
1201
|
+
if code == "-1" and attempt < attempts:
|
|
1431
1202
|
_LOGGER.warning(
|
|
1432
|
-
"
|
|
1203
|
+
"http_retry: serial=%s code=%s msg=%s",
|
|
1433
1204
|
serial,
|
|
1434
|
-
|
|
1435
|
-
|
|
1205
|
+
code,
|
|
1206
|
+
"cam_key_not_found",
|
|
1436
1207
|
)
|
|
1437
|
-
|
|
1208
|
+
continue
|
|
1438
1209
|
raise PyEzvizError(
|
|
1439
1210
|
f"Could not get camera encryption key: Got {json_output})"
|
|
1440
1211
|
)
|
|
1441
1212
|
|
|
1442
|
-
|
|
1213
|
+
raise PyEzvizError("Could not get camera encryption key: exceeded retries")
|
|
1443
1214
|
|
|
1444
1215
|
def get_cam_auth_code(
|
|
1445
1216
|
self,
|
|
@@ -1474,7 +1245,6 @@ class EzvizClient:
|
|
|
1474
1245
|
}
|
|
1475
1246
|
}
|
|
1476
1247
|
"""
|
|
1477
|
-
|
|
1478
1248
|
if max_retries > MAX_RETRIES:
|
|
1479
1249
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
1480
1250
|
|
|
@@ -1484,43 +1254,21 @@ class EzvizClient:
|
|
|
1484
1254
|
"senderType": sender_type,
|
|
1485
1255
|
}
|
|
1486
1256
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
req.raise_for_status()
|
|
1495
|
-
|
|
1496
|
-
except requests.HTTPError as err:
|
|
1497
|
-
if err.response.status_code == 401:
|
|
1498
|
-
# session is wrong, need to relogin
|
|
1499
|
-
self.login()
|
|
1500
|
-
return self.get_cam_auth_code(
|
|
1501
|
-
serial, encrypt_pwd, msg_auth_code, max_retries + 1
|
|
1502
|
-
)
|
|
1503
|
-
|
|
1504
|
-
raise HTTPError from err
|
|
1505
|
-
|
|
1506
|
-
try:
|
|
1507
|
-
json_output = req.json()
|
|
1508
|
-
|
|
1509
|
-
except ValueError as err:
|
|
1510
|
-
raise PyEzvizError(
|
|
1511
|
-
"Impossible to decode response: "
|
|
1512
|
-
+ str(err)
|
|
1513
|
-
+ "\nResponse was: "
|
|
1514
|
-
+ str(req.text)
|
|
1515
|
-
) from err
|
|
1257
|
+
json_output = self._request_json(
|
|
1258
|
+
"GET",
|
|
1259
|
+
f"{API_ENDPOINT_CAM_AUTH_CODE}{serial}",
|
|
1260
|
+
params=params,
|
|
1261
|
+
retry_401=True,
|
|
1262
|
+
max_retries=max_retries,
|
|
1263
|
+
)
|
|
1516
1264
|
|
|
1517
|
-
if json_output
|
|
1265
|
+
if self._meta_code(json_output) == 80000:
|
|
1518
1266
|
raise EzvizAuthVerificationCode("Operation requires 2FA check")
|
|
1519
1267
|
|
|
1520
|
-
if json_output
|
|
1268
|
+
if self._meta_code(json_output) == 2009:
|
|
1521
1269
|
raise DeviceException(f"Device not reachable: Got {json_output}")
|
|
1522
1270
|
|
|
1523
|
-
if json_output
|
|
1271
|
+
if not self._meta_ok(json_output):
|
|
1524
1272
|
raise PyEzvizError(
|
|
1525
1273
|
f"Could not get camera verification key: Got {json_output}"
|
|
1526
1274
|
)
|
|
@@ -1557,39 +1305,18 @@ class EzvizClient:
|
|
|
1557
1305
|
}
|
|
1558
1306
|
}
|
|
1559
1307
|
"""
|
|
1560
|
-
|
|
1561
1308
|
if max_retries > MAX_RETRIES:
|
|
1562
1309
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
1563
1310
|
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
req.raise_for_status()
|
|
1572
|
-
|
|
1573
|
-
except requests.HTTPError as err:
|
|
1574
|
-
if err.response.status_code == 401:
|
|
1575
|
-
# session is wrong, need to relogin
|
|
1576
|
-
self.login()
|
|
1577
|
-
return self.get_2fa_check_code(biz_type, username, max_retries + 1)
|
|
1578
|
-
|
|
1579
|
-
raise HTTPError from err
|
|
1580
|
-
|
|
1581
|
-
try:
|
|
1582
|
-
json_output = req.json()
|
|
1583
|
-
|
|
1584
|
-
except ValueError as err:
|
|
1585
|
-
raise PyEzvizError(
|
|
1586
|
-
"Impossible to decode response: "
|
|
1587
|
-
+ str(err)
|
|
1588
|
-
+ "\nResponse was: "
|
|
1589
|
-
+ str(req.text)
|
|
1590
|
-
) from err
|
|
1311
|
+
json_output = self._request_json(
|
|
1312
|
+
"POST",
|
|
1313
|
+
API_ENDPOINT_2FA_VALIDATE_POST_AUTH,
|
|
1314
|
+
data={"bizType": biz_type, "from": username},
|
|
1315
|
+
retry_401=True,
|
|
1316
|
+
max_retries=max_retries,
|
|
1317
|
+
)
|
|
1591
1318
|
|
|
1592
|
-
if json_output
|
|
1319
|
+
if not self._meta_ok(json_output):
|
|
1593
1320
|
raise PyEzvizError(
|
|
1594
1321
|
f"Could not request elevated permission: Got {json_output})"
|
|
1595
1322
|
)
|
|
@@ -1598,97 +1325,51 @@ class EzvizClient:
|
|
|
1598
1325
|
|
|
1599
1326
|
def create_panoramic(self, serial: str, max_retries: int = 0) -> Any:
|
|
1600
1327
|
"""Create panoramic image."""
|
|
1601
|
-
|
|
1602
1328
|
if max_retries > MAX_RETRIES:
|
|
1603
1329
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
1604
1330
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1331
|
+
attempts = max(0, max_retries)
|
|
1332
|
+
for attempt in range(attempts + 1):
|
|
1333
|
+
json_output = self._request_json(
|
|
1334
|
+
"POST",
|
|
1335
|
+
API_ENDPOINT_CREATE_PANORAMIC,
|
|
1608
1336
|
data={"deviceSerial": serial},
|
|
1609
|
-
|
|
1337
|
+
retry_401=True,
|
|
1338
|
+
max_retries=0,
|
|
1610
1339
|
)
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
if err.response.status_code == 401:
|
|
1616
|
-
# session is wrong, need to relogin
|
|
1617
|
-
self.login()
|
|
1618
|
-
return self.create_panoramic(serial, max_retries + 1)
|
|
1619
|
-
|
|
1620
|
-
raise HTTPError from err
|
|
1621
|
-
|
|
1622
|
-
try:
|
|
1623
|
-
json_output = req.json()
|
|
1624
|
-
|
|
1625
|
-
except ValueError as err:
|
|
1626
|
-
raise PyEzvizError(
|
|
1627
|
-
"Impossible to decode response: "
|
|
1628
|
-
+ str(err)
|
|
1629
|
-
+ "\nResponse was: "
|
|
1630
|
-
+ str(req.text)
|
|
1631
|
-
) from err
|
|
1632
|
-
|
|
1633
|
-
if json_output["resultCode"] != "0":
|
|
1634
|
-
if json_output["resultCode"] == "-1":
|
|
1340
|
+
result = str(json_output.get("resultCode"))
|
|
1341
|
+
if result == "0":
|
|
1342
|
+
return json_output
|
|
1343
|
+
if result == "-1" and attempt < attempts:
|
|
1635
1344
|
_LOGGER.warning(
|
|
1636
|
-
"Create panoramic failed on device %s retrying %s",
|
|
1345
|
+
"Create panoramic failed on device %s retrying %s/%s",
|
|
1637
1346
|
serial,
|
|
1638
|
-
|
|
1347
|
+
attempt + 1,
|
|
1348
|
+
attempts,
|
|
1639
1349
|
)
|
|
1640
|
-
|
|
1350
|
+
continue
|
|
1641
1351
|
raise PyEzvizError(
|
|
1642
1352
|
f"Could not send command to create panoramic photo: Got {json_output})"
|
|
1643
1353
|
)
|
|
1644
|
-
|
|
1645
|
-
return json_output
|
|
1354
|
+
raise PyEzvizError("Could not send command to create panoramic photo: exceeded retries")
|
|
1646
1355
|
|
|
1647
1356
|
def return_panoramic(self, serial: str, max_retries: int = 0) -> Any:
|
|
1648
1357
|
"""Return panoramic image url list."""
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
try:
|
|
1654
|
-
req = self._session.post(
|
|
1655
|
-
url=f"https://{self._token['api_url']}{API_ENDPOINT_RETURN_PANORAMIC}",
|
|
1358
|
+
json_output = self._retry_json(
|
|
1359
|
+
lambda: self._request_json(
|
|
1360
|
+
"POST",
|
|
1361
|
+
API_ENDPOINT_RETURN_PANORAMIC,
|
|
1656
1362
|
data={"deviceSerial": serial},
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
return self.return_panoramic(serial, max_retries + 1)
|
|
1667
|
-
|
|
1668
|
-
raise HTTPError from err
|
|
1669
|
-
|
|
1670
|
-
try:
|
|
1671
|
-
json_output = req.json()
|
|
1672
|
-
|
|
1673
|
-
except ValueError as err:
|
|
1674
|
-
raise PyEzvizError(
|
|
1675
|
-
"Impossible to decode response: "
|
|
1676
|
-
+ str(err)
|
|
1677
|
-
+ "\nResponse was: "
|
|
1678
|
-
+ str(req.text)
|
|
1679
|
-
) from err
|
|
1680
|
-
|
|
1681
|
-
if json_output["resultCode"] != "0":
|
|
1682
|
-
if json_output["resultCode"] == "-1":
|
|
1683
|
-
_LOGGER.warning(
|
|
1684
|
-
"Camera %s busy or unreachable, retrying %s of %s",
|
|
1685
|
-
serial,
|
|
1686
|
-
max_retries,
|
|
1687
|
-
MAX_RETRIES,
|
|
1688
|
-
)
|
|
1689
|
-
return self.return_panoramic(serial, max_retries + 1)
|
|
1363
|
+
retry_401=True,
|
|
1364
|
+
max_retries=0,
|
|
1365
|
+
),
|
|
1366
|
+
attempts=max_retries,
|
|
1367
|
+
should_retry=lambda p: str(p.get("resultCode")) == "-1",
|
|
1368
|
+
log="panoramic_busy_or_unreachable",
|
|
1369
|
+
serial=serial,
|
|
1370
|
+
)
|
|
1371
|
+
if str(json_output.get("resultCode")) != "0":
|
|
1690
1372
|
raise PyEzvizError(f"Could retrieve panoramic photo: Got {json_output})")
|
|
1691
|
-
|
|
1692
1373
|
return json_output
|
|
1693
1374
|
|
|
1694
1375
|
def ptz_control_coordinates(
|
|
@@ -1705,34 +1386,23 @@ class EzvizClient:
|
|
|
1705
1386
|
f"Invalid Y coordinate: {y_axis}: Should be between 0 and 1 inclusive"
|
|
1706
1387
|
)
|
|
1707
1388
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
req.raise_for_status()
|
|
1720
|
-
|
|
1721
|
-
except requests.HTTPError as err:
|
|
1722
|
-
raise HTTPError from err
|
|
1723
|
-
|
|
1724
|
-
try:
|
|
1725
|
-
json_result = req.json()
|
|
1726
|
-
|
|
1727
|
-
except ValueError as err:
|
|
1728
|
-
raise PyEzvizError(
|
|
1729
|
-
"Impossible to decode response: "
|
|
1730
|
-
+ str(err)
|
|
1731
|
-
+ "\nResponse was: "
|
|
1732
|
-
+ str(req.text)
|
|
1733
|
-
) from err
|
|
1389
|
+
json_result = self._request_json(
|
|
1390
|
+
"POST",
|
|
1391
|
+
API_ENDPOINT_PANORAMIC_DEVICES_OPERATION,
|
|
1392
|
+
data={
|
|
1393
|
+
"x": f"{x_axis:.6f}",
|
|
1394
|
+
"y": f"{y_axis:.6f}",
|
|
1395
|
+
"deviceSerial": serial,
|
|
1396
|
+
},
|
|
1397
|
+
retry_401=False,
|
|
1398
|
+
)
|
|
1734
1399
|
|
|
1735
|
-
_LOGGER.debug(
|
|
1400
|
+
_LOGGER.debug(
|
|
1401
|
+
"http_debug: serial=%s code=%s msg=%s",
|
|
1402
|
+
serial,
|
|
1403
|
+
self._meta_code(json_result),
|
|
1404
|
+
"ptz_control_coordinates",
|
|
1405
|
+
)
|
|
1736
1406
|
|
|
1737
1407
|
return True
|
|
1738
1408
|
|
|
@@ -1746,50 +1416,33 @@ class EzvizClient:
|
|
|
1746
1416
|
|
|
1747
1417
|
Raises:
|
|
1748
1418
|
PyEzvizError: If max retries are exceeded or if the response indicates failure.
|
|
1749
|
-
HTTPError: If an HTTP error occurs (other than a 401, which triggers re-login).
|
|
1750
|
-
|
|
1751
|
-
Returns:
|
|
1752
|
-
bool: True if the operation was successful.
|
|
1753
|
-
|
|
1754
|
-
"""
|
|
1755
|
-
try:
|
|
1756
|
-
headers = self._session.headers
|
|
1757
|
-
headers.update({"Content-Type": "application/json"})
|
|
1758
|
-
|
|
1759
|
-
req = self._session.put(
|
|
1760
|
-
url=f"https://{self._token['api_url']}{API_ENDPOINT_IOT_ACTION}{serial}{API_ENDPOINT_REMOTE_UNLOCK}",
|
|
1761
|
-
data=json.dumps(
|
|
1762
|
-
{
|
|
1763
|
-
"unLockInfo": {
|
|
1764
|
-
"bindCode": f"{FEATURE_CODE}{user_id}",
|
|
1765
|
-
"lockNo": lock_no,
|
|
1766
|
-
"streamToken": "",
|
|
1767
|
-
"userName": user_id,
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
),
|
|
1771
|
-
timeout=self._timeout,
|
|
1772
|
-
headers=headers,
|
|
1773
|
-
)
|
|
1774
|
-
|
|
1775
|
-
req.raise_for_status()
|
|
1776
|
-
|
|
1777
|
-
except requests.HTTPError as err:
|
|
1778
|
-
raise HTTPError from err
|
|
1779
|
-
|
|
1780
|
-
try:
|
|
1781
|
-
json_result = req.json()
|
|
1782
|
-
|
|
1783
|
-
except ValueError as err:
|
|
1784
|
-
raise PyEzvizError(
|
|
1785
|
-
"Impossible to decode response: "
|
|
1786
|
-
+ str(err)
|
|
1787
|
-
+ "\nResponse was: "
|
|
1788
|
-
+ str(req.text)
|
|
1789
|
-
) from err
|
|
1419
|
+
HTTPError: If an HTTP error occurs (other than a 401, which triggers re-login).
|
|
1790
1420
|
|
|
1791
|
-
|
|
1421
|
+
Returns:
|
|
1422
|
+
bool: True if the operation was successful.
|
|
1792
1423
|
|
|
1424
|
+
"""
|
|
1425
|
+
payload = {
|
|
1426
|
+
"unLockInfo": {
|
|
1427
|
+
"bindCode": f"{FEATURE_CODE}{user_id}",
|
|
1428
|
+
"lockNo": lock_no,
|
|
1429
|
+
"streamToken": "",
|
|
1430
|
+
"userName": user_id,
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
json_result = self._request_json(
|
|
1434
|
+
"PUT",
|
|
1435
|
+
f"{API_ENDPOINT_IOT_ACTION}{serial}{API_ENDPOINT_REMOTE_UNLOCK}",
|
|
1436
|
+
json_body=payload,
|
|
1437
|
+
retry_401=True,
|
|
1438
|
+
max_retries=0,
|
|
1439
|
+
)
|
|
1440
|
+
_LOGGER.debug(
|
|
1441
|
+
"http_debug: serial=%s code=%s msg=%s",
|
|
1442
|
+
serial,
|
|
1443
|
+
self._response_code(json_result),
|
|
1444
|
+
"remote_unlock",
|
|
1445
|
+
)
|
|
1793
1446
|
return True
|
|
1794
1447
|
|
|
1795
1448
|
def login(self, sms_code: int | None = None) -> dict[Any, Any]:
|
|
@@ -1832,7 +1485,7 @@ class EzvizClient:
|
|
|
1832
1485
|
if not self._token.get("service_urls"):
|
|
1833
1486
|
self._token["service_urls"] = self.get_service_urls()
|
|
1834
1487
|
|
|
1835
|
-
return self._token
|
|
1488
|
+
return cast(dict[Any, Any], self._token)
|
|
1836
1489
|
|
|
1837
1490
|
if json_result["meta"]["code"] == 403:
|
|
1838
1491
|
if self.account and self.password:
|
|
@@ -1866,7 +1519,12 @@ class EzvizClient:
|
|
|
1866
1519
|
|
|
1867
1520
|
except requests.HTTPError as err:
|
|
1868
1521
|
if err.response.status_code == 401:
|
|
1869
|
-
_LOGGER.warning(
|
|
1522
|
+
_LOGGER.warning(
|
|
1523
|
+
"http_warning: serial=%s code=%s msg=%s",
|
|
1524
|
+
"unknown",
|
|
1525
|
+
401,
|
|
1526
|
+
"logout_already_invalid",
|
|
1527
|
+
)
|
|
1870
1528
|
return True
|
|
1871
1529
|
raise HTTPError from err
|
|
1872
1530
|
|
|
@@ -1887,7 +1545,7 @@ class EzvizClient:
|
|
|
1887
1545
|
|
|
1888
1546
|
def set_camera_defence_old(self, serial: str, enable: int) -> bool:
|
|
1889
1547
|
"""Enable/Disable motion detection on camera."""
|
|
1890
|
-
cas_client = EzvizCAS(self._token)
|
|
1548
|
+
cas_client = EzvizCAS(cast(dict[str, Any], self._token))
|
|
1891
1549
|
cas_client.set_camera_defence_state(serial, enable)
|
|
1892
1550
|
|
|
1893
1551
|
return True
|
|
@@ -1908,91 +1566,33 @@ class EzvizClient:
|
|
|
1908
1566
|
+ schedule
|
|
1909
1567
|
+ "]}]}"
|
|
1910
1568
|
)
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
)
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
self.login()
|
|
1926
|
-
return self.api_set_defence_schedule(
|
|
1927
|
-
serial, schedule, enable, max_retries + 1
|
|
1928
|
-
)
|
|
1929
|
-
|
|
1930
|
-
raise HTTPError from err
|
|
1931
|
-
|
|
1932
|
-
try:
|
|
1933
|
-
json_output = req.json()
|
|
1934
|
-
|
|
1935
|
-
except ValueError as err:
|
|
1936
|
-
raise PyEzvizError(
|
|
1937
|
-
"Impossible to decode response: "
|
|
1938
|
-
+ str(err)
|
|
1939
|
-
+ "\nResponse was: "
|
|
1940
|
-
+ str(req.text)
|
|
1941
|
-
) from err
|
|
1942
|
-
|
|
1943
|
-
if json_output["resultCode"] != "0":
|
|
1944
|
-
if json_output["resultCode"] == "-1":
|
|
1945
|
-
_LOGGER.warning(
|
|
1946
|
-
"Camara %s offline or unreachable, retrying %s of %s",
|
|
1947
|
-
serial,
|
|
1948
|
-
max_retries,
|
|
1949
|
-
MAX_RETRIES,
|
|
1950
|
-
)
|
|
1951
|
-
return self.api_set_defence_schedule(
|
|
1952
|
-
serial, schedule, enable, max_retries + 1
|
|
1953
|
-
)
|
|
1569
|
+
json_output = self._retry_json(
|
|
1570
|
+
lambda: self._request_json(
|
|
1571
|
+
"POST",
|
|
1572
|
+
API_ENDPOINT_SET_DEFENCE_SCHEDULE,
|
|
1573
|
+
data={"devTimingPlan": schedulestring},
|
|
1574
|
+
retry_401=True,
|
|
1575
|
+
max_retries=0,
|
|
1576
|
+
),
|
|
1577
|
+
attempts=max_retries,
|
|
1578
|
+
should_retry=lambda p: str(p.get("resultCode")) == "-1",
|
|
1579
|
+
log="defence_schedule_offline_or_unreachable",
|
|
1580
|
+
serial=serial,
|
|
1581
|
+
)
|
|
1582
|
+
if str(json_output.get("resultCode")) not in ("0", 0):
|
|
1954
1583
|
raise PyEzvizError(f"Could not set the schedule: Got {json_output})")
|
|
1955
|
-
|
|
1956
1584
|
return True
|
|
1957
1585
|
|
|
1958
1586
|
def api_set_defence_mode(self, mode: DefenseModeType, max_retries: int = 0) -> bool:
|
|
1959
1587
|
"""Set defence mode for all devices. The alarm panel from main page is used."""
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
},
|
|
1969
|
-
timeout=self._timeout,
|
|
1970
|
-
)
|
|
1971
|
-
|
|
1972
|
-
req.raise_for_status()
|
|
1973
|
-
|
|
1974
|
-
except requests.HTTPError as err:
|
|
1975
|
-
if err.response.status_code == 401:
|
|
1976
|
-
# session is wrong, need to relogin
|
|
1977
|
-
self.login()
|
|
1978
|
-
return self.api_set_defence_mode(mode, max_retries + 1)
|
|
1979
|
-
|
|
1980
|
-
raise HTTPError from err
|
|
1981
|
-
|
|
1982
|
-
try:
|
|
1983
|
-
json_output = req.json()
|
|
1984
|
-
|
|
1985
|
-
except ValueError as err:
|
|
1986
|
-
raise PyEzvizError(
|
|
1987
|
-
"Impossible to decode response: "
|
|
1988
|
-
+ str(err)
|
|
1989
|
-
+ "\nResponse was: "
|
|
1990
|
-
+ str(req.text)
|
|
1991
|
-
) from err
|
|
1992
|
-
|
|
1993
|
-
if json_output["meta"]["code"] != 200:
|
|
1994
|
-
raise PyEzvizError(f"Could not set defence mode: Got {json_output})")
|
|
1995
|
-
|
|
1588
|
+
json_output = self._request_json(
|
|
1589
|
+
"POST",
|
|
1590
|
+
API_ENDPOINT_SWITCH_DEFENCE_MODE,
|
|
1591
|
+
data={"groupId": -1, "mode": mode},
|
|
1592
|
+
retry_401=True,
|
|
1593
|
+
max_retries=max_retries,
|
|
1594
|
+
)
|
|
1595
|
+
self._ensure_ok(json_output, "Could not set defence mode")
|
|
1996
1596
|
return True
|
|
1997
1597
|
|
|
1998
1598
|
def do_not_disturb(
|
|
@@ -2003,34 +1603,14 @@ class EzvizClient:
|
|
|
2003
1603
|
max_retries: int = 0,
|
|
2004
1604
|
) -> bool:
|
|
2005
1605
|
"""Set do not disturb on camera with specified serial."""
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
)
|
|
2015
|
-
req.raise_for_status()
|
|
2016
|
-
|
|
2017
|
-
except requests.HTTPError as err:
|
|
2018
|
-
if err.response.status_code == 401:
|
|
2019
|
-
# session is wrong, need to re-log-in
|
|
2020
|
-
self.login()
|
|
2021
|
-
return self.do_not_disturb(serial, enable, channelno, max_retries + 1)
|
|
2022
|
-
|
|
2023
|
-
raise HTTPError from err
|
|
2024
|
-
|
|
2025
|
-
try:
|
|
2026
|
-
json_output = req.json()
|
|
2027
|
-
|
|
2028
|
-
except ValueError as err:
|
|
2029
|
-
raise PyEzvizError("Could not decode response:" + str(err)) from err
|
|
2030
|
-
|
|
2031
|
-
if json_output["meta"]["code"] != 200:
|
|
2032
|
-
raise PyEzvizError(f"Could not set do not disturb: Got {json_output})")
|
|
2033
|
-
|
|
1606
|
+
json_output = self._request_json(
|
|
1607
|
+
"PUT",
|
|
1608
|
+
f"{API_ENDPOINT_V3_ALARMS}{serial}/{channelno}{API_ENDPOINT_DO_NOT_DISTURB}",
|
|
1609
|
+
data={"enable": enable},
|
|
1610
|
+
retry_401=True,
|
|
1611
|
+
max_retries=max_retries,
|
|
1612
|
+
)
|
|
1613
|
+
self._ensure_ok(json_output, "Could not set do not disturb")
|
|
2034
1614
|
return True
|
|
2035
1615
|
|
|
2036
1616
|
def set_answer_call(
|
|
@@ -2040,32 +1620,14 @@ class EzvizClient:
|
|
|
2040
1620
|
max_retries: int = 0,
|
|
2041
1621
|
) -> bool:
|
|
2042
1622
|
"""Set answer call on camera with specified serial."""
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
req.raise_for_status()
|
|
2052
|
-
|
|
2053
|
-
except requests.HTTPError as err:
|
|
2054
|
-
if err.response.status_code == 401:
|
|
2055
|
-
# session is wrong, need to re-log-in
|
|
2056
|
-
self.login()
|
|
2057
|
-
return self.set_answer_call(serial, enable, max_retries + 1)
|
|
2058
|
-
|
|
2059
|
-
raise HTTPError from err
|
|
2060
|
-
|
|
2061
|
-
try:
|
|
2062
|
-
json_output = req.json()
|
|
2063
|
-
|
|
2064
|
-
except ValueError as err:
|
|
2065
|
-
raise PyEzvizError("Could not decode response:" + str(err)) from err
|
|
2066
|
-
|
|
2067
|
-
if json_output["meta"]["code"] != 200:
|
|
2068
|
-
raise PyEzvizError(f"Could not set answer call: Got {json_output})")
|
|
1623
|
+
json_output = self._request_json(
|
|
1624
|
+
"PUT",
|
|
1625
|
+
f"{API_ENDPOINT_CALLING_NOTIFY}{serial}{API_ENDPOINT_DO_NOT_DISTURB}",
|
|
1626
|
+
data={"deviceSerial": serial, "switchStatus": enable},
|
|
1627
|
+
retry_401=True,
|
|
1628
|
+
max_retries=max_retries,
|
|
1629
|
+
)
|
|
1630
|
+
self._ensure_ok(json_output, "Could not set answer call")
|
|
2069
1631
|
|
|
2070
1632
|
return True
|
|
2071
1633
|
|
|
@@ -2100,40 +1662,18 @@ class EzvizClient:
|
|
|
2100
1662
|
"""
|
|
2101
1663
|
if max_retries > MAX_RETRIES:
|
|
2102
1664
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
req = self._session.delete(url=url, timeout=self._timeout)
|
|
2116
|
-
else:
|
|
2117
|
-
raise PyEzvizError(f"Invalid action '{action}'. Use 'add' or 'remove'.")
|
|
2118
|
-
|
|
2119
|
-
req.raise_for_status()
|
|
2120
|
-
|
|
2121
|
-
except requests.HTTPError as err:
|
|
2122
|
-
if err.response.status_code == 401:
|
|
2123
|
-
# Session might be invalid; attempt to re-login and retry.
|
|
2124
|
-
self.login()
|
|
2125
|
-
return self.manage_intelligent_app(
|
|
2126
|
-
serial, resource_id, app_name, action, max_retries + 1
|
|
2127
|
-
)
|
|
2128
|
-
raise HTTPError from err
|
|
2129
|
-
|
|
2130
|
-
try:
|
|
2131
|
-
json_output = req.json()
|
|
2132
|
-
except ValueError as err:
|
|
2133
|
-
raise PyEzvizError("Could not decode response: " + str(err)) from err
|
|
2134
|
-
|
|
2135
|
-
if json_output.get("meta", {}).get("code") != 200:
|
|
2136
|
-
raise PyEzvizError(f"Could not {action} intelligent app: Got {json_output}")
|
|
1665
|
+
url_path = f"{API_ENDPOINT_INTELLIGENT_APP}{serial}/{resource_id}/{app_name}"
|
|
1666
|
+
# Determine which method to call based on the parameter.
|
|
1667
|
+
action = action.lower()
|
|
1668
|
+
if action == "add":
|
|
1669
|
+
method = "PUT"
|
|
1670
|
+
elif action == "remove":
|
|
1671
|
+
method = "DELETE"
|
|
1672
|
+
else:
|
|
1673
|
+
raise PyEzvizError(f"Invalid action '{action}'. Use 'add' or 'remove'.")
|
|
1674
|
+
|
|
1675
|
+
json_output = self._request_json(method, url_path, retry_401=True, max_retries=max_retries)
|
|
1676
|
+
self._ensure_ok(json_output, f"Could not {action} intelligent app")
|
|
2137
1677
|
|
|
2138
1678
|
return True
|
|
2139
1679
|
|
|
@@ -2158,31 +1698,13 @@ class EzvizClient:
|
|
|
2158
1698
|
bool: True if the operation was successful.
|
|
2159
1699
|
|
|
2160
1700
|
"""
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
)
|
|
2169
|
-
req.raise_for_status()
|
|
2170
|
-
|
|
2171
|
-
except requests.HTTPError as err:
|
|
2172
|
-
if err.response.status_code == 401:
|
|
2173
|
-
# Session might be invalid; attempt to re-login and retry.
|
|
2174
|
-
self.login()
|
|
2175
|
-
return self.flip_image(serial, channel, max_retries + 1)
|
|
2176
|
-
raise HTTPError from err
|
|
2177
|
-
|
|
2178
|
-
try:
|
|
2179
|
-
json_output = req.json()
|
|
2180
|
-
|
|
2181
|
-
except ValueError as err:
|
|
2182
|
-
raise PyEzvizError("Could not decode response: " + str(err)) from err
|
|
2183
|
-
|
|
2184
|
-
if json_output.get("meta", {}).get("code") != 200:
|
|
2185
|
-
raise PyEzvizError(f"Could not flip image on camera: Got {json_output}")
|
|
1701
|
+
json_output = self._request_json(
|
|
1702
|
+
"PUT",
|
|
1703
|
+
f"{API_ENDPOINT_DEVICE_BASICS}{serial}/{channel}/CENTER/mirror",
|
|
1704
|
+
retry_401=True,
|
|
1705
|
+
max_retries=max_retries,
|
|
1706
|
+
)
|
|
1707
|
+
self._ensure_ok(json_output, "Could not flip image on camera")
|
|
2186
1708
|
|
|
2187
1709
|
return True
|
|
2188
1710
|
|
|
@@ -2209,32 +1731,14 @@ class EzvizClient:
|
|
|
2209
1731
|
bool: True if the operation was successful.
|
|
2210
1732
|
|
|
2211
1733
|
"""
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
)
|
|
2221
|
-
req.raise_for_status()
|
|
2222
|
-
|
|
2223
|
-
except requests.HTTPError as err:
|
|
2224
|
-
if err.response.status_code == 401:
|
|
2225
|
-
# Session might be invalid; attempt to re-login and retry.
|
|
2226
|
-
self.login()
|
|
2227
|
-
return self.set_camera_osd(serial, text, channel, max_retries + 1)
|
|
2228
|
-
raise HTTPError from err
|
|
2229
|
-
|
|
2230
|
-
try:
|
|
2231
|
-
json_output = req.json()
|
|
2232
|
-
|
|
2233
|
-
except ValueError as err:
|
|
2234
|
-
raise PyEzvizError("Could not decode response: " + str(err)) from err
|
|
2235
|
-
|
|
2236
|
-
if json_output.get("meta", {}).get("code") != 200:
|
|
2237
|
-
raise PyEzvizError(f"Could set osd message on camera: Got {json_output}")
|
|
1734
|
+
json_output = self._request_json(
|
|
1735
|
+
"PUT",
|
|
1736
|
+
f"{API_ENDPOINT_OSD}{serial}/{channel}/osd",
|
|
1737
|
+
data={"osd": text},
|
|
1738
|
+
retry_401=True,
|
|
1739
|
+
max_retries=max_retries,
|
|
1740
|
+
)
|
|
1741
|
+
self._ensure_ok(json_output, "Could set osd message on camera")
|
|
2238
1742
|
|
|
2239
1743
|
return True
|
|
2240
1744
|
|
|
@@ -2254,35 +1758,14 @@ class EzvizClient:
|
|
|
2254
1758
|
"Range of luminance is 1-100, got " + str(luminance) + "."
|
|
2255
1759
|
)
|
|
2256
1760
|
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
req.raise_for_status()
|
|
2267
|
-
|
|
2268
|
-
except requests.HTTPError as err:
|
|
2269
|
-
if err.response.status_code == 401:
|
|
2270
|
-
# session is wrong, need to re-log-in
|
|
2271
|
-
self.login()
|
|
2272
|
-
return self.set_floodlight_brightness(
|
|
2273
|
-
serial, luminance, channelno, max_retries + 1
|
|
2274
|
-
)
|
|
2275
|
-
|
|
2276
|
-
raise HTTPError from err
|
|
2277
|
-
|
|
2278
|
-
try:
|
|
2279
|
-
response_json = req.json()
|
|
2280
|
-
|
|
2281
|
-
except ValueError as err:
|
|
2282
|
-
raise PyEzvizError("Could not decode response:" + str(err)) from err
|
|
2283
|
-
|
|
2284
|
-
if response_json["meta"]["code"] != 200:
|
|
2285
|
-
raise PyEzvizError(f"Unable to set brightness, got: {response_json}")
|
|
1761
|
+
response_json = self._request_json(
|
|
1762
|
+
"POST",
|
|
1763
|
+
f"{API_ENDPOINT_SET_LUMINANCE}{serial}/{channelno}",
|
|
1764
|
+
data={"luminance": luminance},
|
|
1765
|
+
retry_401=True,
|
|
1766
|
+
max_retries=max_retries,
|
|
1767
|
+
)
|
|
1768
|
+
self._ensure_ok(response_json, "Unable to set brightness")
|
|
2286
1769
|
|
|
2287
1770
|
return True
|
|
2288
1771
|
|
|
@@ -2294,7 +1777,6 @@ class EzvizClient:
|
|
|
2294
1777
|
max_retries: int = 0,
|
|
2295
1778
|
) -> bool | str:
|
|
2296
1779
|
"""Facade that changes the brightness to light bulbs or cameras' light."""
|
|
2297
|
-
|
|
2298
1780
|
device = self._light_bulbs.get(serial)
|
|
2299
1781
|
if device:
|
|
2300
1782
|
# the device is a light bulb
|
|
@@ -2313,7 +1795,6 @@ class EzvizClient:
|
|
|
2313
1795
|
max_retries: int = 0,
|
|
2314
1796
|
) -> bool:
|
|
2315
1797
|
"""Facade that turns on/off light bulbs or cameras' light."""
|
|
2316
|
-
|
|
2317
1798
|
device = self._light_bulbs.get(serial)
|
|
2318
1799
|
if device:
|
|
2319
1800
|
# the device is a light bulb
|
|
@@ -2392,54 +1873,27 @@ class EzvizClient:
|
|
|
2392
1873
|
self, serial: str, type_value: str = "0", max_retries: int = 0
|
|
2393
1874
|
) -> Any:
|
|
2394
1875
|
"""Get detection sensibility notifications."""
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
)
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
except requests.HTTPError as err:
|
|
2409
|
-
if err.response.status_code == 401:
|
|
2410
|
-
# session is wrong, need to re-log-in.
|
|
2411
|
-
self.login()
|
|
2412
|
-
return self.get_detection_sensibility(
|
|
2413
|
-
serial, type_value, max_retries + 1
|
|
2414
|
-
)
|
|
2415
|
-
|
|
2416
|
-
raise HTTPError from err
|
|
2417
|
-
|
|
2418
|
-
try:
|
|
2419
|
-
response_json = req.json()
|
|
2420
|
-
|
|
2421
|
-
except ValueError as err:
|
|
2422
|
-
raise PyEzvizError("Could not decode response:" + str(err)) from err
|
|
2423
|
-
|
|
2424
|
-
if response_json["resultCode"] != "0":
|
|
2425
|
-
if response_json["resultCode"] == "-1":
|
|
2426
|
-
_LOGGER.warning(
|
|
2427
|
-
"Camera %s is offline or unreachable, retrying %s of %s",
|
|
2428
|
-
serial,
|
|
2429
|
-
max_retries,
|
|
2430
|
-
MAX_RETRIES,
|
|
2431
|
-
)
|
|
2432
|
-
return self.get_detection_sensibility(
|
|
2433
|
-
serial, type_value, max_retries + 1
|
|
2434
|
-
)
|
|
1876
|
+
response_json = self._retry_json(
|
|
1877
|
+
lambda: self._request_json(
|
|
1878
|
+
"POST",
|
|
1879
|
+
API_ENDPOINT_DETECTION_SENSIBILITY_GET,
|
|
1880
|
+
data={"subSerial": serial},
|
|
1881
|
+
retry_401=True,
|
|
1882
|
+
max_retries=0,
|
|
1883
|
+
),
|
|
1884
|
+
attempts=max_retries,
|
|
1885
|
+
should_retry=lambda p: str(p.get("resultCode")) == "-1",
|
|
1886
|
+
log=f"Camera {serial} is offline or unreachable",
|
|
1887
|
+
)
|
|
1888
|
+
if str(response_json.get("resultCode")) != "0":
|
|
2435
1889
|
raise PyEzvizError(
|
|
2436
1890
|
f"Unable to get detection sensibility. Got: {response_json}"
|
|
2437
1891
|
)
|
|
2438
1892
|
|
|
2439
|
-
if response_json
|
|
1893
|
+
if response_json.get("algorithmConfig", {}).get("algorithmList"):
|
|
2440
1894
|
for idx in response_json["algorithmConfig"]["algorithmList"]:
|
|
2441
|
-
if idx
|
|
2442
|
-
return idx
|
|
1895
|
+
if idx.get("type") == type_value:
|
|
1896
|
+
return idx.get("value")
|
|
2443
1897
|
|
|
2444
1898
|
return None
|
|
2445
1899
|
|
|
@@ -2456,37 +1910,39 @@ class EzvizClient:
|
|
|
2456
1910
|
"Invalid sound_type, should be 0,1,2: " + str(sound_type)
|
|
2457
1911
|
)
|
|
2458
1912
|
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
1913
|
+
response_json = self._request_json(
|
|
1914
|
+
"PUT",
|
|
1915
|
+
f"{API_ENDPOINT_DEVICES}{serial}{API_ENDPOINT_ALARM_SOUND}",
|
|
1916
|
+
data={
|
|
1917
|
+
"enable": enable,
|
|
1918
|
+
"soundType": sound_type,
|
|
1919
|
+
"voiceId": "0",
|
|
1920
|
+
"deviceSerial": serial,
|
|
1921
|
+
},
|
|
1922
|
+
retry_401=True,
|
|
1923
|
+
max_retries=max_retries,
|
|
1924
|
+
)
|
|
1925
|
+
self._ensure_ok(response_json, "Could not set alarm sound")
|
|
1926
|
+
_LOGGER.debug(
|
|
1927
|
+
"http_debug: serial=%s code=%s msg=%s",
|
|
1928
|
+
serial,
|
|
1929
|
+
self._meta_code(response_json),
|
|
1930
|
+
"alarm_sound",
|
|
1931
|
+
)
|
|
1932
|
+
return True
|
|
1933
|
+
|
|
1934
|
+
def get_mqtt_client(
|
|
1935
|
+
self, on_message_callback: Callable[[dict[str, Any]], None] | None = None
|
|
1936
|
+
) -> MQTTClient:
|
|
1937
|
+
"""Return a configured MQTTClient using this client's session."""
|
|
1938
|
+
if self.mqtt_client is None:
|
|
1939
|
+
self.mqtt_client = MQTTClient(
|
|
1940
|
+
token=cast(dict[Any, Any], self._token),
|
|
1941
|
+
session=self._session,
|
|
2468
1942
|
timeout=self._timeout,
|
|
1943
|
+
on_message_callback=on_message_callback,
|
|
2469
1944
|
)
|
|
2470
|
-
|
|
2471
|
-
req.raise_for_status()
|
|
2472
|
-
|
|
2473
|
-
except requests.HTTPError as err:
|
|
2474
|
-
if err.response.status_code == 401:
|
|
2475
|
-
# session is wrong, need to re-log-in
|
|
2476
|
-
self.login()
|
|
2477
|
-
return self.alarm_sound(serial, sound_type, enable, max_retries + 1)
|
|
2478
|
-
|
|
2479
|
-
raise HTTPError from err
|
|
2480
|
-
|
|
2481
|
-
try:
|
|
2482
|
-
response_json = req.json()
|
|
2483
|
-
|
|
2484
|
-
except ValueError as err:
|
|
2485
|
-
raise PyEzvizError("Could not decode response:" + str(err)) from err
|
|
2486
|
-
|
|
2487
|
-
_LOGGER.debug("Response: %s", response_json)
|
|
2488
|
-
|
|
2489
|
-
return True
|
|
1945
|
+
return self.mqtt_client
|
|
2490
1946
|
|
|
2491
1947
|
def _get_page_list(self) -> Any:
|
|
2492
1948
|
"""Get ezviz device info broken down in sections."""
|