pyezvizapi 1.0.2.3__tar.gz → 1.0.2.4__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.
- {pyezvizapi-1.0.2.3/pyezvizapi.egg-info → pyezvizapi-1.0.2.4}/PKG-INFO +1 -1
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/__main__.py +25 -8
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/cas.py +4 -2
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/client.py +59 -21
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/constants.py +1 -1
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/test_cam_rtsp.py +37 -13
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/utils.py +10 -8
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4/pyezvizapi.egg-info}/PKG-INFO +1 -1
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/setup.py +1 -1
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/LICENSE +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/LICENSE.md +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/MANIFEST.in +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/README.md +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/__init__.py +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/api_endpoints.py +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/camera.py +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/exceptions.py +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/light_bulb.py +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/models.py +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/mqtt.py +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi/test_mqtt.py +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi.egg-info/SOURCES.txt +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi.egg-info/dependency_links.txt +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi.egg-info/entry_points.txt +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi.egg-info/requires.txt +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/pyezvizapi.egg-info/top_level.txt +0 -0
- {pyezvizapi-1.0.2.3 → pyezvizapi-1.0.2.4}/setup.cfg +0 -0
|
@@ -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
|
|
|
@@ -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"
|
|
@@ -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:
|
|
@@ -1017,9 +1037,14 @@ class EzvizClient:
|
|
|
1017
1037
|
self._light_bulbs[device] = EzvizLightBulb(
|
|
1018
1038
|
self, device, dict(rec.raw)
|
|
1019
1039
|
).status()
|
|
1020
|
-
except (
|
|
1040
|
+
except (
|
|
1041
|
+
PyEzvizError,
|
|
1042
|
+
KeyError,
|
|
1043
|
+
TypeError,
|
|
1044
|
+
ValueError,
|
|
1045
|
+
) as err: # pragma: no cover - defensive
|
|
1021
1046
|
_LOGGER.warning(
|
|
1022
|
-
"
|
|
1047
|
+
"Load_device_failed: serial=%s code=%s msg=%s",
|
|
1023
1048
|
device,
|
|
1024
1049
|
"load_error",
|
|
1025
1050
|
str(err),
|
|
@@ -1029,9 +1054,14 @@ class EzvizClient:
|
|
|
1029
1054
|
# Create camera object
|
|
1030
1055
|
cam = EzvizCamera(self, device, dict(rec.raw))
|
|
1031
1056
|
self._cameras[device] = cam.status(refresh=refresh)
|
|
1032
|
-
except (
|
|
1057
|
+
except (
|
|
1058
|
+
PyEzvizError,
|
|
1059
|
+
KeyError,
|
|
1060
|
+
TypeError,
|
|
1061
|
+
ValueError,
|
|
1062
|
+
) as err: # pragma: no cover - defensive
|
|
1033
1063
|
_LOGGER.warning(
|
|
1034
|
-
"
|
|
1064
|
+
"Load_device_failed: serial=%s code=%s msg=%s",
|
|
1035
1065
|
device,
|
|
1036
1066
|
"load_error",
|
|
1037
1067
|
str(err),
|
|
@@ -1100,7 +1130,9 @@ class EzvizClient:
|
|
|
1100
1130
|
try:
|
|
1101
1131
|
support_ext = result[_serial].get("deviceInfos", {}).get("supportExt")
|
|
1102
1132
|
if isinstance(support_ext, str) and support_ext:
|
|
1103
|
-
result[_serial]["deviceInfos"]["supportExt"] = json.loads(
|
|
1133
|
+
result[_serial]["deviceInfos"]["supportExt"] = json.loads(
|
|
1134
|
+
support_ext
|
|
1135
|
+
)
|
|
1104
1136
|
except (TypeError, ValueError):
|
|
1105
1137
|
# Leave as-is if not valid JSON
|
|
1106
1138
|
pass
|
|
@@ -1199,14 +1231,16 @@ class EzvizClient:
|
|
|
1199
1231
|
|
|
1200
1232
|
code = str(json_output.get("resultCode"))
|
|
1201
1233
|
if code == "20002":
|
|
1202
|
-
raise EzvizAuthVerificationCode(
|
|
1234
|
+
raise EzvizAuthVerificationCode(
|
|
1235
|
+
f"MFA code required: Got {json_output})"
|
|
1236
|
+
)
|
|
1203
1237
|
if code == "2009":
|
|
1204
1238
|
raise DeviceException(f"Device not reachable: Got {json_output})")
|
|
1205
1239
|
if code == "0":
|
|
1206
1240
|
return json_output.get("encryptkey")
|
|
1207
1241
|
if code == "-1" and attempt < attempts:
|
|
1208
1242
|
_LOGGER.warning(
|
|
1209
|
-
"
|
|
1243
|
+
"Http_retry: serial=%s code=%s msg=%s",
|
|
1210
1244
|
serial,
|
|
1211
1245
|
code,
|
|
1212
1246
|
"cam_key_not_found",
|
|
@@ -1357,7 +1391,9 @@ class EzvizClient:
|
|
|
1357
1391
|
raise PyEzvizError(
|
|
1358
1392
|
f"Could not send command to create panoramic photo: Got {json_output})"
|
|
1359
1393
|
)
|
|
1360
|
-
raise PyEzvizError(
|
|
1394
|
+
raise PyEzvizError(
|
|
1395
|
+
"Could not send command to create panoramic photo: exceeded retries"
|
|
1396
|
+
)
|
|
1361
1397
|
|
|
1362
1398
|
def return_panoramic(self, serial: str, max_retries: int = 0) -> Any:
|
|
1363
1399
|
"""Return panoramic image url list."""
|
|
@@ -1526,7 +1562,7 @@ class EzvizClient:
|
|
|
1526
1562
|
except requests.HTTPError as err:
|
|
1527
1563
|
if err.response.status_code == 401:
|
|
1528
1564
|
_LOGGER.warning(
|
|
1529
|
-
"
|
|
1565
|
+
"Http_warning: serial=%s code=%s msg=%s",
|
|
1530
1566
|
"unknown",
|
|
1531
1567
|
401,
|
|
1532
1568
|
"logout_already_invalid",
|
|
@@ -1678,7 +1714,9 @@ class EzvizClient:
|
|
|
1678
1714
|
else:
|
|
1679
1715
|
raise PyEzvizError(f"Invalid action '{action}'. Use 'add' or 'remove'.")
|
|
1680
1716
|
|
|
1681
|
-
json_output = self._request_json(
|
|
1717
|
+
json_output = self._request_json(
|
|
1718
|
+
method, url_path, retry_401=True, max_retries=max_retries
|
|
1719
|
+
)
|
|
1682
1720
|
self._ensure_ok(json_output, f"Could not {action} intelligent app")
|
|
1683
1721
|
|
|
1684
1722
|
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"
|
|
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,
|
|
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
|
|
@@ -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:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|