pyezvizapi 1.0.2.3__tar.gz → 1.0.2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyezvizapi might be problematic. Click here for more details.

Files changed (27) hide show
  1. {pyezvizapi-1.0.2.3/pyezvizapi.egg-info → pyezvizapi-1.0.2.5}/PKG-INFO +1 -1
  2. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/__main__.py +25 -8
  3. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/cas.py +4 -2
  4. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/client.py +99 -22
  5. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/constants.py +1 -1
  6. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/test_cam_rtsp.py +37 -13
  7. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/utils.py +10 -8
  8. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5/pyezvizapi.egg-info}/PKG-INFO +1 -1
  9. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/setup.py +1 -1
  10. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/LICENSE +0 -0
  11. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/LICENSE.md +0 -0
  12. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/MANIFEST.in +0 -0
  13. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/README.md +0 -0
  14. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/__init__.py +0 -0
  15. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/api_endpoints.py +0 -0
  16. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/camera.py +0 -0
  17. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/exceptions.py +0 -0
  18. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/light_bulb.py +0 -0
  19. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/models.py +0 -0
  20. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/mqtt.py +0 -0
  21. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi/test_mqtt.py +0 -0
  22. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi.egg-info/SOURCES.txt +0 -0
  23. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi.egg-info/dependency_links.txt +0 -0
  24. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi.egg-info/entry_points.txt +0 -0
  25. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi.egg-info/requires.txt +0 -0
  26. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/pyezvizapi.egg-info/top_level.txt +0 -0
  27. {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyezvizapi
3
- Version: 1.0.2.3
3
+ Version: 1.0.2.5
4
4
  Summary: Pilot your Ezviz cameras
5
5
  Home-page: https://github.com/RenierM26/pyEzvizApi/
6
6
  Author: Renier Moorcroft
@@ -26,7 +26,9 @@ _LOGGER = logging.getLogger(__name__)
26
26
  def _setup_logging(debug: bool) -> None:
27
27
  """Configure root logger for CLI usage."""
28
28
  level = logging.DEBUG if debug else logging.INFO
29
- logging.basicConfig(level=level, stream=sys.stderr, format="%(levelname)s: %(message)s")
29
+ logging.basicConfig(
30
+ level=level, stream=sys.stderr, format="%(levelname)s: %(message)s"
31
+ )
30
32
  if debug:
31
33
  # Verbose requests logging in debug mode
32
34
  requests_log = logging.getLogger("requests.packages.urllib3")
@@ -94,7 +96,7 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
94
96
  type=str,
95
97
  default="status",
96
98
  help="Light bulbs action to perform",
97
- choices=["status"]
99
+ choices=["status"],
98
100
  )
