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/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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyezvizapi
3
- Version: 1.0.3.0
3
+ Version: 1.0.3.2
4
4
  Summary: Pilot your Ezviz cameras
5
5
  Home-page: https://github.com/RenierM26/pyEzvizApi/
6
6
  Author: Renier Moorcroft
@@ -1,21 +1,22 @@
1
- pyezvizapi/__init__.py,sha256=IDnIN_nfIISVwuy0cVBh4wspgAav6MuOJCQGajjyU3g,1881
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=yC-pSKJ6-JLeoSlF73EOR-i3JNxnf-UR41-Bwd8bkm4,2884
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=Kc2r8KyXaOZII8GXnfy6Ae0FrV5IhAFv8eccry-SxVk,78800
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.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
- pyezvizapi-1.0.3.0.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
- pyezvizapi-1.0.3.0.dist-info/METADATA,sha256=rrvvly5CkWYrHn5ms2rZcsaa2K9z4QtYA1ECqUItEGg,695
18
- pyezvizapi-1.0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- pyezvizapi-1.0.3.0.dist-info/entry_points.txt,sha256=_BSJ3eNb2H_AZkRdsv1s4mojqWn3N7m503ujvg1SudA,56
20
- pyezvizapi-1.0.3.0.dist-info/top_level.txt,sha256=gMZTelIi8z7pXyTCQLLaIkxVRrDQ_lS2NEv0WgfHrHs,11
21
- pyezvizapi-1.0.3.0.dist-info/RECORD,,
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,,