pyezvizapi 1.0.3.0__py3-none-any.whl → 1.0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyezvizapi might be problematic. Click here for more details.
- pyezvizapi/__init__.py +30 -0
- pyezvizapi/api_endpoints.py +49 -0
- pyezvizapi/client.py +2187 -37
- pyezvizapi/feature.py +251 -0
- {pyezvizapi-1.0.3.0.dist-info → pyezvizapi-1.0.3.2.dist-info}/METADATA +1 -1
- {pyezvizapi-1.0.3.0.dist-info → pyezvizapi-1.0.3.2.dist-info}/RECORD +11 -10
- {pyezvizapi-1.0.3.0.dist-info → pyezvizapi-1.0.3.2.dist-info}/WHEEL +0 -0
- {pyezvizapi-1.0.3.0.dist-info → pyezvizapi-1.0.3.2.dist-info}/entry_points.txt +0 -0
- {pyezvizapi-1.0.3.0.dist-info → pyezvizapi-1.0.3.2.dist-info}/licenses/LICENSE +0 -0
- {pyezvizapi-1.0.3.0.dist-info → pyezvizapi-1.0.3.2.dist-info}/licenses/LICENSE.md +0 -0
- {pyezvizapi-1.0.3.0.dist-info → pyezvizapi-1.0.3.2.dist-info}/top_level.txt +0 -0
pyezvizapi/feature.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Helpers for working with Ezviz feature metadata payloads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, MutableMapping
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
from .utils import coerce_int, decode_json
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _feature_video_section(camera_data: Mapping[str, Any]) -> dict[str, Any]:
|
|
12
|
+
"""Return the nested Video feature section from feature info payload."""
|
|
13
|
+
|
|
14
|
+
feature = camera_data.get("FEATURE_INFO")
|
|
15
|
+
if not isinstance(feature, Mapping):
|
|
16
|
+
return {}
|
|
17
|
+
|
|
18
|
+
for group in feature.values():
|
|
19
|
+
if isinstance(group, Mapping):
|
|
20
|
+
video = group.get("Video")
|
|
21
|
+
if isinstance(video, MutableMapping):
|
|
22
|
+
return cast(dict[str, Any], video)
|
|
23
|
+
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def lens_defog_config(camera_data: Mapping[str, Any]) -> dict[str, Any]:
|
|
28
|
+
"""Return the LensCleaning defog configuration if present."""
|
|
29
|
+
|
|
30
|
+
video = _feature_video_section(camera_data)
|
|
31
|
+
lens = video.get("LensCleaning") if isinstance(video, Mapping) else None
|
|
32
|
+
if not isinstance(lens, MutableMapping):
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
config = lens.get("DefogCfg")
|
|
36
|
+
if isinstance(config, MutableMapping):
|
|
37
|
+
return cast(dict[str, Any], config)
|
|
38
|
+
return {}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def lens_defog_value(camera_data: Mapping[str, Any]) -> int:
|
|
42
|
+
"""Return canonical defogging mode (0=auto,1=on,2=off)."""
|
|
43
|
+
|
|
44
|
+
cfg = lens_defog_config(camera_data)
|
|
45
|
+
if not cfg:
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
enabled = bool(cfg.get("enabled"))
|
|
49
|
+
mode = str(cfg.get("defogMode") or "").lower()
|
|
50
|
+
|
|
51
|
+
if not enabled:
|
|
52
|
+
return 2
|
|
53
|
+
|
|
54
|
+
if mode == "open":
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def optionals_mapping(camera_data: Mapping[str, Any]) -> dict[str, Any]:
|
|
61
|
+
"""Return decoded optionals mapping from the camera payload."""
|
|
62
|
+
|
|
63
|
+
status_info = camera_data.get("statusInfo")
|
|
64
|
+
optionals: Any = None
|
|
65
|
+
if isinstance(status_info, Mapping):
|
|
66
|
+
optionals = status_info.get("optionals")
|
|
67
|
+
|
|
68
|
+
optionals = decode_json(optionals)
|
|
69
|
+
|
|
70
|
+
if not isinstance(optionals, Mapping):
|
|
71
|
+
optionals = decode_json(camera_data.get("optionals"))
|
|
72
|
+
|
|
73
|
+
if not isinstance(optionals, Mapping):
|
|
74
|
+
status = camera_data.get("STATUS")
|
|
75
|
+
if isinstance(status, Mapping):
|
|
76
|
+
optionals = decode_json(status.get("optionals"))
|
|
77
|
+
|
|
78
|
+
return dict(optionals) if isinstance(optionals, Mapping) else {}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def display_mode_value(camera_data: Mapping[str, Any]) -> int:
|
|
82
|
+
"""Return display mode value (1..3) from camera data."""
|
|
83
|
+
|
|
84
|
+
optionals = optionals_mapping(camera_data)
|
|
85
|
+
display_mode = optionals.get("display_mode")
|
|
86
|
+
display_mode = decode_json(display_mode)
|
|
87
|
+
|
|
88
|
+
if isinstance(display_mode, Mapping):
|
|
89
|
+
mode = display_mode.get("mode")
|
|
90
|
+
else:
|
|
91
|
+
mode = display_mode
|
|
92
|
+
|
|
93
|
+
if isinstance(mode, int) and mode in (1, 2, 3):
|
|
94
|
+
return mode
|
|
95
|
+
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def device_icr_dss_config(camera_data: Mapping[str, Any]) -> dict[str, Any]:
|
|
100
|
+
"""Decode and return the device_ICR_DSS configuration."""
|
|
101
|
+
|
|
102
|
+
optionals = optionals_mapping(camera_data)
|
|
103
|
+
icr = decode_json(optionals.get("device_ICR_DSS"))
|
|
104
|
+
|
|
105
|
+
return dict(icr) if isinstance(icr, Mapping) else {}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def day_night_mode_value(camera_data: Mapping[str, Any]) -> int:
|
|
109
|
+
"""Return current day/night mode (0=auto,1=day,2=night)."""
|
|
110
|
+
|
|
111
|
+
config = device_icr_dss_config(camera_data)
|
|
112
|
+
mode = config.get("mode")
|
|
113
|
+
if isinstance(mode, int) and mode in (0, 1, 2):
|
|
114
|
+
return mode
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def day_night_sensitivity_value(camera_data: Mapping[str, Any]) -> int:
|
|
119
|
+
"""Return current day/night sensitivity value (1..3)."""
|
|
120
|
+
|
|
121
|
+
config = device_icr_dss_config(camera_data)
|
|
122
|
+
sensitivity = config.get("sensitivity")
|
|
123
|
+
if isinstance(sensitivity, int) and sensitivity in (1, 2, 3):
|
|
124
|
+
return sensitivity
|
|
125
|
+
return 2
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def resolve_channel(camera_data: Mapping[str, Any]) -> int:
|
|
129
|
+
"""Return the channel number to use for devconfig operations."""
|
|
130
|
+
|
|
131
|
+
candidate = camera_data.get("channelNo") or camera_data.get("channel_no")
|
|
132
|
+
if isinstance(candidate, int):
|
|
133
|
+
return candidate
|
|
134
|
+
if isinstance(candidate, str) and candidate.isdigit():
|
|
135
|
+
return int(candidate)
|
|
136
|
+
return 1
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def night_vision_config(camera_data: Mapping[str, Any]) -> dict[str, Any]:
|
|
140
|
+
"""Return decoded NightVision_Model configuration mapping."""
|
|
141
|
+
|
|
142
|
+
optionals = optionals_mapping(camera_data)
|
|
143
|
+
config: Any = optionals.get("NightVision_Model")
|
|
144
|
+
if config is None:
|
|
145
|
+
config = camera_data.get("NightVision_Model")
|
|
146
|
+
|
|
147
|
+
config = decode_json(config)
|
|
148
|
+
|
|
149
|
+
return dict(config) if isinstance(config, Mapping) else {}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def night_vision_mode_value(camera_data: Mapping[str, Any]) -> int:
|
|
153
|
+
"""Return current night vision mode (0=BW,1=colour,2=smart,5=super)."""
|
|
154
|
+
|
|
155
|
+
config = night_vision_config(camera_data)
|
|
156
|
+
mode = coerce_int(config.get("graphicType"))
|
|
157
|
+
if mode is None:
|
|
158
|
+
return 0
|
|
159
|
+
return mode if mode in (0, 1, 2, 5) else 0
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def night_vision_luminance_value(camera_data: Mapping[str, Any]) -> int:
|
|
163
|
+
"""Return the configured night vision luminance (default 40)."""
|
|
164
|
+
|
|
165
|
+
config = night_vision_config(camera_data)
|
|
166
|
+
value = coerce_int(config.get("luminance"))
|
|
167
|
+
if value is None:
|
|
168
|
+
value = 40
|
|
169
|
+
return max(0, value)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def night_vision_duration_value(camera_data: Mapping[str, Any]) -> int:
|
|
173
|
+
"""Return the configured smart night vision duration (default 60)."""
|
|
174
|
+
|
|
175
|
+
config = night_vision_config(camera_data)
|
|
176
|
+
value = coerce_int(config.get("duration"))
|
|
177
|
+
return value if value is not None else 60
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def night_vision_payload(
|
|
181
|
+
camera_data: Mapping[str, Any],
|
|
182
|
+
*,
|
|
183
|
+
mode: int | None = None,
|
|
184
|
+
luminance: int | None = None,
|
|
185
|
+
duration: int | None = None,
|
|
186
|
+
) -> dict[str, Any]:
|
|
187
|
+
"""Return a sanitized NightVision_Model payload for updates."""
|
|
188
|
+
|
|
189
|
+
config = dict(night_vision_config(camera_data))
|
|
190
|
+
|
|
191
|
+
resolved_mode = (
|
|
192
|
+
int(mode)
|
|
193
|
+
if mode is not None
|
|
194
|
+
else int(config.get("graphicType") or night_vision_mode_value(camera_data))
|
|
195
|
+
)
|
|
196
|
+
config["graphicType"] = resolved_mode
|
|
197
|
+
|
|
198
|
+
if luminance is None:
|
|
199
|
+
luminance_value = night_vision_luminance_value(camera_data)
|
|
200
|
+
else:
|
|
201
|
+
coerced_luminance = coerce_int(luminance)
|
|
202
|
+
luminance_value = (
|
|
203
|
+
coerced_luminance
|
|
204
|
+
if coerced_luminance is not None
|
|
205
|
+
else night_vision_luminance_value(camera_data)
|
|
206
|
+
)
|
|
207
|
+
if resolved_mode == 1:
|
|
208
|
+
config["luminance"] = 0 if luminance_value <= 0 else max(20, luminance_value)
|
|
209
|
+
elif resolved_mode == 2:
|
|
210
|
+
config["luminance"] = max(
|
|
211
|
+
20,
|
|
212
|
+
luminance_value if luminance_value > 0 else 40,
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
config["luminance"] = max(0, luminance_value)
|
|
216
|
+
|
|
217
|
+
if duration is None:
|
|
218
|
+
duration_value = night_vision_duration_value(camera_data)
|
|
219
|
+
else:
|
|
220
|
+
coerced_duration = coerce_int(duration)
|
|
221
|
+
duration_value = (
|
|
222
|
+
coerced_duration
|
|
223
|
+
if coerced_duration is not None
|
|
224
|
+
else night_vision_duration_value(camera_data)
|
|
225
|
+
)
|
|
226
|
+
if resolved_mode == 2:
|
|
227
|
+
config["duration"] = max(15, min(120, duration_value))
|
|
228
|
+
else:
|
|
229
|
+
config.pop("duration", None)
|
|
230
|
+
|
|
231
|
+
return config
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def has_osd_overlay(camera_data: Mapping[str, Any]) -> bool:
|
|
235
|
+
"""Return True when the camera has an active OSD label."""
|
|
236
|
+
|
|
237
|
+
optionals = optionals_mapping(camera_data)
|
|
238
|
+
osd_entries = optionals.get("OSD")
|
|
239
|
+
|
|
240
|
+
if isinstance(osd_entries, Mapping):
|
|
241
|
+
entries: list[Mapping[str, Any]] = [osd_entries]
|
|
242
|
+
elif isinstance(osd_entries, list):
|
|
243
|
+
entries = [entry for entry in osd_entries if isinstance(entry, Mapping)]
|
|
244
|
+
else:
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
for entry in entries:
|
|
248
|
+
name = entry.get("name")
|
|
249
|
+
if isinstance(name, str) and name.strip():
|
|
250
|
+
return True
|
|
251
|
+
return False
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
pyezvizapi/__init__.py,sha256=
|
|
1
|
+
pyezvizapi/__init__.py,sha256=mp3XbdXy00TyittfvvGrQ2oAaDeoebLE6_QU0Sbuf8o,2668
|
|
2
2
|
pyezvizapi/__main__.py,sha256=9uttTuOfO22tzyomJIV8ebFJ-G-YUNDYOadZ_0AgdNA,20925
|
|
3
|
-
pyezvizapi/api_endpoints.py,sha256=
|
|
3
|
+
pyezvizapi/api_endpoints.py,sha256=2M5Vs4YB1VWZGcowT-4Fj2hhRNjFh976LT3jtRrqvrc,5754
|
|
4
4
|
pyezvizapi/camera.py,sha256=Pl5oIEdrFcv1Hz5sQI1IyyJIDCMjOjQdtExgKzmLoK8,22102
|
|
5
5
|
pyezvizapi/cas.py,sha256=3zHe-_a0KchCmGeAj1of-pV6oMPRUmSCIiDqBFsTK8A,6025
|
|
6
|
-
pyezvizapi/client.py,sha256=
|
|
6
|
+
pyezvizapi/client.py,sha256=rQ95oGgx16M69BWA8WTmmpyX9d4kHdxt6biVMrjDmOQ,140990
|
|
7
7
|
pyezvizapi/constants.py,sha256=5AxJYfof6NvebBcFvPkoKI6xinpkwmCnaauUvhvBMDY,12810
|
|
8
8
|
pyezvizapi/exceptions.py,sha256=8rmxEUQdrziqMe-M1SeeRd0HtP2IDQ2xpJVj7wvOQyo,976
|
|
9
|
+
pyezvizapi/feature.py,sha256=Sc-L-2qqz7GXmaWf0_j6QF84KRSw_emy1cWKhcs5HzE,7654
|
|
9
10
|
pyezvizapi/light_bulb.py,sha256=9wgycG3dTvBbrsxQjQnXal-GA8VXPsIN1m-CTtRh8i0,7797
|
|
10
11
|
pyezvizapi/models.py,sha256=NQzwTP0yEe2IWU-Vc6nAn87xulpTuo0MX2Rcf0WxifA,4176
|
|
11
12
|
pyezvizapi/mqtt.py,sha256=aOL-gexZgYvCCaNQ03M4vZan91d5p2Fl_qsFykn9NW4,22365
|
|
12
13
|
pyezvizapi/test_cam_rtsp.py,sha256=O9NHh-vcNFfnzNw8jbuhM9a_5TWfNZIMXaJP7Lmkaj4,5162
|
|
13
14
|
pyezvizapi/test_mqtt.py,sha256=Orn-fwZPJIE4G5KROMX0MRAkLwU6nLb9LUtXyb2ZCQs,4147
|
|
14
15
|
pyezvizapi/utils.py,sha256=G8gGjG0ecdN05Y0vxOHvcQMtQXgVB7nHzyvCzz66kLk,12148
|
|
15
|
-
pyezvizapi-1.0.3.
|
|
16
|
-
pyezvizapi-1.0.3.
|
|
17
|
-
pyezvizapi-1.0.3.
|
|
18
|
-
pyezvizapi-1.0.3.
|
|
19
|
-
pyezvizapi-1.0.3.
|
|
20
|
-
pyezvizapi-1.0.3.
|
|
21
|
-
pyezvizapi-1.0.3.
|
|
16
|
+
pyezvizapi-1.0.3.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
17
|
+
pyezvizapi-1.0.3.2.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
18
|
+
pyezvizapi-1.0.3.2.dist-info/METADATA,sha256=MYfDFjbk5sSplo9fHGmS-aAzlWsVSNx1A3JQFwAIJEI,695
|
|
19
|
+
pyezvizapi-1.0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
+
pyezvizapi-1.0.3.2.dist-info/entry_points.txt,sha256=_BSJ3eNb2H_AZkRdsv1s4mojqWn3N7m503ujvg1SudA,56
|
|
21
|
+
pyezvizapi-1.0.3.2.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
|
|
22
|
+
pyezvizapi-1.0.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|