99
101
  parser_device_lights.add_argument(
100
102
  "--refresh",
@@ -125,7 +127,9 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
125
127
 
126
128
  subparsers_camera = parser_camera.add_subparsers(dest="camera_action")
127
129
 
128
- parser_camera_status = subparsers_camera.add_parser("status", help="Get the status of the camera")
130
+ parser_camera_status = subparsers_camera.add_parser(
131
+ "status", help="Get the status of the camera"
132
+ )
129
133
  parser_camera_status.add_argument(
130
134
  "--refresh",
131
135
  action=argparse.BooleanOptionalAction,
@@ -229,14 +233,19 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
229
233
  )
230
234
 
231
235
  parser_camera_select = subparsers_camera.add_parser(
232
- "select", help="Change the value of a multi-value option (for on/off value, see 'switch' command)"
236
+ "select",
237
+ help="Change the value of a multi-value option (for on/off value, see 'switch' command)",
233
238
  )
234
239
 
235
240
  parser_camera_select.add_argument(
236
241
  "--battery_work_mode",
237
242
  required=False,
238
243
  help="Change the work mode for battery powered camera",
239
- choices=[mode.name for mode in BatteryCameraWorkMode if mode is not BatteryCameraWorkMode.UNKNOWN],
244
+ choices=[
245
+ mode.name
246
+ for mode in BatteryCameraWorkMode
247
+ if mode is not BatteryCameraWorkMode.UNKNOWN
248
+ ],
240
249
  )
241
250
 
242
251
  # Dump full pagelist for exploration
@@ -244,7 +253,8 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
244
253
 
245
254
  # Dump device infos mapping (optionally for a single serial)
246
255
  parser_device_infos = subparsers.add_parser(
247
- "device_infos", help="Output device infos (raw JSON), optionally filtered by serial"
256
+ "device_infos",
257
+ help="Output device infos (raw JSON), optionally filtered by serial",
248
258
  )
249
259
  parser_device_infos.add_argument(
250
260
  "--serial", required=False, help="Optional serial to filter a single device"
@@ -418,7 +428,11 @@ def _handle_pagelist(client: EzvizClient) -> int:
418
428
 
419
429
  def _handle_device_infos(args: argparse.Namespace, client: EzvizClient) -> int:
420
430
  """Output device infos mapping (raw JSON), optionally filtered by serial."""
421
- data = client.get_device_infos(args.serial) if args.serial else client.get_device_infos()
431
+ data = (
432
+ client.get_device_infos(args.serial)
433
+ if args.serial
434
+ else client.get_device_infos()
435
+ )
422
436
  _write_json(data)
423
437
  return 0
424
438
 
@@ -535,7 +549,10 @@ def _load_token_file(path: str | None) -> dict[str, Any] | None:
535
549
  return None
536
550
  try:
537
551
  return cast(dict[str, Any], json.loads(p.read_text(encoding="utf-8")))
538
- except (OSError, json.JSONDecodeError): # pragma: no cover - tolerate malformed file
552
+ except (
553
+ OSError,
554
+ json.JSONDecodeError,
555
+ ): # pragma: no cover - tolerate malformed file
539
556
  _LOGGER.warning("Failed to read token file: %s", p)
540
557
  return None
541
558
 
@@ -38,7 +38,9 @@ class EzvizCAS:
38
38
  "api_url": "apiieu.ezvizlife.com",
39
39
  }
40
40
  if not token or "service_urls" not in token:
41
- raise PyEzvizError("Missing service_urls in token; call EzvizClient.login() first")
41
+ raise PyEzvizError(
42
+ "Missing service_urls in token; call EzvizClient.login() first"
43
+ )
42
44
  self._service_urls: dict[str, Any] = token["service_urls"]
43
45
 
44
46
  def cas_get_encryption(self, devserial: str) -> dict[str, Any]:
@@ -53,7 +55,7 @@ class EzvizCAS:
53
55
  f"\x01" # Check or order?
54
56
  f"\x00\x00\x00\x00\x00\x00\x02\t\x00\x00\x00\x00"
55
57
  f'<?xml version="1.0" encoding="utf-8"?>\n<Request>\n\t'
56
- f'<ClientID>{self._token["session_id"]}</ClientID>'
58
+ f"<ClientID>{self._token['session_id']}</ClientID>"
57
59
  f"\n\t<Sign>{FEATURE_CODE}</Sign>\n\t"
58
60
  f"<DevSerial>{devserial}</DevSerial>"
59
61
  f"\n\t<ClientType>0</ClientType>\n</Request>\n"
@@ -167,7 +167,7 @@ class EzvizClient:
167
167
  DeviceCatagories.BASE_STATION_DEVICE_CATEGORY.value,
168
168
  DeviceCatagories.CAT_EYE_CATEGORY.value,
169
169
  DeviceCatagories.LIGHTING.value,
170
- DeviceCatagories.W2H_BASE_STATION_DEVICE_CATEGORY.value, # @emeric699 Adding support for W2H Base Station
170
+ DeviceCatagories.W2H_BASE_STATION_DEVICE_CATEGORY.value,
171
171
  ]
172
172
 
