pyezvizapi 1.0.0.0__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/client.py ADDED
@@ -0,0 +1,2089 @@
1
+ """Ezviz API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ import hashlib
7
+ import json
8
+ import logging
9
+ from typing import Any
10
+ import urllib.parse
11
+ from uuid import uuid4
12
+
13
+ import requests
14
+
15
+ from .api_endpoints import (
16
+ API_ENDPOINT_ALARM_SOUND,
17
+ API_ENDPOINT_ALARMINFO_GET,
18
+ API_ENDPOINT_CAM_ENCRYPTKEY,
19
+ API_ENDPOINT_CANCEL_ALARM,
20
+ API_ENDPOINT_CHANGE_DEFENCE_STATUS,
21
+ API_ENDPOINT_CREATE_PANORAMIC,
22
+ API_ENDPOINT_DETECTION_SENSIBILITY,
23
+ API_ENDPOINT_DETECTION_SENSIBILITY_GET,
24
+ API_ENDPOINT_DEVCONFIG_BY_KEY,
25
+ API_ENDPOINT_DEVICE_STORAGE_STATUS,
26
+ API_ENDPOINT_DEVICE_SYS_OPERATION,
27
+ API_ENDPOINT_DEVICES,
28
+ API_ENDPOINT_DO_NOT_DISTURB,
29
+ API_ENDPOINT_GROUP_DEFENCE_MODE,
30
+ API_ENDPOINT_IOT_FEATURE,
31
+ API_ENDPOINT_LOGIN,
32
+ API_ENDPOINT_LOGOUT,
33
+ API_ENDPOINT_PAGELIST,
34
+ API_ENDPOINT_PANORAMIC_DEVICES_OPERATION,
35
+ API_ENDPOINT_PTZCONTROL,
36
+ API_ENDPOINT_REFRESH_SESSION_ID,
37
+ API_ENDPOINT_RETURN_PANORAMIC,
38
+ API_ENDPOINT_SEND_CODE,
39
+ API_ENDPOINT_SERVER_INFO,
40
+ API_ENDPOINT_SET_DEFENCE_SCHEDULE,
41
+ API_ENDPOINT_SET_LUMINANCE,
42
+ API_ENDPOINT_SWITCH_DEFENCE_MODE,
43
+ API_ENDPOINT_SWITCH_OTHER,
44
+ API_ENDPOINT_SWITCH_SOUND_ALARM,
45
+ API_ENDPOINT_SWITCH_STATUS,
46
+ API_ENDPOINT_UNIFIEDMSG_LIST_GET,
47
+ API_ENDPOINT_UPGRADE_DEVICE,
48
+ API_ENDPOINT_USER_ID,
49
+ API_ENDPOINT_V3_ALARMS,
50
+ API_ENDPOINT_VIDEO_ENCRYPT,
51
+ )
52
+ from .camera import EzvizCamera
53
+ from .cas import EzvizCAS
54
+ from .constants import (
55
+ DEFAULT_TIMEOUT,
56
+ FEATURE_CODE,
57
+ MAX_RETRIES,
58
+ REQUEST_HEADER,
59
+ DefenseModeType,
60
+ DeviceCatagories,
61
+ DeviceSwitchType,
62
+ MessageFilterType,
63
+ )
64
+ from .exceptions import (
65
+ EzvizAuthTokenExpired,
66
+ EzvizAuthVerificationCode,
67
+ HTTPError,
68
+ InvalidURL,
69
+ PyEzvizError,
70
+ )
71
+ from .light_bulb import EzvizLightBulb
72
+ from .utils import convert_to_dict, deep_merge
73
+
74
+ _LOGGER = logging.getLogger(__name__)
75
+
76
+
77
+ class EzvizClient:
78
+ """Initialize api client object."""
79
+
80
+ def __init__(
81
+ self,
82
+ account: str | None = None,
83
+ password: str | None = None,
84
+ url: str = "apiieu.ezvizlife.com",
85
+ timeout: int = DEFAULT_TIMEOUT,
86
+ token: dict | None = None,
87
+ ) -> None:
88
+ """Initialize the client object."""
89
+ self.account = account
90
+ self.password = (
91
+ hashlib.md5(password.encode("utf-8")).hexdigest() if password else None
92
+ ) # Ezviz API sends md5 of password
93
+ self._session = requests.session()
94
+ self._session.headers.update(REQUEST_HEADER)
95
+ self._session.headers["sessionId"] = token["session_id"] if token else None
96
+ self._token = token or {
97
+ "session_id": None,
98
+ "rf_session_id": None,
99
+ "username": None,
100
+ "api_url": url,
101
+ }
102
+ self._timeout = timeout
103
+ self._cameras: dict[str, Any] = {}
104
+ self._light_bulbs: dict[str, Any] = {}
105
+
106
+ def _login(self, smscode: int | None = None) -> dict[Any, Any]:
107
+ """Login to Ezviz API."""
108
+
109
+ # Region code to url.
110
+ if len(self._token["api_url"].split(".")) == 1:
111
+ self._token["api_url"] = "apii" + self._token["api_url"] + ".ezvizlife.com"
112
+
113
+ payload = {
114
+ "account": self.account,
115
+ "password": self.password,
116
+ "featureCode": FEATURE_CODE,
117
+ "msgType": "3" if smscode else "0",
118
+ "bizType": "TERMINAL_BIND" if smscode else "",
119
+ "cuName": "SGFzc2lv", # hassio base64 encoded
120
+ "smsCode": smscode,
121
+ }
122
+
123
+ try:
124
+ req = self._session.post(
125
+ "https://" + self._token["api_url"] + API_ENDPOINT_LOGIN,
126
+ allow_redirects=False,
127
+ data=payload,
128
+ timeout=self._timeout,
129
+ )
130
+
131
+ req.raise_for_status()
132
+
133
+ except requests.ConnectionError as err:
134
+ raise InvalidURL("A Invalid URL or Proxy error occurred") from err
135
+
136
+ except requests.HTTPError as err:
137
+ raise HTTPError from err
138
+
139
+ try:
140
+ json_result = req.json()
141
+
142
+ except ValueError as err:
143
+ raise PyEzvizError(
144
+ "Impossible to decode response: "
145
+ + str(err)
146
+ + "\nResponse was: "
147
+ + str(req.text)
148
+ ) from err
149
+
150
+ if json_result["meta"]["code"] == 200:
151
+ self._session.headers["sessionId"] = json_result["loginSession"][
152
+ "sessionId"
153
+ ]
154
+ self._token = {
155
+ "session_id": str(json_result["loginSession"]["sessionId"]),
156
+ "rf_session_id": str(json_result["loginSession"]["rfSessionId"]),
157
+ "username": str(json_result["loginUser"]["username"]),
158
+ "api_url": str(json_result["loginArea"]["apiDomain"]),
159
+ }
160
+
161
+ self._token["service_urls"] = self.get_service_urls()
162
+
163
+ return self._token
164
+
165
+ if json_result["meta"]["code"] == 1100:
166
+ self._token["api_url"] = json_result["loginArea"]["apiDomain"]
167
+ _LOGGER.warning("Region incorrect!")
168
+ _LOGGER.warning("Your region url: %s", self._token["api_url"])
169
+ return self.login()
170
+
171
+ if json_result["meta"]["code"] == 1012:
172
+ raise PyEzvizError("The MFA code is invalid, please try again.")
173
+
174
+ if json_result["meta"]["code"] == 1013:
175
+ raise PyEzvizError("Incorrect Username.")
176
+
177
+ if json_result["meta"]["code"] == 1014:
178
+ raise PyEzvizError("Incorrect Password.")
179
+
180
+ if json_result["meta"]["code"] == 1015:
181
+ raise PyEzvizError("The user is locked.")
182
+
183
+ if json_result["meta"]["code"] == 6002:
184
+ self.send_mfa_code()
185
+ raise EzvizAuthVerificationCode(
186
+ "MFA enabled on account. Please retry with code."
187
+ )
188
+
189
+ raise PyEzvizError(f"Login error: {json_result['meta']}")
190
+
191
+ def send_mfa_code(self) -> bool:
192
+ """Send verification code."""
193
+ try:
194
+ req = self._session.post(
195
+ "https://" + self._token["api_url"] + API_ENDPOINT_SEND_CODE,
196
+ data={
197
+ "from": self.account,
198
+ "bizType": "TERMINAL_BIND",
199
+ },
200
+ timeout=self._timeout,
201
+ )
202
+
203
+ req.raise_for_status()
204
+
205
+ except requests.HTTPError as err:
206
+ raise HTTPError from err
207
+
208
+ try:
209
+ json_output = req.json()
210
+
211
+ except ValueError as err:
212
+ raise PyEzvizError(
213
+ "Impossible to decode response: "
214
+ + str(err)
215
+ + "\nResponse was: "
216
+ + str(req.text)
217
+ ) from err
218
+
219
+ if json_output["meta"]["code"] != 200:
220
+ raise PyEzvizError(f"Could not request MFA code: Got {json_output})")
221
+
222
+ return True
223
+
224
+ def get_service_urls(self) -> Any:
225
+ """Get Ezviz service urls."""
226
+
227
+ if not self._token["session_id"]:
228
+ raise PyEzvizError("No Login token present!")
229
+
230
+ try:
231
+ req = self._session.get(
232
+ f"https://{self._token['api_url']}{API_ENDPOINT_SERVER_INFO}",
233
+ timeout=self._timeout,
234
+ )
235
+ req.raise_for_status()
236
+
237
+ except requests.ConnectionError as err:
238
+ raise InvalidURL("A Invalid URL or Proxy error occurred") from err
239
+
240
+ except requests.HTTPError as err:
241
+ raise HTTPError from err
242
+
243
+ try:
244
+ json_output = req.json()
245
+
246
+ except ValueError as err:
247
+ raise PyEzvizError(
248
+ "Impossible to decode response: "
249
+ + str(err)
250
+ + "\nResponse was: "
251
+ + str(req.text)
252
+ ) from err
253
+
254
+ if json_output["meta"]["code"] != 200:
255
+ raise PyEzvizError(f"Error getting Service URLs: {json_output}")
256
+
257
+ service_urls = json_output["systemConfigInfo"]
258
+ service_urls["sysConf"] = service_urls["sysConf"].split("|")
259
+
260
+ return service_urls
261
+
262
+ def _api_get_pagelist(
263
+ self,
264
+ page_filter: str,
265
+ json_key: str | None = None,
266
+ group_id: int = -1,
267
+ limit: int = 30,
268
+ offset: int = 0,
269
+ max_retries: int = 0,
270
+ ) -> Any:
271
+ """Get data from pagelist API."""
272
+
273
+ if max_retries > MAX_RETRIES:
274
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
275
+
276
+ if page_filter is None:
277
+ raise PyEzvizError("Trying to call get_pagelist without filter")
278
+
279
+ params: dict[str, int | str] = {
280
+ "groupId": group_id,
281
+ "limit": limit,
282
+ "offset": offset,
283
+ "filter": page_filter,
284
+ }
285
+
286
+ try:
287
+ req = self._session.get(
288
+ "https://" + self._token["api_url"] + API_ENDPOINT_PAGELIST,
289
+ params=params,
290
+ timeout=self._timeout,
291
+ )
292
+
293
+ req.raise_for_status()
294
+
295
+ except requests.HTTPError as err:
296
+ if err.response.status_code == 401:
297
+ # session is wrong, need to relogin
298
+ self.login()
299
+ return self._api_get_pagelist(
300
+ page_filter, json_key, group_id, limit, offset, max_retries + 1
301
+ )
302
+
303
+ raise HTTPError from err
304
+
305
+ try:
306
+ json_output = req.json()
307
+
308
+ except ValueError as err:
309
+ raise PyEzvizError(
310
+ "Impossible to decode response: "
311
+ + str(err)
312
+ + "\nResponse was: "
313
+ + str(req.text)
314
+ ) from err
315
+
316
+ if json_output["meta"]["code"] != 200:
317
+ # session is wrong, need to relogin
318
+ self.login()
319
+ _LOGGER.warning(
320
+ "Could not get pagelist, relogging (max retries: %s), got: %s",
321
+ str(max_retries),
322
+ json_output,
323
+ )
324
+ return self._api_get_pagelist(
325
+ page_filter, json_key, group_id, limit, offset, max_retries + 1
326
+ )
327
+
328
+ next_page = json_output["page"].get("hasNext", False)
329
+
330
+ data = json_output[json_key] if json_key else json_output
331
+
332
+ if next_page:
333
+ next_offset = offset + limit
334
+ # Recursive call to fetch next page
335
+ next_data = self._api_get_pagelist(
336
+ page_filter, json_key, group_id, limit, next_offset, max_retries
337
+ )
338
+ # Merge data from next page into current data
339
+ data = deep_merge(data, next_data)
340
+
341
+ return data
342
+
343
+
344
+ def get_alarminfo(self, serial: str, limit: int = 1, max_retries: int = 0) -> dict:
345
+ """Get data from alarm info API for camera serial."""
346
+ if max_retries > MAX_RETRIES:
347
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
348
+
349
+ params: dict[str, int | str] = {
350
+ "deviceSerials": serial,
351
+ "queryType": -1,
352
+ "limit": limit,
353
+ "stype": -1,
354
+ }
355
+
356
+ try:
357
+ req = self._session.get(
358
+ "https://" + self._token["api_url"] + API_ENDPOINT_ALARMINFO_GET,
359
+ params=params,
360
+ timeout=self._timeout,
361
+ )
362
+
363
+ req.raise_for_status()
364
+
365
+ except requests.HTTPError as err:
366
+ if err.response.status_code == 401:
367
+ # session is wrong, need to relogin
368
+ self.login()
369
+ return self.get_alarminfo(serial, limit, max_retries + 1)
370
+
371
+ raise HTTPError from err
372
+
373
+ try:
374
+ json_output: dict = req.json()
375
+
376
+ except ValueError as err:
377
+ raise PyEzvizError(
378
+ "Impossible to decode response: "
379
+ + str(err)
380
+ + "\nResponse was: "
381
+ + str(req.text)
382
+ ) from err
383
+
384
+ if json_output["meta"]["code"] != 200:
385
+ if json_output["meta"]["code"] == 500:
386
+ _LOGGER.debug(
387
+ "Retry getting alarm info, server returned busy: %s",
388
+ json_output,
389
+ )
390
+ return self.get_alarminfo(serial, limit, max_retries + 1)
391
+
392
+ raise PyEzvizError(f"Could not get data from alarm api: Got {json_output})")
393
+
394
+ return json_output
395
+
396
+ def get_device_messages_list(
397
+ self,
398
+ serials: str | None = None,
399
+ s_type: int = MessageFilterType.FILTER_TYPE_ALL_ALARM.value,
400
+ limit: int | None = 20, # 50 is the max even if you set it higher
401
+ date: str = datetime.today().strftime("%Y%m%d"),
402
+ end_time: str | None = None,
403
+ tags: str = "ALL",
404
+ max_retries: int = 0,
405
+ ) -> dict:
406
+ """Get data from Unified message list API."""
407
+ if max_retries > MAX_RETRIES:
408
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
409
+
410
+ params: dict[str, int | str | None] = {
411
+ "serials:": serials,
412
+ "stype": s_type,
413
+ "limit": limit,
414
+ "date": date,
415
+ "endTime": end_time,
416
+ "tags": tags,
417
+ }
418
+
419
+ try:
420
+ req = self._session.get(
421
+ "https://" + self._token["api_url"] + API_ENDPOINT_UNIFIEDMSG_LIST_GET,
422
+ params=params,
423
+ timeout=self._timeout,
424
+ )
425
+
426
+ req.raise_for_status()
427
+
428
+ except requests.HTTPError as err:
429
+ if err.response.status_code == 401:
430
+ # session is wrong, need to relogin
431
+ self.login()
432
+ return self.get_device_messages_list(
433
+ serials, s_type, limit, date, end_time, tags, max_retries + 1
434
+ )
435
+
436
+ raise HTTPError from err
437
+
438
+ try:
439
+ json_output: dict = req.json()
440
+
441
+ except ValueError as err:
442
+ raise PyEzvizError(
443
+ "Impossible to decode response: "
444
+ + str(err)
445
+ + "\nResponse was: "
446
+ + str(req.text)
447
+ ) from err
448
+
449
+ if json_output["meta"]["code"] != 200:
450
+ raise PyEzvizError(
451
+ f"Could not get unified message list: Got {json_output})"
452
+ )
453
+
454
+ return json_output
455
+
456
+ def switch_status(
457
+ self,
458
+ serial: str,
459
+ status_type: int,
460
+ enable: int,
461
+ channel_no: int = 0,
462
+ max_retries: int = 0,
463
+ ) -> bool:
464
+ """Camera features are represented as switches. Switch them on or off."""
465
+ if max_retries > MAX_RETRIES:
466
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
467
+
468
+ try:
469
+ req = self._session.put(
470
+ url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICES}{serial}/{channel_no}/{enable}/{status_type}{API_ENDPOINT_SWITCH_STATUS}",
471
+ timeout=self._timeout,
472
+ )
473
+
474
+ req.raise_for_status()
475
+
476
+ except requests.HTTPError as err:
477
+ if err.response.status_code == 401:
478
+ # session is wrong, need to relogin
479
+ self.login()
480
+ return self.switch_status(serial, status_type, enable, max_retries + 1)
481
+
482
+ raise HTTPError from err
483
+
484
+ try:
485
+ json_output = req.json()
486
+
487
+ except ValueError as err:
488
+ raise PyEzvizError(
489
+ "Impossible to decode response: "
490
+ + str(err)
491
+ + "\nResponse was: "
492
+ + str(req.text)
493
+ ) from err
494
+
495
+ if json_output["meta"]["code"] != 200:
496
+ raise PyEzvizError(f"Could not set the switch: Got {json_output})")
497
+
498
+ if self._cameras.get(serial):
499
+ self._cameras[serial]["switches"][status_type] = bool(enable)
500
+
501
+ return True
502
+
503
+ def switch_status_other(
504
+ self,
505
+ serial: str,
506
+ status_type: int,
507
+ enable: int,
508
+ channel_number: int = 1,
509
+ max_retries: int = 0,
510
+ ) -> bool:
511
+ """Features are represented as switches. This api is for alternative switch types to turn them on or off.
512
+
513
+ All day recording is a good example.
514
+ """
515
+ if max_retries > MAX_RETRIES:
516
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
517
+
518
+ try:
519
+ req = self._session.put(
520
+ url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICES}{serial}{API_ENDPOINT_SWITCH_OTHER}",
521
+ timeout=self._timeout,
522
+ params={
523
+ "channelNo": channel_number,
524
+ "enable": enable,
525
+ "switchType": status_type,
526
+ },
527
+ )
528
+
529
+ req.raise_for_status()
530
+
531
+ except requests.HTTPError as err:
532
+ if err.response.status_code == 401:
533
+ # session is wrong, need to relogin
534
+ self.login()
535
+ return self.switch_status_other(
536
+ serial, status_type, enable, channel_number, max_retries + 1
537
+ )
538
+
539
+ raise HTTPError from err
540
+
541
+ try:
542
+ json_output = req.json()
543
+
544
+ except ValueError as err:
545
+ raise PyEzvizError(
546
+ "Impossible to decode response: "
547
+ + str(err)
548
+ + "\nResponse was: "
549
+ + str(req.text)
550
+ ) from err
551
+
552
+ if json_output["meta"]["code"] != 200:
553
+ raise PyEzvizError(f"Could not set the switch: Got {json_output})")
554
+
555
+ return True
556
+
557
+ def set_camera_defence(
558
+ self,
559
+ serial: str,
560
+ enable: int,
561
+ channel_no: int = 1,
562
+ arm_type: str = "Global",
563
+ actor: str = "V",
564
+ max_retries: int = 0,
565
+ ) -> bool:
566
+ """Enable/Disable motion detection on camera."""
567
+
568
+ if max_retries > MAX_RETRIES:
569
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
570
+
571
+ try:
572
+ req = self._session.put(
573
+ url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICES}{serial}/{channel_no}/{API_ENDPOINT_CHANGE_DEFENCE_STATUS}",
574
+ timeout=self._timeout,
575
+ data={
576
+ "type": arm_type,
577
+ "status": enable,
578
+ "actor": actor,
579
+ },
580
+ )
581
+
582
+ req.raise_for_status()
583
+
584
+ except requests.HTTPError as err:
585
+ if err.response.status_code == 401:
586
+ # session is wrong, need to relogin
587
+ self.login()
588
+ return self.set_camera_defence(serial, enable, max_retries + 1)
589
+
590
+ raise HTTPError from err
591
+
592
+ try:
593
+ json_output = req.json()
594
+
595
+ except ValueError as err:
596
+ raise PyEzvizError(
597
+ "Impossible to decode response: "
598
+ + str(err)
599
+ + "\nResponse was: "
600
+ + str(req.text)
601
+ ) from err
602
+
603
+ if json_output["meta"]["code"] != 200:
604
+ if json_output["meta"]["code"] == 504:
605
+ _LOGGER.warning(
606
+ "Arm or disarm for camera %s timed out. Retrying %s of %s",
607
+ serial,
608
+ max_retries,
609
+ MAX_RETRIES,
610
+ )
611
+ return self.set_camera_defence(serial, enable, max_retries + 1)
612
+
613
+ raise PyEzvizError(
614
+ f"Could not arm or disarm Camera {serial}: Got {json_output})"
615
+ )
616
+
617
+ return True
618
+
619
+ def set_battery_camera_work_mode(self, serial: str, value: int) -> bool:
620
+ """Set battery camera work mode."""
621
+ return self.set_device_config_by_key(serial, value, key="batteryCameraWorkMode")
622
+
623
+ def set_detection_mode(self, serial: str, value: int) -> bool:
624
+ """Set detection mode."""
625
+ return self.set_device_config_by_key(
626
+ serial, value=f'{{"type":{value}}}', key="Alarm_DetectHumanCar"
627
+ )
628
+
629
+ def set_night_vision_mode(
630
+ self, serial: str, mode: int, luminance: int = 100
631
+ ) -> bool:
632
+ """Set night vision mode."""
633
+ return self.set_device_config_by_key(
634
+ serial,
635
+ value=f'{{"graphicType":{mode},"luminance":{luminance}}}',
636
+ key="NightVision_Model",
637
+ )
638
+
639
+ def set_display_mode(self, serial: str, mode: int) -> bool:
640
+ """Change video color and saturation mode."""
641
+ return self.set_device_config_by_key(
642
+ serial, value=f'{{"mode":{mode}}}', key="display_mode"
643
+ )
644
+
645
+ def set_device_config_by_key(
646
+ self,
647
+ serial: str,
648
+ value: Any,
649
+ key: str,
650
+ max_retries: int = 0,
651
+ ) -> bool:
652
+ """Change value on device by setting key."""
653
+ if max_retries > MAX_RETRIES:
654
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
655
+
656
+ params = {"key": key, "value": value}
657
+ params_str = urllib.parse.urlencode(
658
+ params, safe="}{:"
659
+ ) # not encode curly braces and colon
660
+
661
+ full_url = f'https://{self._token["api_url"]}{API_ENDPOINT_DEVCONFIG_BY_KEY}{serial}/1/op'
662
+
663
+ # EZVIZ api request needs {}: in the url, but requests lib doesn't allow it
664
+ # so we need to manually prepare it
665
+ req_prep = requests.Request(
666
+ method="PUT", url=full_url, headers=self._session.headers
667
+ ).prepare()
668
+ req_prep.url = full_url + "?" + params_str
669
+
670
+ try:
671
+ req = self._session.send(
672
+ request=req_prep,
673
+ timeout=self._timeout,
674
+ )
675
+
676
+ req.raise_for_status()
677
+
678
+ except requests.HTTPError as err:
679
+ if err.response.status_code == 401:
680
+ # session is wrong, need to relogin
681
+ self.login()
682
+ return self.set_device_config_by_key(
683
+ serial, value, key, max_retries + 1
684
+ )
685
+
686
+ raise HTTPError from err
687
+
688
+ try:
689
+ json_output = req.json()
690
+
691
+ except ValueError as err:
692
+ raise PyEzvizError(
693
+ "Impossible to decode response: "
694
+ + str(err)
695
+ + "\nResponse was: "
696
+ + str(req.text)
697
+ ) from err
698
+
699
+ if json_output["meta"]["code"] != 200:
700
+ raise PyEzvizError(f"Could not set config key '${key}': Got {json_output})")
701
+
702
+ return True
703
+
704
+ def set_device_feature_by_key(
705
+ self,
706
+ serial: str,
707
+ product_id: str,
708
+ value: Any,
709
+ key: str,
710
+ max_retries: int = 0,
711
+ ) -> bool:
712
+ """Change value on device by setting the iot-feature's key.
713
+
714
+ The FEATURE key that is part of 'device info' holds
715
+ information about the device's functions (for example light_switch, brightness etc.).
716
+ """
717
+ if max_retries > MAX_RETRIES:
718
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
719
+
720
+ payload = json.dumps({"itemKey": key, "productId": product_id, "value": value})
721
+
722
+ full_url = f'https://{self._token["api_url"]}{API_ENDPOINT_IOT_FEATURE}{serial.upper()}/0'
723
+
724
+ headers = self._session.headers
725
+ headers.update({"Content-Type": "application/json"})
726
+
727
+ req_prep = requests.Request(
728
+ method="PUT", url=full_url, headers=headers, data=payload
729
+ ).prepare()
730
+
731
+ try:
732
+ req = self._session.send(
733
+ request=req_prep,
734
+ timeout=self._timeout,
735
+ )
736
+
737
+ req.raise_for_status()
738
+
739
+ except requests.HTTPError as err:
740
+ if err.response.status_code == 401:
741
+ # session is wrong, need to relogin
742
+ self.login()
743
+ return self.set_device_feature_by_key(
744
+ serial, product_id, value, key, max_retries + 1
745
+ )
746
+
747
+ raise HTTPError from err
748
+
749
+ try:
750
+ json_output = req.json()
751
+
752
+ except ValueError as err:
753
+ raise PyEzvizError(
754
+ "Impossible to decode response: "
755
+ + str(err)
756
+ + "\nResponse was: "
757
+ + str(req.text)
758
+ ) from err
759
+
760
+ if json_output["meta"]["code"] != 200:
761
+ raise PyEzvizError(
762
+ f"Could not set iot-feature key '${key}': Got {json_output})"
763
+ )
764
+
765
+ return True
766
+
767
+ def upgrade_device(self, serial: str, max_retries: int = 0) -> bool:
768
+ """Upgrade device firmware."""
769
+ if max_retries > MAX_RETRIES:
770
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
771
+
772
+ try:
773
+ req = self._session.put(
774
+ "https://"
775
+ + self._token["api_url"]
776
+ + API_ENDPOINT_UPGRADE_DEVICE
777
+ + serial
778
+ + "/0/upgrade",
779
+ timeout=self._timeout,
780
+ )
781
+
782
+ req.raise_for_status()
783
+
784
+ except requests.HTTPError as err:
785
+ if err.response.status_code == 401:
786
+ # session is wrong, need to relogin
787
+ self.login()
788
+ return self.upgrade_device(serial, max_retries + 1)
789
+
790
+ raise HTTPError from err
791
+
792
+ try:
793
+ json_output = req.json()
794
+
795
+ except ValueError as err:
796
+ raise PyEzvizError(
797
+ "Impossible to decode response: "
798
+ + str(err)
799
+ + "\nResponse was: "
800
+ + str(req.text)
801
+ ) from err
802
+
803
+ if json_output["meta"]["code"] != 200:
804
+ raise PyEzvizError(
805
+ f"Could not initiate firmware upgrade: Got {json_output})"
806
+ )
807
+
808
+ return True
809
+
810
+ def get_storage_status(self, serial: str, max_retries: int = 0) -> Any:
811
+ """Get device storage status."""
812
+ if max_retries > MAX_RETRIES:
813
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
814
+
815
+ try:
816
+ req = self._session.post(
817
+ url=f"https://{self._token['api_url']}{API_ENDPOINT_DEVICE_STORAGE_STATUS}",
818
+ data={"subSerial": serial},
819
+ timeout=self._timeout,
820
+ )
821
+
822
+ req.raise_for_status()
823
+
824
+ except requests.HTTPError as err:
825
+ if err.response.status_code == 401:
826
+ # session is wrong, need to relogin
827
+ self.login()
828
+ return self.get_storage_status(serial, max_retries + 1)
829
+
830
+ raise HTTPError from err
831
+
832
+ try:
833
+ json_output = req.json()
834
+
835
+ except ValueError as err:
836
+ raise PyEzvizError(
837
+ "Impossible to decode response: "
838
+ + str(err)
839
+ + "\nResponse was: "
840
+ + str(req.text)
841
+ ) from err
842
+
843
+ if json_output["resultCode"] != "0":
844
+ if json_output["resultCode"] == "-1":
845
+ _LOGGER.warning(
846
+ "Can't get storage status from device %s, retrying %s of %s",
847
+ serial,
848
+ max_retries,
849
+ MAX_RETRIES,
850
+ )
851
+ return self.get_storage_status(serial, max_retries + 1)
852
+ raise PyEzvizError(
853
+ f"Could not get device storage status: Got {json_output})"
854
+ )
855
+
856
+ return json_output["storageStatus"]
857
+
858
+ def sound_alarm(self, serial: str, enable: int = 1, max_retries: int = 0) -> bool:
859
+ """Sound alarm on a device."""
860
+ if max_retries > MAX_RETRIES:
861
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
862
+
863
+ try:
864
+ req = self._session.put(
865
+ "https://"
866
+ + self._token["api_url"]
867
+ + API_ENDPOINT_DEVICES
868
+ + serial
869
+ + "/0"
870
+ + API_ENDPOINT_SWITCH_SOUND_ALARM,
871
+ data={
872
+ "enable": enable,
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
+
901
+ return True
902
+
903
+ def get_user_id(self, max_retries: int = 0) -> Any:
904
+ """Get Ezviz userid, used by restricted api endpoints."""
905
+
906
+ if max_retries > MAX_RETRIES:
907
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
908
+
909
+ try:
910
+ req = self._session.get(
911
+ f"https://{self._token['api_url']}{API_ENDPOINT_USER_ID}",
912
+ timeout=self._timeout,
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"]
939
+
940
+ def set_video_enc(
941
+ self,
942
+ serial: str,
943
+ enable: int = 1,
944
+ camera_verification_code: str | None = None,
945
+ new_password: str | None = None,
946
+ old_password: str | None = None,
947
+ max_retries: int = 0,
948
+ ) -> bool:
949
+ """Enable or Disable video encryption."""
950
+ if max_retries > MAX_RETRIES:
951
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
952
+
953
+ device_token_info = self.get_user_id()
954
+ cookies = {
955
+ "clientType": "3",
956
+ "clientVersion": "5.12.1.0517",
957
+ "userId": device_token_info["userId"],
958
+ "ASG_DisplayName": "home",
959
+ "C_SS": self._session.headers["sessionId"],
960
+ "lang": "en_US",
961
+ }
962
+
963
+ try:
964
+ req = self._session.put(
965
+ "https://"
966
+ + self._token["api_url"]
967
+ + API_ENDPOINT_DEVICES
968
+ + API_ENDPOINT_VIDEO_ENCRYPT,
969
+ data={
970
+ "deviceSerial": serial,
971
+ "isEncrypt": enable,
972
+ "oldPassword": old_password,
973
+ "password": new_password,
974
+ "featureCode": FEATURE_CODE,
975
+ "validateCode": camera_verification_code,
976
+ "msgType": -1,
977
+ },
978
+ cookies=cookies,
979
+ timeout=self._timeout,
980
+ )
981
+
982
+ req.raise_for_status()
983
+
984
+ except requests.HTTPError as err:
985
+ if err.response.status_code == 401:
986
+ # session is wrong, need to relogin
987
+ self.login()
988
+ return self.set_video_enc(
989
+ serial,
990
+ enable,
991
+ camera_verification_code,
992
+ new_password,
993
+ old_password,
994
+ max_retries + 1,
995
+ )
996
+
997
+ raise HTTPError from err
998
+
999
+ try:
1000
+ json_output = req.json()
1001
+
1002
+ except ValueError as err:
1003
+ raise PyEzvizError(
1004
+ "Impossible to decode response: "
1005
+ + str(err)
1006
+ + "\nResponse was: "
1007
+ + str(req.text)
1008
+ ) from err
1009
+
1010
+ if json_output["meta"]["code"] != 200:
1011
+ raise PyEzvizError(f"Could not set video encryption: Got {json_output})")
1012
+
1013
+ return True
1014
+
1015
+ def reboot_camera(
1016
+ self,
1017
+ serial: str,
1018
+ delay: int = 1,
1019
+ operation: int = 1,
1020
+ max_retries: int = 0,
1021
+ ) -> bool:
1022
+ """Reboot camera."""
1023
+ if max_retries > MAX_RETRIES:
1024
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1025
+
1026
+ try:
1027
+ req = self._session.post(
1028
+ url=f'https://{self._token["api_url"]}{API_ENDPOINT_DEVICE_SYS_OPERATION}{serial}',
1029
+ data={
1030
+ "oper": operation,
1031
+ "deviceSerial": serial,
1032
+ "delay": delay,
1033
+ },
1034
+ timeout=self._timeout,
1035
+ )
1036
+
1037
+ req.raise_for_status()
1038
+
1039
+ except requests.HTTPError as err:
1040
+ if err.response.status_code == 401:
1041
+ # session is wrong, need to relogin
1042
+ self.login()
1043
+ return self.reboot_camera(
1044
+ serial,
1045
+ delay,
1046
+ operation,
1047
+ max_retries + 1,
1048
+ )
1049
+
1050
+ raise HTTPError from err
1051
+
1052
+ try:
1053
+ json_output = req.json()
1054
+
1055
+ except ValueError as err:
1056
+ raise PyEzvizError(
1057
+ "Impossible to decode response: "
1058
+ + str(err)
1059
+ + "\nResponse was: "
1060
+ + str(req.text)
1061
+ ) from err
1062
+
1063
+ if json_output["resultCode"] != "0":
1064
+ if json_output["resultCode"] == "-1":
1065
+ _LOGGER.warning(
1066
+ "Unable to reboot camera, camera %s is unreachable, retrying %s of %s",
1067
+ serial,
1068
+ max_retries,
1069
+ MAX_RETRIES,
1070
+ )
1071
+ return self.reboot_camera(serial, delay, operation, max_retries + 1)
1072
+ raise PyEzvizError(f"Could not reboot device {json_output})")
1073
+
1074
+ return True
1075
+
1076
+ def get_group_defence_mode(self, max_retries: int = 0) -> Any:
1077
+ """Get group arm status. The alarm arm/disarm concept on 1st page of app."""
1078
+
1079
+ if max_retries > MAX_RETRIES:
1080
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1081
+
1082
+ try:
1083
+ req = self._session.get(
1084
+ "https://" + self._token["api_url"] + API_ENDPOINT_GROUP_DEFENCE_MODE,
1085
+ params={
1086
+ "groupId": -1,
1087
+ },
1088
+ timeout=self._timeout,
1089
+ )
1090
+
1091
+ req.raise_for_status()
1092
+
1093
+ except requests.HTTPError as err:
1094
+ if err.response.status_code == 401:
1095
+ # session is wrong, need to relogin
1096
+ self.login()
1097
+ return self.get_group_defence_mode(max_retries + 1)
1098
+
1099
+ raise HTTPError from err
1100
+
1101
+ try:
1102
+ json_output = req.json()
1103
+
1104
+ except ValueError as err:
1105
+ raise PyEzvizError(
1106
+ "Impossible to decode response: "
1107
+ + str(err)
1108
+ + "\nResponse was: "
1109
+ + str(req.text)
1110
+ ) from err
1111
+
1112
+ if json_output["meta"]["code"] != 200:
1113
+ raise PyEzvizError(
1114
+ f"Could not get group defence status: Got {json_output})"
1115
+ )
1116
+
1117
+ return json_output["mode"]
1118
+
1119
+ # Not tested
1120
+ def cancel_alarm_device(self, serial: str, max_retries: int = 0) -> bool:
1121
+ """Cacnel alarm on an Alarm device."""
1122
+ if max_retries > MAX_RETRIES:
1123
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1124
+
1125
+ try:
1126
+ req = self._session.post(
1127
+ "https://" + self._token["api_url"] + API_ENDPOINT_CANCEL_ALARM,
1128
+ data={"subSerial": serial},
1129
+ timeout=self._timeout,
1130
+ )
1131
+
1132
+ req.raise_for_status()
1133
+
1134
+ except requests.HTTPError as err:
1135
+ if err.response.status_code == 401:
1136
+ # session is wrong, need to relogin
1137
+ self.login()
1138
+ return self.sound_alarm(serial, max_retries + 1)
1139
+
1140
+ raise HTTPError from err
1141
+
1142
+ try:
1143
+ json_output = req.json()
1144
+
1145
+ except ValueError as err:
1146
+ raise PyEzvizError(
1147
+ "Impossible to decode response: "
1148
+ + str(err)
1149
+ + "\nResponse was: "
1150
+ + str(req.text)
1151
+ ) from err
1152
+
1153
+ if json_output["meta"]["code"] != 200:
1154
+ raise PyEzvizError(f"Could not cancel alarm siren: Got {json_output})")
1155
+
1156
+ return True
1157
+
1158
+ def load_devices(self) -> dict[Any, Any]:
1159
+ """Load and return all cameras and light bulb objects."""
1160
+
1161
+ devices = self.get_device_infos()
1162
+ supported_categories = [
1163
+ DeviceCatagories.COMMON_DEVICE_CATEGORY.value,
1164
+ DeviceCatagories.CAMERA_DEVICE_CATEGORY.value,
1165
+ DeviceCatagories.BATTERY_CAMERA_DEVICE_CATEGORY.value,
1166
+ DeviceCatagories.DOORBELL_DEVICE_CATEGORY.value,
1167
+ DeviceCatagories.BASE_STATION_DEVICE_CATEGORY.value,
1168
+ DeviceCatagories.CAT_EYE_CATEGORY.value,
1169
+ DeviceCatagories.LIGHTING.value,
1170
+ ]
1171
+
1172
+ for device, data in devices.items():
1173
+ if data["deviceInfos"]["deviceCategory"] in supported_categories:
1174
+ # Add support for connected HikVision cameras
1175
+ if (
1176
+ data["deviceInfos"]["deviceCategory"]
1177
+ == DeviceCatagories.COMMON_DEVICE_CATEGORY.value
1178
+ and not data["deviceInfos"]["hik"]
1179
+ ):
1180
+ continue
1181
+
1182
+ if (
1183
+ data["deviceInfos"]["deviceCategory"]
1184
+ == DeviceCatagories.LIGHTING.value
1185
+ ):
1186
+ # Create a light bulb object
1187
+ self._light_bulbs[device] = EzvizLightBulb(
1188
+ self, device, data
1189
+ ).status()
1190
+ else:
1191
+ # Create camera object
1192
+ self._cameras[device] = EzvizCamera(self, device, data).status()
1193
+
1194
+ return {**self._cameras, **self._light_bulbs}
1195
+
1196
+ def load_cameras(self) -> dict[Any, Any]:
1197
+ """Load and return all cameras objects."""
1198
+
1199
+ self.load_devices()
1200
+ return self._cameras
1201
+
1202
+ def load_light_bulbs(self) -> dict[Any, Any]:
1203
+ """Load light bulbs."""
1204
+
1205
+ self.load_devices()
1206
+ return self._light_bulbs
1207
+
1208
+ def get_device_infos(self, serial: str | None = None) -> dict[Any, Any]:
1209
+ """Load all devices and build dict per device serial."""
1210
+
1211
+ devices = self._get_page_list()
1212
+ result: dict[str, Any] = {}
1213
+ _res_id = "NONE"
1214
+
1215
+ for device in devices["deviceInfos"]:
1216
+ _serial = device["deviceSerial"]
1217
+ _res_id_list = {
1218
+ item
1219
+ for item in devices.get("CLOUD", {})
1220
+ if devices["CLOUD"][item].get("deviceSerial") == _serial
1221
+ }
1222
+ _res_id = _res_id_list.pop() if len(_res_id_list) else "NONE"
1223
+
1224
+ result[_serial] = {
1225
+ "CLOUD": {_res_id: devices.get("CLOUD", {}).get(_res_id, {})},
1226
+ "VTM": {_res_id: devices.get("VTM", {}).get(_res_id, {})},
1227
+ "P2P": devices.get("P2P", {}).get(_serial, {}),
1228
+ "CONNECTION": devices.get("CONNECTION", {}).get(_serial, {}),
1229
+ "KMS": devices.get("KMS", {}).get(_serial, {}),
1230
+ "STATUS": devices.get("STATUS", {}).get(_serial, {}),
1231
+ "TIME_PLAN": devices.get("TIME_PLAN", {}).get(_serial, {}),
1232
+ "CHANNEL": {_res_id: devices.get("CHANNEL", {}).get(_res_id, {})},
1233
+ "QOS": devices.get("QOS", {}).get(_serial, {}),
1234
+ "NODISTURB": devices.get("NODISTURB", {}).get(_serial, {}),
1235
+ "FEATURE": devices.get("FEATURE", {}).get(_serial, {}),
1236
+ "UPGRADE": devices.get("UPGRADE", {}).get(_serial, {}),
1237
+ "FEATURE_INFO": devices.get("FEATURE_INFO", {}).get(_serial, {}),
1238
+ "SWITCH": devices.get("SWITCH", {}).get(_serial, {}),
1239
+ "CUSTOM_TAG": devices.get("CUSTOM_TAG", {}).get(_serial, {}),
1240
+ "VIDEO_QUALITY": {
1241
+ _res_id: devices.get("VIDEO_QUALITY", {}).get(_res_id, {})
1242
+ },
1243
+ "resourceInfos": [
1244
+ item
1245
+ for item in devices.get("resourceInfos")
1246
+ if item.get("deviceSerial") == _serial
1247
+ ], # Could be more than one
1248
+ "WIFI": devices.get("WIFI", {}).get(_serial, {}),
1249
+ "deviceInfos": device,
1250
+ }
1251
+ # Nested keys are still encoded as JSON strings
1252
+ result[_serial]["deviceInfos"]["supportExt"] = json.loads(
1253
+ result[_serial]["deviceInfos"]["supportExt"]
1254
+ )
1255
+ convert_to_dict(result[_serial]["STATUS"].get("optionals"))
1256
+
1257
+ if not serial:
1258
+ return result
1259
+
1260
+ return result.get(serial, {})
1261
+
1262
+ def ptz_control(
1263
+ self, command: str, serial: str, action: str, speed: int = 5
1264
+ ) -> Any:
1265
+ """PTZ Control by API."""
1266
+ if command is None:
1267
+ raise PyEzvizError("Trying to call ptzControl without command")
1268
+ if action is None:
1269
+ raise PyEzvizError("Trying to call ptzControl without action")
1270
+
1271
+ try:
1272
+ req = self._session.put(
1273
+ "https://"
1274
+ + self._token["api_url"]
1275
+ + API_ENDPOINT_DEVICES
1276
+ + serial
1277
+ + API_ENDPOINT_PTZCONTROL,
1278
+ data={
1279
+ "command": command,
1280
+ "action": action,
1281
+ "channelNo": "1",
1282
+ "speed": speed,
1283
+ "uuid": str(uuid4()),
1284
+ "serial": serial,
1285
+ },
1286
+ timeout=self._timeout,
1287
+ )
1288
+
1289
+ req.raise_for_status()
1290
+
1291
+ except requests.HTTPError as err:
1292
+ raise HTTPError from err
1293
+
1294
+ try:
1295
+ json_output = req.json()
1296
+
1297
+ except ValueError as err:
1298
+ raise PyEzvizError(
1299
+ "Impossible to decode response: "
1300
+ + str(err)
1301
+ + "\nResponse was: "
1302
+ + str(req.text)
1303
+ ) from err
1304
+
1305
+ _LOGGER.debug("PTZ Control: %s", json_output)
1306
+
1307
+ return True
1308
+
1309
+ def get_cam_key(
1310
+ self, serial: str, smscode: int | None = None, max_retries: int = 0
1311
+ ) -> Any:
1312
+ """Get Camera encryption key. The key that is set after the camera is added to the account."""
1313
+
1314
+ if max_retries > MAX_RETRIES:
1315
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1316
+
1317
+ try:
1318
+ req = self._session.post(
1319
+ "https://" + self._token["api_url"] + API_ENDPOINT_CAM_ENCRYPTKEY,
1320
+ data={
1321
+ "checkcode": smscode,
1322
+ "serial": serial,
1323
+ "clientNo": "web_site",
1324
+ "clientType": 3,
1325
+ "netType": "WIFI",
1326
+ "featureCode": FEATURE_CODE,
1327
+ "sessionId": self._token["session_id"],
1328
+ },
1329
+ timeout=self._timeout,
1330
+ )
1331
+
1332
+ req.raise_for_status()
1333
+
1334
+ except requests.HTTPError as err:
1335
+ if err.response.status_code == 401:
1336
+ # session is wrong, need to relogin
1337
+ self.login()
1338
+ return self.get_cam_key(serial, smscode, max_retries + 1)
1339
+
1340
+ raise HTTPError from err
1341
+
1342
+ try:
1343
+ json_output = req.json()
1344
+
1345
+ except ValueError as err:
1346
+ raise PyEzvizError(
1347
+ "Impossible to decode response: "
1348
+ + str(err)
1349
+ + "\nResponse was: "
1350
+ + str(req.text)
1351
+ ) from err
1352
+
1353
+ if json_output["resultCode"] == "20002":
1354
+ raise EzvizAuthVerificationCode(f"MFA code required: Got {json_output})")
1355
+
1356
+ if json_output["resultCode"] != "0":
1357
+ if json_output["resultCode"] == "-1":
1358
+ _LOGGER.warning(
1359
+ "Camera %s encryption key not found, retrying %s of %s",
1360
+ serial,
1361
+ max_retries,
1362
+ MAX_RETRIES,
1363
+ )
1364
+ return self.get_cam_key(serial, smscode, max_retries + 1)
1365
+ raise PyEzvizError(
1366
+ f"Could not get camera encryption key: Got {json_output})"
1367
+ )
1368
+
1369
+ return json_output["encryptkey"]
1370
+
1371
+ def create_panoramic(self, serial: str, max_retries: int = 0) -> Any:
1372
+ """Create panoramic image."""
1373
+
1374
+ if max_retries > MAX_RETRIES:
1375
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1376
+
1377
+ try:
1378
+ req = self._session.post(
1379
+ "https://" + self._token["api_url"] + API_ENDPOINT_CREATE_PANORAMIC,
1380
+ data={"deviceSerial": serial},
1381
+ timeout=self._timeout,
1382
+ )
1383
+
1384
+ req.raise_for_status()
1385
+
1386
+ except requests.HTTPError as err:
1387
+ if err.response.status_code == 401:
1388
+ # session is wrong, need to relogin
1389
+ self.login()
1390
+ return self.create_panoramic(serial, max_retries + 1)
1391
+
1392
+ raise HTTPError from err
1393
+
1394
+ try:
1395
+ json_output = req.json()
1396
+
1397
+ except ValueError as err:
1398
+ raise PyEzvizError(
1399
+ "Impossible to decode response: "
1400
+ + str(err)
1401
+ + "\nResponse was: "
1402
+ + str(req.text)
1403
+ ) from err
1404
+
1405
+ if json_output["resultCode"] != "0":
1406
+ if json_output["resultCode"] == "-1":
1407
+ _LOGGER.warning(
1408
+ "Create panoramic failed on device %s retrying %s",
1409
+ serial,
1410
+ max_retries,
1411
+ )
1412
+ return self.create_panoramic(serial, max_retries + 1)
1413
+ raise PyEzvizError(
1414
+ f"Could not send command to create panoramic photo: Got {json_output})"
1415
+ )
1416
+
1417
+ return json_output
1418
+
1419
+ def return_panoramic(self, serial: str, max_retries: int = 0) -> Any:
1420
+ """Return panoramic image url list."""
1421
+
1422
+ if max_retries > MAX_RETRIES:
1423
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1424
+
1425
+ try:
1426
+ req = self._session.post(
1427
+ "https://" + self._token["api_url"] + API_ENDPOINT_RETURN_PANORAMIC,
1428
+ data={"deviceSerial": serial},
1429
+ timeout=self._timeout,
1430
+ )
1431
+
1432
+ req.raise_for_status()
1433
+
1434
+ except requests.HTTPError as err:
1435
+ if err.response.status_code == 401:
1436
+ # session is wrong, need to relogin
1437
+ self.login()
1438
+ return self.return_panoramic(serial, max_retries + 1)
1439
+
1440
+ raise HTTPError from err
1441
+
1442
+ try:
1443
+ json_output = req.json()
1444
+
1445
+ except ValueError as err:
1446
+ raise PyEzvizError(
1447
+ "Impossible to decode response: "
1448
+ + str(err)
1449
+ + "\nResponse was: "
1450
+ + str(req.text)
1451
+ ) from err
1452
+
1453
+ if json_output["resultCode"] != "0":
1454
+ if json_output["resultCode"] == "-1":
1455
+ _LOGGER.warning(
1456
+ "Camera %s busy or unreachable, retrying %s of %s",
1457
+ serial,
1458
+ max_retries,
1459
+ MAX_RETRIES,
1460
+ )
1461
+ return self.return_panoramic(serial, max_retries + 1)
1462
+ raise PyEzvizError(f"Could retrieve panoramic photo: Got {json_output})")
1463
+
1464
+ return json_output
1465
+
1466
+ def ptz_control_coordinates(
1467
+ self, serial: str, x_axis: float, y_axis: float
1468
+ ) -> bool:
1469
+ """PTZ Coordinate Move."""
1470
+ if 0 < x_axis > 1:
1471
+ raise PyEzvizError(
1472
+ f"Invalid X coordinate: {x_axis}: Should be between 0 and 1 inclusive"
1473
+ )
1474
+
1475
+ if 0 < y_axis > 1:
1476
+ raise PyEzvizError(
1477
+ f"Invalid Y coordinate: {y_axis}: Should be between 0 and 1 inclusive"
1478
+ )
1479
+
1480
+ try:
1481
+ req = self._session.post(
1482
+ "https://"
1483
+ + self._token["api_url"]
1484
+ + API_ENDPOINT_PANORAMIC_DEVICES_OPERATION,
1485
+ data={
1486
+ "x": f"{x_axis:.6f}",
1487
+ "y": f"{y_axis:.6f}",
1488
+ "deviceSerial": serial,
1489
+ },
1490
+ timeout=self._timeout,
1491
+ )
1492
+
1493
+ req.raise_for_status()
1494
+
1495
+ except requests.HTTPError as err:
1496
+ raise HTTPError from err
1497
+
1498
+ try:
1499
+ json_result = req.json()
1500
+
1501
+ except ValueError as err:
1502
+ raise PyEzvizError(
1503
+ "Impossible to decode response: "
1504
+ + str(err)
1505
+ + "\nResponse was: "
1506
+ + str(req.text)
1507
+ ) from err
1508
+
1509
+ _LOGGER.debug("PTZ control coordinates: %s", json_result)
1510
+
1511
+ return True
1512
+
1513
+ def login(self, sms_code: int | None = None) -> dict[Any, Any]:
1514
+ """Get or refresh ezviz login token."""
1515
+ if self._token["session_id"] and self._token["rf_session_id"]:
1516
+ try:
1517
+ req = self._session.put(
1518
+ "https://"
1519
+ + self._token["api_url"]
1520
+ + API_ENDPOINT_REFRESH_SESSION_ID,
1521
+ data={
1522
+ "refreshSessionId": self._token["rf_session_id"],
1523
+ "featureCode": FEATURE_CODE,
1524
+ },
1525
+ timeout=self._timeout,
1526
+ )
1527
+ req.raise_for_status()
1528
+
1529
+ except requests.HTTPError as err:
1530
+ raise HTTPError from err
1531
+
1532
+ try:
1533
+ json_result = req.json()
1534
+
1535
+ except ValueError as err:
1536
+ raise PyEzvizError(
1537
+ "Impossible to decode response: "
1538
+ + str(err)
1539
+ + "\nResponse was: "
1540
+ + str(req.text)
1541
+ ) from err
1542
+
1543
+ if json_result["meta"]["code"] == 200:
1544
+ self._session.headers["sessionId"] = json_result["sessionInfo"][
1545
+ "sessionId"
1546
+ ]
1547
+ self._token["session_id"] = str(json_result["sessionInfo"]["sessionId"])
1548
+ self._token["rf_session_id"] = str(
1549
+ json_result["sessionInfo"]["refreshSessionId"]
1550
+ )
1551
+
1552
+ if not self._token.get("service_urls"):
1553
+ self._token["service_urls"] = self.get_service_urls()
1554
+
1555
+ return self._token
1556
+
1557
+ if json_result["meta"]["code"] == 403:
1558
+ if self.account and self.password:
1559
+ self._token = {
1560
+ "session_id": None,
1561
+ "rf_session_id": None,
1562
+ "username": None,
1563
+ "api_url": self._token["api_url"],
1564
+ }
1565
+ return self.login()
1566
+
1567
+ raise EzvizAuthTokenExpired(
1568
+ f"Token expired, Login with username and password required: {req.text}"
1569
+ )
1570
+
1571
+ raise PyEzvizError(f"Error renewing login token: {json_result['meta']}")
1572
+
1573
+ if self.account and self.password:
1574
+ return self._login(sms_code)
1575
+
1576
+ raise PyEzvizError("Login with account and password required")
1577
+
1578
+ def logout(self) -> bool:
1579
+ """Close Ezviz session and remove login session from ezviz servers."""
1580
+ try:
1581
+ req = self._session.delete(
1582
+ "https://" + self._token["api_url"] + API_ENDPOINT_LOGOUT,
1583
+ timeout=self._timeout,
1584
+ )
1585
+ req.raise_for_status()
1586
+
1587
+ except requests.HTTPError as err:
1588
+ if err.response.status_code == 401:
1589
+ _LOGGER.warning("Token is no longer valid. Already logged out?")
1590
+ return True
1591
+ raise HTTPError from err
1592
+
1593
+ try:
1594
+ json_result = req.json()
1595
+
1596
+ except ValueError as err:
1597
+ raise PyEzvizError(
1598
+ "Impossible to decode response: "
1599
+ + str(err)
1600
+ + "\nResponse was: "
1601
+ + str(req.text)
1602
+ ) from err
1603
+
1604
+ self.close_session()
1605
+
1606
+ return bool(json_result["meta"]["code"] == 200)
1607
+
1608
+ def set_camera_defence_old(self, serial: str, enable: int) -> bool:
1609
+ """Enable/Disable motion detection on camera."""
1610
+ cas_client = EzvizCAS(self._token)
1611
+ cas_client.set_camera_defence_state(serial, enable)
1612
+
1613
+ return True
1614
+
1615
+ def api_set_defence_schedule(
1616
+ self, serial: str, schedule: str, enable: int, max_retries: int = 0
1617
+ ) -> bool:
1618
+ """Set defence schedules."""
1619
+ if max_retries > MAX_RETRIES:
1620
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1621
+
1622
+ schedulestring = (
1623
+ '{"CN":0,"EL":'
1624
+ + str(enable)
1625
+ + ',"SS":"'
1626
+ + serial
1627
+ + '","WP":['
1628
+ + schedule
1629
+ + "]}]}"
1630
+ )
1631
+ try:
1632
+ req = self._session.post(
1633
+ "https://" + self._token["api_url"] + API_ENDPOINT_SET_DEFENCE_SCHEDULE,
1634
+ data={
1635
+ "devTimingPlan": schedulestring,
1636
+ },
1637
+ timeout=self._timeout,
1638
+ )
1639
+
1640
+ req.raise_for_status()
1641
+
1642
+ except requests.HTTPError as err:
1643
+ if err.response.status_code == 401:
1644
+ # session is wrong, need to relogin
1645
+ self.login()
1646
+ return self.api_set_defence_schedule(
1647
+ serial, schedule, enable, max_retries + 1
1648
+ )
1649
+
1650
+ raise HTTPError from err
1651
+
1652
+ try:
1653
+ json_output = req.json()
1654
+
1655
+ except ValueError as err:
1656
+ raise PyEzvizError(
1657
+ "Impossible to decode response: "
1658
+ + str(err)
1659
+ + "\nResponse was: "
1660
+ + str(req.text)
1661
+ ) from err
1662
+
1663
+ if json_output["resultCode"] != "0":
1664
+ if json_output["resultCode"] == "-1":
1665
+ _LOGGER.warning(
1666
+ "Camara %s offline or unreachable, retrying %s of %s",
1667
+ serial,
1668
+ max_retries,
1669
+ MAX_RETRIES,
1670
+ )
1671
+ return self.api_set_defence_schedule(
1672
+ serial, schedule, enable, max_retries + 1
1673
+ )
1674
+ raise PyEzvizError(f"Could not set the schedule: Got {json_output})")
1675
+
1676
+ return True
1677
+
1678
+ def api_set_defence_mode(self, mode: DefenseModeType, max_retries: int = 0) -> bool:
1679
+ """Set defence mode for all devices. The alarm panel from main page is used."""
1680
+ if max_retries > MAX_RETRIES:
1681
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1682
+
1683
+ try:
1684
+ req = self._session.post(
1685
+ "https://" + self._token["api_url"] + API_ENDPOINT_SWITCH_DEFENCE_MODE,
1686
+ data={
1687
+ "groupId": -1,
1688
+ "mode": mode,
1689
+ },
1690
+ timeout=self._timeout,
1691
+ )
1692
+
1693
+ req.raise_for_status()
1694
+
1695
+ except requests.HTTPError as err:
1696
+ if err.response.status_code == 401:
1697
+ # session is wrong, need to relogin
1698
+ self.login()
1699
+ return self.api_set_defence_mode(mode, max_retries + 1)
1700
+
1701
+ raise HTTPError from err
1702
+
1703
+ try:
1704
+ json_output = req.json()
1705
+
1706
+ except ValueError as err:
1707
+ raise PyEzvizError(
1708
+ "Impossible to decode response: "
1709
+ + str(err)
1710
+ + "\nResponse was: "
1711
+ + str(req.text)
1712
+ ) from err
1713
+
1714
+ if json_output["meta"]["code"] != 200:
1715
+ raise PyEzvizError(f"Could not set defence mode: Got {json_output})")
1716
+
1717
+ return True
1718
+
1719
+ def do_not_disturb(
1720
+ self,
1721
+ serial: str,
1722
+ enable: int = 1,
1723
+ channelno: int = 1,
1724
+ max_retries: int = 0,
1725
+ ) -> bool:
1726
+ """Set do not disturb on camera with specified serial."""
1727
+ if max_retries > MAX_RETRIES:
1728
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1729
+
1730
+ try:
1731
+ req = self._session.put(
1732
+ "https://"
1733
+ + self._token["api_url"]
1734
+ + API_ENDPOINT_V3_ALARMS
1735
+ + serial
1736
+ + "/"
1737
+ + channelno
1738
+ + API_ENDPOINT_DO_NOT_DISTURB,
1739
+ data={"enable": enable, "channelNo": channelno, "deviceSerial": serial},
1740
+ timeout=self._timeout,
1741
+ )
1742
+ req.raise_for_status()
1743
+
1744
+ except requests.HTTPError as err:
1745
+ if err.response.status_code == 401:
1746
+ # session is wrong, need to re-log-in
1747
+ self.login()
1748
+ return self.do_not_disturb(serial, enable, channelno, max_retries + 1)
1749
+
1750
+ raise HTTPError from err
1751
+
1752
+ try:
1753
+ json_output = req.json()
1754
+
1755
+ except ValueError as err:
1756
+ raise PyEzvizError("Could not decode response:" + str(err)) from err
1757
+
1758
+ if json_output["meta"]["code"] != 200:
1759
+ raise PyEzvizError(f"Could not set do not disturb: Got {json_output})")
1760
+
1761
+ return True
1762
+
1763
+ def set_floodlight_brightness(
1764
+ self,
1765
+ serial: str,
1766
+ luminance: int = 50,
1767
+ channelno: str = "1",
1768
+ max_retries: int = 0,
1769
+ ) -> bool | str:
1770
+ """Set brightness on camera with adjustable light."""
1771
+ if max_retries > MAX_RETRIES:
1772
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1773
+
1774
+ if luminance not in range(1, 100):
1775
+ raise PyEzvizError(
1776
+ "Range of luminance is 1-100, got " + str(luminance) + "."
1777
+ )
1778
+
1779
+ try:
1780
+ req = self._session.post(
1781
+ "https://"
1782
+ + self._token["api_url"]
1783
+ + API_ENDPOINT_SET_LUMINANCE
1784
+ + "/"
1785
+ + serial
1786
+ + "/"
1787
+ + channelno,
1788
+ data={
1789
+ "luminance": luminance,
1790
+ },
1791
+ timeout=self._timeout,
1792
+ )
1793
+
1794
+ req.raise_for_status()
1795
+
1796
+ except requests.HTTPError as err:
1797
+ if err.response.status_code == 401:
1798
+ # session is wrong, need to re-log-in
1799
+ self.login()
1800
+ return self.set_floodlight_brightness(
1801
+ serial, luminance, channelno, max_retries + 1
1802
+ )
1803
+
1804
+ raise HTTPError from err
1805
+
1806
+ try:
1807
+ response_json = req.json()
1808
+
1809
+ except ValueError as err:
1810
+ raise PyEzvizError("Could not decode response:" + str(err)) from err
1811
+
1812
+ if response_json["meta"]["code"] != 200:
1813
+ raise PyEzvizError(f"Unable to set brightness, got: {response_json}")
1814
+
1815
+ return True
1816
+
1817
+ def set_brightness(
1818
+ self,
1819
+ serial: str,
1820
+ luminance: int = 50,
1821
+ channelno: str = "1",
1822
+ max_retries: int = 0,
1823
+ ) -> bool | str:
1824
+ """Facade that changes the brightness to light bulbs or cameras' light."""
1825
+
1826
+ device = self._light_bulbs.get(serial)
1827
+ if device:
1828
+ # the device is a light bulb
1829
+ return self.set_device_feature_by_key(
1830
+ serial, device["productId"], luminance, "brightness", max_retries
1831
+ )
1832
+
1833
+ # assume the device is a camera
1834
+ return self.set_floodlight_brightness(serial, luminance, channelno, max_retries)
1835
+
1836
+ def switch_light_status(
1837
+ self,
1838
+ serial: str,
1839
+ enable: int,
1840
+ channel_no: int = 0,
1841
+ max_retries: int = 0,
1842
+ ) -> bool:
1843
+ """Facade that turns on/off light bulbs or cameras' light."""
1844
+
1845
+ device = self._light_bulbs.get(serial)
1846
+ if device:
1847
+ # the device is a light bulb
1848
+ return self.set_device_feature_by_key(
1849
+ serial, device["productId"], bool(enable), "light_switch", max_retries
1850
+ )
1851
+
1852
+ # assume the device is a camera
1853
+ return self.switch_status(
1854
+ serial, DeviceSwitchType.ALARM_LIGHT.value, enable, channel_no, max_retries
1855
+ )
1856
+
1857
+ def detection_sensibility(
1858
+ self,
1859
+ serial: str,
1860
+ sensibility: int = 3,
1861
+ type_value: int = 3,
1862
+ max_retries: int = 0,
1863
+ ) -> bool | str:
1864
+ """Set detection sensibility."""
1865
+ if max_retries > MAX_RETRIES:
1866
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1867
+
1868
+ if sensibility not in [0, 1, 2, 3, 4, 5, 6] and type_value == 0:
1869
+ raise PyEzvizError(
1870
+ "Unproper sensibility for type 0 (should be within 1 to 6)."
1871
+ )
1872
+
1873
+ try:
1874
+ req = self._session.post(
1875
+ "https://"
1876
+ + self._token["api_url"]
1877
+ + API_ENDPOINT_DETECTION_SENSIBILITY,
1878
+ data={
1879
+ "subSerial": serial,
1880
+ "type": type_value,
1881
+ "channelNo": "1",
1882
+ "value": sensibility,
1883
+ },
1884
+ timeout=self._timeout,
1885
+ )
1886
+
1887
+ req.raise_for_status()
1888
+
1889
+ except requests.HTTPError as err:
1890
+ if err.response.status_code == 401:
1891
+ # session is wrong, need to re-log-in
1892
+ self.login()
1893
+ return self.detection_sensibility(
1894
+ serial, sensibility, type_value, max_retries + 1
1895
+ )
1896
+
1897
+ raise HTTPError from err
1898
+
1899
+ try:
1900
+ response_json = req.json()
1901
+
1902
+ except ValueError as err:
1903
+ raise PyEzvizError("Could not decode response:" + str(err)) from err
1904
+
1905
+ if response_json["resultCode"] != "0":
1906
+ if response_json["resultCode"] == "-1":
1907
+ _LOGGER.warning(
1908
+ "Camera %s is offline or unreachable, can't set sensitivity, retrying %s of %s",
1909
+ serial,
1910
+ max_retries,
1911
+ MAX_RETRIES,
1912
+ )
1913
+ return self.detection_sensibility(
1914
+ serial, sensibility, type_value, max_retries + 1
1915
+ )
1916
+ raise PyEzvizError(
1917
+ f"Unable to set detection sensibility. Got: {response_json}"
1918
+ )
1919
+
1920
+ return True
1921
+
1922
+ def get_detection_sensibility(
1923
+ self, serial: str, type_value: str = "0", max_retries: int = 0
1924
+ ) -> Any:
1925
+ """Get detection sensibility notifications."""
1926
+ if max_retries > MAX_RETRIES:
1927
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1928
+
1929
+ try:
1930
+ req = self._session.post(
1931
+ "https://"
1932
+ + self._token["api_url"]
1933
+ + API_ENDPOINT_DETECTION_SENSIBILITY_GET,
1934
+ data={
1935
+ "subSerial": serial,
1936
+ },
1937
+ timeout=self._timeout,
1938
+ )
1939
+
1940
+ req.raise_for_status()
1941
+
1942
+ except requests.HTTPError as err:
1943
+ if err.response.status_code == 401:
1944
+ # session is wrong, need to re-log-in.
1945
+ self.login()
1946
+ return self.get_detection_sensibility(
1947
+ serial, type_value, max_retries + 1
1948
+ )
1949
+
1950
+ raise HTTPError from err
1951
+
1952
+ try:
1953
+ response_json = req.json()
1954
+
1955
+ except ValueError as err:
1956
+ raise PyEzvizError("Could not decode response:" + str(err)) from err
1957
+
1958
+ if response_json["resultCode"] != "0":
1959
+ if response_json["resultCode"] == "-1":
1960
+ _LOGGER.warning(
1961
+ "Camera %s is offline or unreachable, retrying %s of %s",
1962
+ serial,
1963
+ max_retries,
1964
+ MAX_RETRIES,
1965
+ )
1966
+ return self.get_detection_sensibility(
1967
+ serial, type_value, max_retries + 1
1968
+ )
1969
+ raise PyEzvizError(
1970
+ f"Unable to get detection sensibility. Got: {response_json}"
1971
+ )
1972
+
1973
+ if response_json["algorithmConfig"]["algorithmList"]:
1974
+ for idx in response_json["algorithmConfig"]["algorithmList"]:
1975
+ if idx["type"] == type_value:
1976
+ return idx["value"]
1977
+
1978
+ return None
1979
+
1980
+ # soundtype: 0 = normal, 1 = intensive, 2 = disabled ... don't ask me why...
1981
+ def alarm_sound(
1982
+ self, serial: str, sound_type: int, enable: int = 1, max_retries: int = 0
1983
+ ) -> bool:
1984
+ """Enable alarm sound by API."""
1985
+ if max_retries > MAX_RETRIES:
1986
+ raise PyEzvizError("Can't gather proper data. Max retries exceeded.")
1987
+
1988
+ if sound_type not in [0, 1, 2]:
1989
+ raise PyEzvizError(
1990
+ "Invalid sound_type, should be 0,1,2: " + str(sound_type)
1991
+ )
1992
+
1993
+ try:
1994
+ req = self._session.put(
1995
+ "https://"
1996
+ + self._token["api_url"]
1997
+ + API_ENDPOINT_DEVICES
1998
+ + serial
1999
+ + API_ENDPOINT_ALARM_SOUND,
2000
+ data={
2001
+ "enable": enable,
2002
+ "soundType": sound_type,
2003
+ "voiceId": "0",
2004
+ "deviceSerial": serial,
2005
+ },
2006
+ timeout=self._timeout,
2007
+ )
2008
+
2009
+ req.raise_for_status()
2010
+
2011
+ except requests.HTTPError as err:
2012
+ if err.response.status_code == 401:
2013
+ # session is wrong, need to re-log-in
2014
+ self.login()
2015
+ return self.alarm_sound(serial, sound_type, enable, max_retries + 1)
2016
+
2017
+ raise HTTPError from err
2018
+
2019
+ try:
2020
+ response_json = req.json()
2021
+
2022
+ except ValueError as err:
2023
+ raise PyEzvizError("Could not decode response:" + str(err)) from err
2024
+
2025
+ _LOGGER.debug("Response: %s", response_json)
2026
+
2027
+ return True
2028
+
2029
+ def _get_page_list(self) -> Any:
2030
+ """Get ezviz device info broken down in sections."""
2031
+ return self._api_get_pagelist(
2032
+ page_filter="CLOUD, TIME_PLAN, CONNECTION, SWITCH,"
2033
+ "STATUS, WIFI, NODISTURB, KMS,"
2034
+ "P2P, TIME_PLAN, CHANNEL, VTM, DETECTOR,"
2035
+ "FEATURE, CUSTOM_TAG, UPGRADE, VIDEO_QUALITY,"
2036
+ "QOS, PRODUCTS_INFO, SIM_CARD, MULTI_UPGRADE_EXT,"
2037
+ "FEATURE_INFO",
2038
+ json_key=None,
2039
+ )
2040
+
2041
+ def get_device(self) -> Any:
2042
+ """Get ezviz devices filter."""
2043
+ return self._api_get_pagelist(page_filter="CLOUD", json_key="deviceInfos")
2044
+
2045
+ def get_connection(self) -> Any:
2046
+ """Get ezviz connection infos filter."""
2047
+ return self._api_get_pagelist(
2048
+ page_filter="CONNECTION", json_key="CONNECTION"
2049
+ )
2050
+
2051
+ def _get_status(self) -> Any:
2052
+ """Get ezviz status infos filter."""
2053
+ return self._api_get_pagelist(page_filter="STATUS", json_key="STATUS")
2054
+
2055
+ def get_switch(self) -> Any:
2056
+ """Get ezviz switch infos filter."""
2057
+ return self._api_get_pagelist(
2058
+ page_filter="SWITCH", json_key="SWITCH"
2059
+ )
2060
+
2061
+ def _get_wifi(self) -> Any:
2062
+ """Get ezviz wifi infos filter."""
2063
+ return self._api_get_pagelist(page_filter="WIFI", json_key="WIFI")
2064
+
2065
+ def _get_nodisturb(self) -> Any:
2066
+ """Get ezviz nodisturb infos filter."""
2067
+ return self._api_get_pagelist(
2068
+ page_filter="NODISTURB", json_key="NODISTURB"
2069
+ )
2070
+
2071
+ def _get_p2p(self) -> Any:
2072
+ """Get ezviz P2P infos filter."""
2073
+ return self._api_get_pagelist(page_filter="P2P", json_key="P2P")
2074
+
2075
+ def _get_kms(self) -> Any:
2076
+ """Get ezviz KMS infos filter."""
2077
+ return self._api_get_pagelist(page_filter="KMS", json_key="KMS")
2078
+
2079
+ def _get_time_plan(self) -> Any:
2080
+ """Get ezviz TIME_PLAN infos filter."""
2081
+ return self._api_get_pagelist(page_filter="TIME_PLAN", json_key="TIME_PLAN")
2082
+
2083
+ def close_session(self) -> None:
2084
+ """Clear current session."""
2085
+ if self._session:
2086
+ self._session.close()
2087
+
2088
+ self._session = requests.session()
2089
+ self._session.headers.update(REQUEST_HEADER) # Reset session.