pyezvizapi 1.0.3.3__tar.gz → 1.0.3.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 (28) hide show
  1. {pyezvizapi-1.0.3.3/pyezvizapi.egg-info → pyezvizapi-1.0.3.5}/PKG-INFO +1 -1
  2. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/__init__.py +18 -0
  3. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/feature.py +219 -1
  4. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5/pyezvizapi.egg-info}/PKG-INFO +1 -1
  5. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/setup.py +1 -1
  6. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/LICENSE +0 -0
  7. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/LICENSE.md +0 -0
  8. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/MANIFEST.in +0 -0
  9. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/README.md +0 -0
  10. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/__main__.py +0 -0
  11. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/api_endpoints.py +0 -0
  12. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/camera.py +0 -0
  13. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/cas.py +0 -0
  14. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/client.py +0 -0
  15. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/constants.py +0 -0
  16. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/exceptions.py +0 -0
  17. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/light_bulb.py +0 -0
  18. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/models.py +0 -0
  19. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/mqtt.py +0 -0
  20. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/test_cam_rtsp.py +0 -0
  21. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/test_mqtt.py +0 -0
  22. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi/utils.py +0 -0
  23. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi.egg-info/SOURCES.txt +0 -0
  24. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi.egg-info/dependency_links.txt +0 -0
  25. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi.egg-info/entry_points.txt +0 -0
  26. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi.egg-info/requires.txt +0 -0
  27. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/pyezvizapi.egg-info/top_level.txt +0 -0
  28. {pyezvizapi-1.0.3.3 → pyezvizapi-1.0.3.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyezvizapi
3
- Version: 1.0.3.3
3
+ Version: 1.0.3.5
4
4
  Summary: Pilot your Ezviz cameras
5
5
  Home-page: https://github.com/RenierM26/pyEzvizApi/
6
6
  Author: Renier Moorcroft
@@ -39,7 +39,11 @@ from .feature import (
39
39
  day_night_sensitivity_value,
40
40
  device_icr_dss_config,
41
41
  display_mode_value,
42
+ get_algorithm_value,
43
+ has_algorithm_subtype,
42
44
  has_osd_overlay,
45
+ iter_algorithm_entries,
46
+ iter_channel_algorithm_entries,
43
47
  lens_defog_config,
44
48
  lens_defog_value,
45
49
  night_vision_config,
@@ -47,8 +51,13 @@ from .feature import (
47
51
  night_vision_luminance_value,
48
52
  night_vision_mode_value,
49
53
  night_vision_payload,
54
+ normalize_port_security,
50
55
  optionals_mapping,
56
+ port_security_config,
57
+ port_security_has_port,
58
+ port_security_port_enabled,
51
59
  resolve_channel,
60
+ support_ext_value,
52
61
  )
53
62
  from .light_bulb import EzvizLightBulb
54
63
  from .models import EzvizDeviceRecord, build_device_records_map
@@ -91,7 +100,11 @@ __all__ = [
91
100
  "day_night_sensitivity_value",
92
101
  "device_icr_dss_config",
93
102
  "display_mode_value",
103
+ "get_algorithm_value",
104
+ "has_algorithm_subtype",
94
105
  "has_osd_overlay",
106
+ "iter_algorithm_entries",
107
+ "iter_channel_algorithm_entries",
95
108
  "lens_defog_config",
96
109
  "lens_defog_value",
97
110
  "night_vision_config",
@@ -99,6 +112,11 @@ __all__ = [
99
112
  "night_vision_luminance_value",
100
113
  "night_vision_mode_value",
101
114
  "night_vision_payload",
115
+ "normalize_port_security",
102
116
  "optionals_mapping",
117
+ "port_security_config",
118
+ "port_security_has_port",
119
+ "port_security_port_enabled",
103
120
  "resolve_channel",
121
+ "support_ext_value",
104
122
  ]
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Mapping, MutableMapping
5
+ from collections.abc import Iterable, Iterator, Mapping, MutableMapping
6
6
  from typing import Any, cast
7
7
 
8
8
  from .utils import coerce_int, decode_json
@@ -78,6 +78,197 @@ def optionals_mapping(camera_data: Mapping[str, Any]) -> dict[str, Any]:
78
78
  return dict(optionals) if isinstance(optionals, Mapping) else {}
79
79
 
80
80
 
81
+ def optionals_dict(camera_data: Mapping[str, Any]) -> dict[str, Any]:
82
+ """Return convenience wrapper for optionals mapping."""
83
+
84
+ return optionals_mapping(camera_data)
85
+
86
+
87
+ def iter_algorithm_entries(camera_data: Mapping[str, Any]) -> Iterator[dict[str, Any]]:
88
+ """Yield entries from the AlgorithmInfo optionals list."""
89
+
90
+ entries = optionals_dict(camera_data).get("AlgorithmInfo")
91
+ if not isinstance(entries, Iterable):
92
+ return
93
+ for entry in entries:
94
+ if isinstance(entry, Mapping):
95
+ yield dict(entry)
96
+
97
+
98
+ def iter_channel_algorithm_entries(
99
+ camera_data: Mapping[str, Any], channel: int
100
+ ) -> Iterator[dict[str, Any]]:
101
+ """Yield AlgorithmInfo entries filtered by channel."""
102
+
103
+ for entry in iter_algorithm_entries(camera_data):
104
+ entry_channel = coerce_int(entry.get("channel")) or 1
105
+ if entry_channel == channel:
106
+ yield entry
107
+
108
+
109
+ def get_algorithm_value(
110
+ camera_data: Mapping[str, Any], subtype: str, channel: int
111
+ ) -> int | None:
112
+ """Return AlgorithmInfo value for provided subtype/channel."""
113
+
114
+ for entry in iter_channel_algorithm_entries(camera_data, channel):
115
+ if entry.get("SubType") != subtype:
116
+ continue
117
+ return coerce_int(entry.get("Value"))
118
+ return None
119
+
120
+
121
+ def has_algorithm_subtype(
122
+ camera_data: Mapping[str, Any], subtype: str, channel: int = 1
123
+ ) -> bool:
124
+ """Return True when AlgorithmInfo contains subtype for channel."""
125
+
126
+ return get_algorithm_value(camera_data, subtype, channel) is not None
127
+
128
+
129
+ def support_ext_value(camera_data: Mapping[str, Any], ext_key: str) -> str | None:
130
+ """Fetch a supportExt entry as a string when present."""
131
+
132
+ raw = camera_data.get("supportExt")
133
+ if not isinstance(raw, Mapping):
134
+ device_infos = camera_data.get("deviceInfos")
135
+ if isinstance(device_infos, Mapping):
136
+ raw = device_infos.get("supportExt")
137
+
138
+ if not isinstance(raw, Mapping):
139
+ return None
140
+
141
+ value = raw.get(ext_key)
142
+ return str(value) if value is not None else None
143
+
144
+
145
+ def _normalize_port_list(value: Any) -> list[dict[str, Any]] | None:
146
+ """Decode a list of port-security entries."""
147
+
148
+ value = decode_json(value)
149
+ if not isinstance(value, Iterable):
150
+ return None
151
+
152
+ normalized: list[dict[str, Any]] = []
153
+ for entry in value:
154
+ entry = decode_json(entry)
155
+ if not isinstance(entry, Mapping):
156
+ return None
157
+ port = coerce_int(entry.get("portNo"))
158
+ if port is None:
159
+ continue
160
+ normalized.append({"portNo": port, "enabled": bool(entry.get("enabled"))})
161
+
162
+ return normalized
163
+
164
+
165
+ def normalize_port_security(payload: Any) -> dict[str, Any]:
166
+ """Normalize IoT port-security payloads."""
167
+
168
+ seen: set[int] = set()
169
+
170
+ def _walk(obj: Any, hint: bool | None = None) -> dict[str, Any] | None:
171
+ obj = decode_json(obj)
172
+ if obj is None:
173
+ return None
174
+
175
+ if isinstance(obj, Mapping):
176
+ obj_id = id(obj)
177
+ if obj_id in seen:
178
+ return None
179
+ seen.add(obj_id)
180
+
181
+ enabled_local = obj.get("enabled")
182
+ if isinstance(enabled_local, bool):
183
+ hint = enabled_local
184
+
185
+ ports = _normalize_port_list(obj.get("portSecurityList"))
186
+ if ports is not None:
187
+ return {
188
+ "portSecurityList": ports,
189
+ "enabled": bool(enabled_local)
190
+ if isinstance(enabled_local, bool)
191
+ else bool(hint)
192
+ if isinstance(hint, bool)
193
+ else True,
194
+ }
195
+
196
+ for key in (
197
+ "PortSecurity",
198
+ "value",
199
+ "data",
200
+ "NetworkSecurityProtection",
201
+ ):
202
+ if key in obj:
203
+ candidate = _walk(obj[key], hint)
204
+ if candidate:
205
+ if "enabled" not in candidate and isinstance(hint, bool):
206
+ candidate["enabled"] = hint
207
+ return candidate
208
+
209
+ for value in obj.values():
210
+ candidate = _walk(value, hint)
211
+ if candidate:
212
+ if "enabled" not in candidate and isinstance(hint, bool):
213
+ candidate["enabled"] = hint
214
+ return candidate
215
+
216
+ elif isinstance(obj, Iterable):
217
+ for item in obj:
218
+ candidate = _walk(item, hint)
219
+ if candidate:
220
+ return candidate
221
+
222
+ return None
223
+
224
+ normalized = _walk(payload)
225
+ if isinstance(normalized, dict):
226
+ normalized.setdefault("enabled", True)
227
+ return normalized
228
+ return {}
229
+
230
+
231
+ def port_security_config(camera_data: Mapping[str, Any]) -> dict[str, Any]:
232
+ """Return the normalized port-security mapping for a camera payload."""
233
+
234
+ direct = camera_data.get("NetworkSecurityProtection")
235
+ normalized = normalize_port_security(direct)
236
+ if normalized:
237
+ return normalized
238
+
239
+ feature = camera_data.get("FEATURE_INFO")
240
+ if isinstance(feature, Mapping):
241
+ normalized = normalize_port_security(feature)
242
+ if normalized:
243
+ return normalized
244
+
245
+ return {}
246
+
247
+
248
+ def port_security_has_port(camera_data: Mapping[str, Any], port: int) -> bool:
249
+ """Return True if the normalized config contains the port."""
250
+
251
+ ports = port_security_config(camera_data).get("portSecurityList")
252
+ if not isinstance(ports, Iterable):
253
+ return False
254
+ return any(
255
+ isinstance(entry, Mapping) and coerce_int(entry.get("portNo")) == port
256
+ for entry in ports
257
+ )
258
+
259
+
260
+ def port_security_port_enabled(camera_data: Mapping[str, Any], port: int) -> bool:
261
+ """Return True if the specific port is enabled."""
262
+
263
+ ports = port_security_config(camera_data).get("portSecurityList")
264
+ if not isinstance(ports, Iterable):
265
+ return False
266
+ for entry in ports:
267
+ if isinstance(entry, Mapping) and coerce_int(entry.get("portNo")) == port:
268
+ return bool(entry.get("enabled"))
269
+ return False
270
+
271
+
81
272
  def display_mode_value(camera_data: Mapping[str, Any]) -> int:
82
273
  """Return display mode value (1..3) from camera data."""
83
274
 
@@ -96,6 +287,33 @@ def display_mode_value(camera_data: Mapping[str, Any]) -> int:
96
287
  return 1
97
288
 
98
289
 
290
+ def blc_current_value(camera_data: Mapping[str, Any]) -> int:
291
+ """Return BLC position (0..5) from camera data. 0 = Off."""
292
+ optionals = optionals_mapping(camera_data)
293
+ inverse_mode = optionals.get("inverse_mode")
294
+ inverse_mode = decode_json(inverse_mode)
295
+
296
+ # Expected: {"mode": int, "enable": 0|1, "position": 0..5}
297
+ if isinstance(inverse_mode, Mapping):
298
+ enable = inverse_mode.get("enable", 0)
299
+ position = inverse_mode.get("position", 0)
300
+ if (
301
+ isinstance(enable, int)
302
+ and enable == 1
303
+ and isinstance(position, int)
304
+ and position in (1, 2, 3, 4, 5)
305
+ ):
306
+ return position
307
+ return 0
308
+
309
+ # Fallbacks if backend ever returns a bare int (position) instead of the object
310
+ if isinstance(inverse_mode, int) and inverse_mode in (0, 1, 2, 3, 4, 5):
311
+ return inverse_mode
312
+
313
+ # Default to Off
314
+ return 0
315
+
316
+
99
317
  def device_icr_dss_config(camera_data: Mapping[str, Any]) -> dict[str, Any]:
100
318
  """Decode and return the device_ICR_DSS configuration."""
101
319
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyezvizapi
3
- Version: 1.0.3.3
3
+ Version: 1.0.3.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.3.3",
8
+ version="1.0.3.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