173
173
  def __init__(
@@ -189,7 +189,8 @@ class EzvizClient:
189
189
  self._session.headers["sessionId"] = str(token["session_id"]) # ensure str
190
190
  self._token: ClientToken = cast(
191
191
  ClientToken,
192
- token or {
192
+ token
193
+ or {
193
194
  "session_id": None,
194
195
  "rf_session_id": None,
195
196
  "username": None,
@@ -262,7 +263,7 @@ class EzvizClient:
262
263
  if json_result["meta"]["code"] == 1100:
263
264
  self._token["api_url"] = json_result["loginArea"]["apiDomain"]
264
265
  _LOGGER.warning(
265
- "region_incorrect: serial=%s code=%s msg=%s",
266
+ "Region_incorrect: serial=%s code=%s msg=%s",
266
267
  "unknown",
267
268
  1100,
268
269
  self._token["api_url"],
@@ -319,7 +320,11 @@ class EzvizClient:
319
320
  )
320
321
  req.raise_for_status()
321
322
  except requests.HTTPError as err:
322
- if retry_401 and err.response is not None and err.response.status_code == 401:
323
+ if (
324
+ retry_401
325
+ and err.response is not None
326
+ and err.response.status_code == 401
327
+ ):
323
328
  if max_retries >= MAX_RETRIES:
324
329
  raise HTTPError from err
325
330
  # Re-login and retry once
@@ -344,7 +349,10 @@ class EzvizClient:
344
349
  return cast(dict, resp.json())
345
350
  except ValueError as err:
346
351
  raise PyEzvizError(
347
- "Impossible to decode response: " + str(err) + "\nResponse was: " + str(resp.text)
352
+ "Impossible to decode response: "
353
+ + str(err)
354
+ + "\nResponse was: "
355
+ + str(resp.text)
348
356
  ) from err
349
357
 
350
358
  @staticmethod
@@ -413,11 +421,17 @@ class EzvizClient:
413
421
  req = self._session.send(request=prepared, timeout=self._timeout)
414
422
  req.raise_for_status()
415
423
  except requests.HTTPError as err:
416
- if retry_401 and err.response is not None and err.response.status_code == 401:
424
+ if (
425
+ retry_401
426
+ and err.response is not None
427
+ and err.response.status_code == 401
428
+ ):
417
429
  if max_retries >= MAX_RETRIES:
418
430
  raise HTTPError from err
419
431
  self.login()
420
- return self._send_prepared(prepared, retry_401=retry_401, max_retries=max_retries + 1)
432
+ return self._send_prepared(
433
+ prepared, retry_401=retry_401, max_retries=max_retries + 1
434
+ )
421
435
  raise HTTPError from err
422
436
  return req
423
437
 
@@ -478,7 +492,7 @@ class EzvizClient:
478
492
  # Prefer modern meta.code; fall back to legacy resultCode
479
493
  code = self._response_code(payload)
480
494
  _LOGGER.warning(
481
- "http_retry: serial=%s code=%s msg=%s",
495
+ "Http_retry: serial=%s code=%s msg=%s",
482
496
  serial or "unknown",
483
497
  code,
484
498
  log,
@@ -549,7 +563,7 @@ class EzvizClient:
549
563
  # session is wrong, need to relogin and retry
550
564
  self.login()
551
565
  _LOGGER.warning(
552
- "http_retry: serial=%s code=%s msg=%s",
566
+ "Http_retry: serial=%s code=%s msg=%s",
553
567
  "unknown",
554
568
  self._meta_code(json_output),
555
569
  "pagelist_relogin",
@@ -673,7 +687,11 @@ class EzvizClient:
673
687
  json_output = self._request_json(
674
688
  "PUT",
675
689
  f"{API_ENDPOINT_DEVICES}{serial}{API_ENDPOINT_SWITCH_OTHER}",
676
- params={"channelNo": channel_number, "enable": enable, "switchType": status_type},
690
+ params={
691
+ "channelNo": channel_number,
692
+ "enable": enable,
693
+ "switchType": status_type,
694
+ },
677
695
  retry_401=True,
678
696
  max_retries=max_retries,
679
697
  )
@@ -704,7 +722,9 @@ class EzvizClient:
704
722
  serial=serial,
705
723
  )
706
724
  if self._meta_code(json_output) != 200:
707
- raise PyEzvizError(f"Could not arm or disarm Camera {serial}: Got {json_output})")
725
+ raise PyEzvizError(
726
+ f"Could not arm or disarm Camera {serial}: Got {json_output})"
727
+ )
708
728
  return True
709
729
 
710
730
  def set_battery_camera_work_mode(self, serial: str, value: int) -> bool:
@@ -712,11 +732,50 @@ class EzvizClient:
712
732
  return self.set_device_config_by_key(serial, value, key="batteryCameraWorkMode")
713
733
 
714
734
  def set_detection_mode(self, serial: str, value: int) -> bool:
715
- """Set detection mode."""
735
+ """Set detection mode.
736
+
737
+ Deprecated in favour of set_alarm_detect_human_car() but kept for
738
+ backwards compatibility with older callers inside the integration.
739
+ """
740
+ return self.set_alarm_detect_human_car(serial, value)
741
+
742
+ def set_alarm_detect_human_car(self, serial: str, value: int) -> bool:
743
+ """Update Alarm_DetectHumanCar type on the device."""
716
744
  return self.set_device_config_by_key(
717
745
  serial, value=f'{{"type":{value}}}', key="Alarm_DetectHumanCar"
718
746
  )
719
747
 
748
+ def set_alarm_advanced_detect(self, serial: str, value: int) -> bool:
749
+ """Update Alarm_AdvancedDetect type on the device."""
750
+ return self.set_device_config_by_key(
751
+ serial, value=f'{{"type":{value}}}', key="Alarm_AdvancedDetect"
752
+ )
753
+
754
+ def set_algorithm_param(
755
+ self,
756
+ serial: str,
757
+ subtype: str | int,
758
+ value: int,
759
+ channel: int = 1,
760
+ ) -> bool:
761
+ """Update a single AlgorithmInfo subtype value via devconfig."""
762
+
763
+ payload = {
764
+ "AlgorithmInfo": [
765
+ {
766
+ "SubType": str(subtype),
767
+ "Value": str(value),
768
+ "channel": channel,
769
+ }
770
+ ]
771
+ }
772
+
773
+ return self.set_device_config_by_key(
774
+ serial,
775
+ value=json.dumps(payload, separators=(",", ":")),
776
+ key="AlgorithmInfo",
777
+ )
778
+
720
779
  def set_night_vision_mode(
721
780
  self, serial: str, mode: int, luminance: int = 100
722
781
  ) -> bool:
@@ -1017,9 +1076,14 @@ class EzvizClient:
1017
1076
  self._light_bulbs[device] = EzvizLightBulb(
1018
1077
  self, device, dict(rec.raw)
1019
1078
  ).status()
1020
- except (PyEzvizError, KeyError, TypeError, ValueError) as err: # pragma: no cover - defensive
1079
+ except (
1080
+ PyEzvizError,
1081
+ KeyError,
1082
+ TypeError,
1083
+ ValueError,
1084
+ ) as err: # pragma: no cover - defensive
1021
1085
  _LOGGER.warning(
1022
- "load_device_failed: serial=%s code=%s msg=%s",
1086
+ "Load_device_failed: serial=%s code=%s msg=%s",
1023
1087
  device,
1024
1088
  "load_error",
1025
1089
  str(err),
@@ -1029,9 +1093,14 @@ class EzvizClient:
1029
1093
  # Create camera object
1030
1094
  cam = EzvizCamera(self, device, dict(rec.raw))
1031
1095
  self._cameras[device] = cam.status(refresh=refresh)
1032
- except (PyEzvizError, KeyError, TypeError, ValueError) as err: # pragma: no cover - defensive
1096
+ except (
1097
+ PyEzvizError,
1098
+ KeyError,
1099
+ TypeError,
1100
+ ValueError,
1101
+ ) as err: # pragma: no cover - defensive
1033
1102
  _LOGGER.warning(
1034
- "load_device_failed: serial=%s code=%s msg=%s",
1103
+ "Load_device_failed: serial=%s code=%s msg=%s",
1035
1104
  device,
1036
1105
  "load_error",
1037
1106
  str(err),
@@ -1100,7 +1169,9 @@ class EzvizClient:
1100
1169
  try:
1101
1170
  support_ext = result[_serial].get("deviceInfos", {}).get("supportExt")
1102
1171
  if isinstance(support_ext, str) and support_ext:
1103
- result[_serial]["deviceInfos"]["supportExt"] = json.loads(support_ext)
1172
+ result[_serial]["deviceInfos"]["supportExt"] = json.loads(
1173
+ support_ext
1174
+ )
1104
1175
  except (TypeError, ValueError):
1105
1176
  # Leave as-is if not valid JSON
1106
1177
  pass
@@ -1199,14 +1270,16 @@ class EzvizClient:
1199
1270
 
1200
1271
  code = str(json_output.get("resultCode"))
1201
1272
  if code == "20002":
1202
- raise EzvizAuthVerificationCode(f"MFA code required: Got {json_output})")
1273
+ raise EzvizAuthVerificationCode(
1274
+ f"MFA code required: Got {json_output})"
1275
+ )
1203
1276
  if code == "2009":
1204
1277
  raise DeviceException(f"Device not reachable: Got {json_output})")
1205
1278
  if code == "0":
1206
1279
  return json_output.get("encryptkey")
1207
1280
  if code == "-1" and attempt < attempts:
1208
1281
  _LOGGER.warning(
1209
- "http_retry: serial=%s code=%s msg=%s",
1282
+ "Http_retry: serial=%s code=%s msg=%s",
1210
1283
  serial,
1211
1284
  code,
1212
1285
  "cam_key_not_found",
@@ -1357,7 +1430,9 @@ class EzvizClient:
1357
1430
  raise PyEzvizError(
1358
1431
  f"Could not send command to create panoramic photo: Got {json_output})"
1359
1432
  )
1360
- raise PyEzvizError("Could not send command to create panoramic photo: exceeded retries")
1433
+ raise PyEzvizError(
1434
+ "Could not send command to create panoramic photo: exceeded retries"
1435
+ )
1361
1436
 
1362
1437
  def return_panoramic(self, serial: str, max_retries: int = 0) -> Any:
1363
1438
  """Return panoramic image url list."""
@@ -1526,7 +1601,7 @@ class EzvizClient:
1526
1601
  except requests.HTTPError as err:
1527
1602
  if err.response.status_code == 401:
1528
1603
  _LOGGER.warning(
1529
- "http_warning: serial=%s code=%s msg=%s",
1604
+ "Http_warning: serial=%s code=%s msg=%s",
1530
1605
  "unknown",
1531
1606
  401,
1532
1607
  "logout_already_invalid",
@@ -1678,7 +1753,9 @@ class EzvizClient:
1678
1753
  else:
1679
1754
  raise PyEzvizError(f"Invalid action '{action}'. Use 'add' or 'remove'.")
1680
1755
 
1681
- json_output = self._request_json(method, url_path, retry_401=True, max_retries=max_retries)
1756
+ json_output = self._request_json(
1757
+ method, url_path, retry_401=True, max_retries=max_retries
1758
+ )
1682
1759
  self._ensure_ok(json_output, f"Could not {action} intelligent app")
1683
1760
 
1684
1761
  return True
@@ -464,4 +464,4 @@ class DeviceCatagories(Enum):
464
464
  BASE_STATION_DEVICE_CATEGORY = "XVR"
465
465
  CAT_EYE_CATEGORY = "CatEye"
466
466
  LIGHTING = "lighting"
467
- W2H_BASE_STATION_DEVICE_CATEGORY = "IGateWay" # @emeric699 Adding support for W2H Base Station
467
+ W2H_BASE_STATION_DEVICE_CATEGORY = "IGateWay"
@@ -22,6 +22,7 @@ def genmsg_describe(url: str, seq: int, user_agent: str, auth_seq: str) -> str:
22
22
 
23
23
  class RTSPDetails(TypedDict):
24
24
  """Typed structure for RTSP test parameters."""
25
+
25
26
  bufLen: int
26
27
  defaultServerIp: str
27
28
  defaultServerPort: int
@@ -37,7 +38,11 @@ class TestRTSPAuth:
37
38
  _rtsp_details: RTSPDetails
38
39
 
39
40
  def __init__(
40
- self, ip_addr: str, username: str | None = None, password: str | None = None, test_uri: str = ""
41
+ self,
42
+ ip_addr: str,
43
+ username: str | None = None,
44
+ password: str | None = None,
45
+ test_uri: str = "",
41
46
  ) -> None:
42
47
  """Initialize RTSP credential test."""
43
48
  self._rtsp_details = RTSPDetails(
@@ -50,7 +55,9 @@ class TestRTSPAuth:
50
55
  defaultPassword=password,
51
56
  )
52
57
 
53
- def generate_auth_string(self, realm: bytes, method: str, uri: str, nonce: bytes) -> str:
58
+ def generate_auth_string(
59
+ self, realm: bytes, method: str, uri: str, nonce: bytes
60
+ ) -> str:
54
61
  """Generate the HTTP Digest Authorization header value."""
55
62
  m_1 = hashlib.md5(
56
63
  f"{self._rtsp_details['defaultUsername']}:{realm.decode()}:{self._rtsp_details['defaultPassword']}".encode()
@@ -60,12 +67,12 @@ class TestRTSPAuth:
60
67
 
61
68
  return (
62
69
  "Digest "
63
- f"username=\"{self._rtsp_details['defaultUsername']}\", "
64
- f"realm=\"{realm.decode()}\", "
70
+ f'username="{self._rtsp_details["defaultUsername"]}", '
71
+ f'realm="{realm.decode()}", '
65
72
  'algorithm="MD5", '
66
- f"nonce=\"{nonce.decode()}\", "
67
- f"uri=\"{uri}\", "
68
- f"response=\"{response}\""
73
+ f'nonce="{nonce.decode()}", '
74
+ f'uri="{uri}", '
75
+ f'response="{response}"'
69
76
  )
70
77
 
71
78
  def main(self) -> None:
@@ -74,7 +81,10 @@ class TestRTSPAuth:
74
81
 
75
82
  try:
76
83
  session.connect(
77
- (self._rtsp_details["defaultServerIp"], self._rtsp_details["defaultServerPort"])
84
+ (
85
+ self._rtsp_details["defaultServerIp"],
86
+ self._rtsp_details["defaultServerPort"],
87
+ )
78
88
  )
79
89
  except TimeoutError as err:
80
90
  raise AuthTestResultFailed("Invalid ip or camera hibernating") from err
@@ -83,15 +93,23 @@ class TestRTSPAuth:
83
93
 
84
94
  seq: int = 1
85
95
 
86
- url: str = "rtsp://" + self._rtsp_details["defaultServerIp"] + self._rtsp_details["defaultTestUri"]
96
+ url: str = (
97
+ "rtsp://"
98
+ + self._rtsp_details["defaultServerIp"]
99
+ + self._rtsp_details["defaultTestUri"]
100
+ )
87
101
 
88
102
  # Basic Authorization header
89
103
  auth_b64: bytes = base64.b64encode(
90
- f"{self._rtsp_details['defaultUsername']}:{self._rtsp_details['defaultPassword']}".encode("ascii")
104
+ f"{self._rtsp_details['defaultUsername']}:{self._rtsp_details['defaultPassword']}".encode(
105
+ "ascii"
106
+ )
91
107
  )
92
108
  auth_seq: str = "Basic " + auth_b64.decode()
93
109
 
94
- describe = genmsg_describe(url, seq, self._rtsp_details["defaultUserAgent"], auth_seq)
110
+ describe = genmsg_describe(
111
+ url, seq, self._rtsp_details["defaultUserAgent"], auth_seq
112
+ )
95
113
  print(describe)
96
114
  session.send(describe.encode())
97
115
  msg1: bytes = session.recv(self._rtsp_details["bufLen"])
@@ -115,9 +133,13 @@ class TestRTSPAuth:
115
133
  end = decoded.find('"', begin + 1)
116
134
  nonce: bytes = msg1[begin + 1 : end]
117
135
 
118
- auth_seq = self.generate_auth_string(realm, "DESCRIBE", self._rtsp_details["defaultTestUri"], nonce)
136
+ auth_seq = self.generate_auth_string(
137
+ realm, "DESCRIBE", self._rtsp_details["defaultTestUri"], nonce
138
+ )
119
139
 
120
- describe = genmsg_describe(url, seq, self._rtsp_details["defaultUserAgent"], auth_seq)
140
+ describe = genmsg_describe(
141
+ url, seq, self._rtsp_details["defaultUserAgent"], auth_seq
142
+ )
121
143
  print(describe)
122
144
  session.send(describe.encode())
123
145
  msg1 = session.recv(self._rtsp_details["bufLen"])
@@ -132,4 +154,6 @@ class TestRTSPAuth:
132
154
  raise AuthTestResultFailed("Credentials not valid!!")
133
155
 
134
156
  print("Basic Auth test passed. Credentials Valid!")
157
+
158
+
135
159
  # ruff: noqa: T201
@@ -197,6 +197,7 @@ def generate_unique_code() -> str:
197
197
  # Time helpers for alarm/motion handling
198
198
  # ---------------------------------------------------------------------------
199
199
 
200
+
200
201
  def normalize_alarm_time(
201
202
  last_alarm: dict[str, Any], tzinfo: datetime.tzinfo
202
203
  ) -> tuple[datetime.datetime | None, datetime.datetime | None, str | None]:
@@ -241,14 +242,15 @@ def normalize_alarm_time(
241
242
  raw_norm, "%Y-%m-%d %H:%M:%S"
242
243
  ).replace(tzinfo=tzinfo)
243
244
  diff = abs(
244
- (event_utc - dt_str_local.astimezone(datetime.UTC)).total_seconds()
245
+ (
246
+ event_utc - dt_str_local.astimezone(datetime.UTC)
247
+ ).total_seconds()
245
248
  )
246
249
  if diff > 120:
247
250
  # Reinterpret epoch as local clock time in camera tz
248
- naive_utc = (
249
- datetime.datetime.fromtimestamp(ts, tz=datetime.UTC)
250
- .replace(tzinfo=None)
251
- )
251
+ naive_utc = datetime.datetime.fromtimestamp(
252
+ ts, tz=datetime.UTC
253
+ ).replace(tzinfo=None)
252
254
  event_local_reint = naive_utc.replace(tzinfo=tzinfo)
253
255
  alarm_dt_local = event_local_reint
254
256
  alarm_dt_utc = event_local_reint.astimezone(datetime.UTC)
@@ -266,9 +268,9 @@ def normalize_alarm_time(
266
268
  if raw_time_str:
267
269
  raw = raw_time_str.replace("Today", str(now_local.date()))
268
270
  try:
269
- alarm_dt_local = datetime.datetime.strptime(raw, "%Y-%m-%d %H:%M:%S").replace(
270
- tzinfo=tzinfo
271
- )
271
+ alarm_dt_local = datetime.datetime.strptime(
272
+ raw, "%Y-%m-%d %H:%M:%S"
273
+ ).replace(tzinfo=tzinfo)
272
274
  alarm_dt_utc = alarm_dt_local.astimezone(datetime.UTC)
273
275
  alarm_str = alarm_dt_local.strftime("%Y-%m-%d %H:%M:%S")
274
276
  except ValueError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyezvizapi
3
- Version: 1.0.2.3
3
+ Version: 1.0.2.5
4
4
  Summary: Pilot your Ezviz cameras
5
5
  Home-page: https://github.com/RenierM26/pyEzvizApi/
6
6
  Author: Renier Moorcroft
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name='pyezvizapi',
8
- version="1.0.2.3",
8
+ version="1.0.2.5",
9
9
  license='Apache Software License 2.0',
10
10
  author='Renier Moorcroft',
11
11
  author_email='RenierM26@users.github.com',
File without changes
File without changes
File without changes
File without changes
File without changes