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