pyezvizapi 1.0.2.3__py3-none-any.whl → 1.0.2.5__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/__main__.py +25 -8
- pyezvizapi/cas.py +4 -2
- pyezvizapi/client.py +99 -22
- pyezvizapi/constants.py +1 -1
- pyezvizapi/test_cam_rtsp.py +37 -13
- pyezvizapi/utils.py +10 -8
- {pyezvizapi-1.0.2.3.dist-info → pyezvizapi-1.0.2.5.dist-info}/METADATA +1 -1
- pyezvizapi-1.0.2.5.dist-info/RECORD +21 -0
- pyezvizapi-1.0.2.3.dist-info/RECORD +0 -21
- {pyezvizapi-1.0.2.3.dist-info → pyezvizapi-1.0.2.5.dist-info}/WHEEL +0 -0
- {pyezvizapi-1.0.2.3.dist-info → pyezvizapi-1.0.2.5.dist-info}/entry_points.txt +0 -0
- {pyezvizapi-1.0.2.3.dist-info → pyezvizapi-1.0.2.5.dist-info}/licenses/LICENSE +0 -0
- {pyezvizapi-1.0.2.3.dist-info → pyezvizapi-1.0.2.5.dist-info}/licenses/LICENSE.md +0 -0
- {pyezvizapi-1.0.2.3.dist-info → pyezvizapi-1.0.2.5.dist-info}/top_level.txt +0 -0
pyezvizapi/__main__.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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",
|
|
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=[
|
|
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",
|
|
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 =
|
|
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 (
|
|
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
|
|
pyezvizapi/cas.py
CHANGED
|
@@ -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(
|
|
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
|
|
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"
|
pyezvizapi/client.py
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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: "
|
|
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
|
|
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(
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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={
|
|
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(
|
|
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 (
|
|
1079
|
+
except (
|
|
1080
|
+
PyEzvizError,
|
|
1081
|
+
KeyError,
|
|
1082
|
+
TypeError,
|
|
1083
|
+
ValueError,
|
|
1084
|
+
) as err: # pragma: no cover - defensive
|
|
1021
1085
|
_LOGGER.warning(
|
|
1022
|
-
"
|
|
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 (
|
|
1096
|
+
except (
|
|
1097
|
+
PyEzvizError,
|
|
1098
|
+
KeyError,
|
|
1099
|
+
TypeError,
|
|
1100
|
+
ValueError,
|
|
1101
|
+
) as err: # pragma: no cover - defensive
|
|
1033
1102
|
_LOGGER.warning(
|
|
1034
|
-
"
|
|
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(
|
|
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(
|
|
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
|
-
"
|
|
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(
|
|
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
|
-
"
|
|
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(
|
|
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
|
pyezvizapi/constants.py
CHANGED
|
@@ -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"
|
|
467
|
+
W2H_BASE_STATION_DEVICE_CATEGORY = "IGateWay"
|
pyezvizapi/test_cam_rtsp.py
CHANGED
|
@@ -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,
|
|
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(
|
|
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
|
|
64
|
-
f
|
|
70
|
+
f'username="{self._rtsp_details["defaultUsername"]}", '
|
|
71
|
+
f'realm="{realm.decode()}", '
|
|
65
72
|
'algorithm="MD5", '
|
|
66
|
-
f
|
|
67
|
-
f
|
|
68
|
-
f
|
|
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
|
-
(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
136
|
+
auth_seq = self.generate_auth_string(
|
|
137
|
+
realm, "DESCRIBE", self._rtsp_details["defaultTestUri"], nonce
|
|
138
|
+
)
|
|
119
139
|
|
|
120
|
-
describe = genmsg_describe(
|
|
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
|
pyezvizapi/utils.py
CHANGED
|
@@ -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
|
-
(
|
|
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
|
-
|
|
250
|
-
|
|
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(
|
|
270
|
-
|
|
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:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
pyezvizapi/__init__.py,sha256=IDnIN_nfIISVwuy0cVBh4wspgAav6MuOJCQGajjyU3g,1881
|
|
2
|
+
pyezvizapi/__main__.py,sha256=9uttTuOfO22tzyomJIV8ebFJ-G-YUNDYOadZ_0AgdNA,20925
|
|
3
|
+
pyezvizapi/api_endpoints.py,sha256=rk6VinLVCn-B6DxnhfV79liplNpgUsipNbTEa_MRVwU,2755
|
|
4
|
+
pyezvizapi/camera.py,sha256=Pl5oIEdrFcv1Hz5sQI1IyyJIDCMjOjQdtExgKzmLoK8,22102
|
|
5
|
+
pyezvizapi/cas.py,sha256=3zHe-_a0KchCmGeAj1of-pV6oMPRUmSCIiDqBFsTK8A,6025
|
|
6
|
+
pyezvizapi/client.py,sha256=Wvl5URCYXt0euze2w3vEilR5urcc4LTage3sCLqZIOo,73683
|
|
7
|
+
pyezvizapi/constants.py,sha256=RPyKUj0OX2VypIzcGcmmnLPDvDlCMUaZGpwADB53q4U,12585
|
|
8
|
+
pyezvizapi/exceptions.py,sha256=8rmxEUQdrziqMe-M1SeeRd0HtP2IDQ2xpJVj7wvOQyo,976
|
|
9
|
+
pyezvizapi/light_bulb.py,sha256=9wgycG3dTvBbrsxQjQnXal-GA8VXPsIN1m-CTtRh8i0,7797
|
|
10
|
+
pyezvizapi/models.py,sha256=NQzwTP0yEe2IWU-Vc6nAn87xulpTuo0MX2Rcf0WxifA,4176
|
|
11
|
+
pyezvizapi/mqtt.py,sha256=aOL-gexZgYvCCaNQ03M4vZan91d5p2Fl_qsFykn9NW4,22365
|
|
12
|
+
pyezvizapi/test_cam_rtsp.py,sha256=O9NHh-vcNFfnzNw8jbuhM9a_5TWfNZIMXaJP7Lmkaj4,5162
|
|
13
|
+
pyezvizapi/test_mqtt.py,sha256=Orn-fwZPJIE4G5KROMX0MRAkLwU6nLb9LUtXyb2ZCQs,4147
|
|
14
|
+
pyezvizapi/utils.py,sha256=G8gGjG0ecdN05Y0vxOHvcQMtQXgVB7nHzyvCzz66kLk,12148
|
|
15
|
+
pyezvizapi-1.0.2.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
16
|
+
pyezvizapi-1.0.2.5.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
17
|
+
pyezvizapi-1.0.2.5.dist-info/METADATA,sha256=bnksAask1LcIhYS8ZDIq2Yb7HCnE4tsfhHcbXMhy-yE,695
|
|
18
|
+
pyezvizapi-1.0.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
pyezvizapi-1.0.2.5.dist-info/entry_points.txt,sha256=_BSJ3eNb2H_AZkRdsv1s4mojqWn3N7m503ujvg1SudA,56
|
|
20
|
+
pyezvizapi-1.0.2.5.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
|
|
21
|
+
pyezvizapi-1.0.2.5.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
pyezvizapi/__init__.py,sha256=IDnIN_nfIISVwuy0cVBh4wspgAav6MuOJCQGajjyU3g,1881
|
|
2
|
-
pyezvizapi/__main__.py,sha256=SeV954H-AV-U1thNxRd7rWTGtSlfWyNzdrjF8gikYus,20777
|
|
3
|
-
pyezvizapi/api_endpoints.py,sha256=rk6VinLVCn-B6DxnhfV79liplNpgUsipNbTEa_MRVwU,2755
|
|
4
|
-
pyezvizapi/camera.py,sha256=Pl5oIEdrFcv1Hz5sQI1IyyJIDCMjOjQdtExgKzmLoK8,22102
|
|
5
|
-
pyezvizapi/cas.py,sha256=ISmb-eTPuacI10L87lULbQ-oDMBuygO7Tf-s9f-tvYE,5995
|
|
6
|
-
pyezvizapi/client.py,sha256=Bp5eQbn4-pjsZicfpWy6jD5bDjQeYFw-SN1p0uzKJRY,71782
|
|
7
|
-
pyezvizapi/constants.py,sha256=SqdJRQSRdVYQxMgJa__AgorzdWglgA4MM4H2fq3QLAE,12633
|
|
8
|
-
pyezvizapi/exceptions.py,sha256=8rmxEUQdrziqMe-M1SeeRd0HtP2IDQ2xpJVj7wvOQyo,976
|
|
9
|
-
pyezvizapi/light_bulb.py,sha256=9wgycG3dTvBbrsxQjQnXal-GA8VXPsIN1m-CTtRh8i0,7797
|
|
10
|
-
pyezvizapi/models.py,sha256=NQzwTP0yEe2IWU-Vc6nAn87xulpTuo0MX2Rcf0WxifA,4176
|
|
11
|
-
pyezvizapi/mqtt.py,sha256=aOL-gexZgYvCCaNQ03M4vZan91d5p2Fl_qsFykn9NW4,22365
|
|
12
|
-
pyezvizapi/test_cam_rtsp.py,sha256=WGSM5EiOTl_r1mWHoMb7bXHm_BCn1P9X_669YQ38r6k,4903
|
|
13
|
-
pyezvizapi/test_mqtt.py,sha256=Orn-fwZPJIE4G5KROMX0MRAkLwU6nLb9LUtXyb2ZCQs,4147
|
|
14
|
-
pyezvizapi/utils.py,sha256=bOWLIytbELQfwBpA2LnhP1m8OthmOk9Vhh_uUXTFpoc,12124
|
|
15
|
-
pyezvizapi-1.0.2.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
16
|
-
pyezvizapi-1.0.2.3.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
17
|
-
pyezvizapi-1.0.2.3.dist-info/METADATA,sha256=xvvts4etMltvpLLaIr0lcKhm1quwBYCJbFqltigk55U,695
|
|
18
|
-
pyezvizapi-1.0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
-
pyezvizapi-1.0.2.3.dist-info/entry_points.txt,sha256=_BSJ3eNb2H_AZkRdsv1s4mojqWn3N7m503ujvg1SudA,56
|
|
20
|
-
pyezvizapi-1.0.2.3.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
|
|
21
|
-
pyezvizapi-1.0.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|