pyimouapi 1.3.2__tar.gz → 1.3.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.
Files changed (33) hide show
  1. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/PKG-INFO +4 -3
  2. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/README.md +3 -2
  3. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi/__init__.py +1 -1
  4. pyimouapi-1.3.4/pyimouapi/collection_point.py +89 -0
  5. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi/const.py +63 -9
  6. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi/device.py +48 -0
  7. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi/ha_device.py +173 -68
  8. pyimouapi-1.3.4/pyimouapi/select_option.py +123 -0
  9. pyimouapi-1.3.4/pyimouapi/siren.py +15 -0
  10. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi.egg-info/PKG-INFO +4 -3
  11. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi.egg-info/SOURCES.txt +8 -1
  12. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyproject.toml +1 -1
  13. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/setup.py +1 -1
  14. pyimouapi-1.3.4/tests/test_collection_point.py +205 -0
  15. pyimouapi-1.3.4/tests/test_select_option.py +111 -0
  16. pyimouapi-1.3.4/tests/test_siren.py +272 -0
  17. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/tests/test_update_from_detail.py +9 -7
  18. pyimouapi-1.3.4/tests/test_write_optimistic_state.py +127 -0
  19. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/LICENSE +0 -0
  20. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi/exceptions.py +0 -0
  21. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi/openapi.py +0 -0
  22. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi/sensor.py +0 -0
  23. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi.egg-info/dependency_links.txt +0 -0
  24. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi.egg-info/requires.txt +0 -0
  25. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/pyimouapi.egg-info/top_level.txt +0 -0
  26. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/setup.cfg +0 -0
  27. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/tests/test_channel_id_and_api_guards.py +0 -0
  28. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/tests/test_collect_property_entities.py +0 -0
  29. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/tests/test_device_summaries.py +0 -0
  30. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/tests/test_event_identifier_map.py +0 -0
  31. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/tests/test_lookup_property.py +0 -0
  32. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/tests/test_message_callback.py +0 -0
  33. {pyimouapi-1.3.2 → pyimouapi-1.3.4}/tests/test_sensor_normalize.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyimouapi
3
- Version: 1.3.2
3
+ Version: 1.3.4
4
4
  Summary: A package for imou open api
5
5
  Home-page: https://github.com/Imou-OpenPlatform/Py-Imou-Open-Api
6
6
  Author: Imou-OpenPlatform
@@ -30,8 +30,9 @@ Async Python client for the **Imou Open Platform** cloud APIs. Built on **aiohtt
30
30
  | Module | Role |
31
31
  |--------|------|
32
32
  | `pyimouapi.openapi` | `ImouOpenApiClient` — auth, signing, token lifecycle, HTTP calls |
33
- | `pyimouapi.device` | `ImouDeviceManager`, `ImouDevice`, `ImouChannel` — listing, PTZ, alarms, storage, and related endpoints |
34
- | `pyimouapi.ha_device` | `ImouHaDeviceManager`, `ImouHaDevice`, … — aggregated “device model” helpers for automation stacks |
33
+ | `pyimouapi.device` | `ImouDeviceManager`, `ImouDevice`, `ImouChannel` — listing, PTZ, alarms, storage, collection points (`getCollection` / `turnCollection`), and related endpoints |
34
+ | `pyimouapi.collection_point` | Parsing helpers for PaaS / IoT collection point (preset) name lists |
35
+ | `pyimouapi.ha_device` | `ImouHaDeviceManager`, `ImouHaDevice`, … — aggregated “device model” helpers for automation stacks (including `select.collection_point` when the device exposes `CollectionPoint` or IoT refs `21500`/`22000`) |
35
36
  | `pyimouapi.exceptions` | `ImouException` and typed errors (connect, request, invalid credentials, …) |
36
37
 
37
38
  The top-level `pyimouapi` package re-exports common symbols. Import submodules directly when needed, for example `from pyimouapi.ha_device import ImouHaDeviceManager`.
@@ -11,8 +11,9 @@ Async Python client for the **Imou Open Platform** cloud APIs. Built on **aiohtt
11
11
  | Module | Role |
12
12
  |--------|------|
13
13
  | `pyimouapi.openapi` | `ImouOpenApiClient` — auth, signing, token lifecycle, HTTP calls |
14
- | `pyimouapi.device` | `ImouDeviceManager`, `ImouDevice`, `ImouChannel` — listing, PTZ, alarms, storage, and related endpoints |
15
- | `pyimouapi.ha_device` | `ImouHaDeviceManager`, `ImouHaDevice`, … — aggregated “device model” helpers for automation stacks |
14
+ | `pyimouapi.device` | `ImouDeviceManager`, `ImouDevice`, `ImouChannel` — listing, PTZ, alarms, storage, collection points (`getCollection` / `turnCollection`), and related endpoints |
15
+ | `pyimouapi.collection_point` | Parsing helpers for PaaS / IoT collection point (preset) name lists |
16
+ | `pyimouapi.ha_device` | `ImouHaDeviceManager`, `ImouHaDevice`, … — aggregated “device model” helpers for automation stacks (including `select.collection_point` when the device exposes `CollectionPoint` or IoT refs `21500`/`22000`) |
16
17
  | `pyimouapi.exceptions` | `ImouException` and typed errors (connect, request, invalid credentials, …) |
17
18
 
18
19
  The top-level `pyimouapi` package re-exports common symbols. Import submodules directly when needed, for example `from pyimouapi.ha_device import ImouHaDeviceManager`.
@@ -1,4 +1,4 @@
1
- __version__ = "1.3.2"
1
+ __version__ = "1.3.4"
2
2
 
3
3
  from .device import ImouChannel, ImouDevice, ImouDeviceManager
4
4
  from .exceptions import (
@@ -0,0 +1,89 @@
1
+ """Collection point (PTZ preset) parsing helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from .const import IOT_COLLECTION_NAME_REF, PARAM_COLLECTION_POINT_PROMPT
8
+
9
+
10
+ def parse_paas_collection_names(data: dict[str, Any]) -> list[str]:
11
+ """Extract preset names from getCollection PaaS response data."""
12
+ names: list[str] = []
13
+ for item in data.get("collections") or []:
14
+ if not isinstance(item, dict):
15
+ continue
16
+ name = item.get("name")
17
+ if name and str(name) != PARAM_COLLECTION_POINT_PROMPT:
18
+ names.append(str(name))
19
+ return unique_preserve_order(names)
20
+
21
+
22
+ def _name_from_collection_item(item: dict[str, Any]) -> str | None:
23
+ for key in ("Name", "name", IOT_COLLECTION_NAME_REF):
24
+ value = item.get(key)
25
+ if value:
26
+ return str(value)
27
+ return None
28
+
29
+
30
+ def parse_iot_collection_names(output_data: Any) -> list[str]:
31
+ """Extract preset names from GetCollection IoT outputData."""
32
+ names: list[str] = []
33
+ if output_data is None:
34
+ return names
35
+
36
+ if isinstance(output_data, dict):
37
+ for value in output_data.values():
38
+ if isinstance(value, list):
39
+ names.extend(_names_from_collection_items(value))
40
+ elif isinstance(value, str) and value != PARAM_COLLECTION_POINT_PROMPT:
41
+ names.append(value)
42
+ return unique_preserve_order(names)
43
+
44
+ if isinstance(output_data, list):
45
+ if output_data and all(isinstance(item, dict) for item in output_data):
46
+ if any(
47
+ item.get("identifier") == "collection"
48
+ or IOT_COLLECTION_NAME_REF in item
49
+ or "Name" in item
50
+ for item in output_data
51
+ ):
52
+ names.extend(_names_from_collection_items(output_data))
53
+ else:
54
+ for item in output_data:
55
+ if not isinstance(item, dict):
56
+ continue
57
+ nested = item.get("value")
58
+ if isinstance(nested, list):
59
+ names.extend(_names_from_collection_items(nested))
60
+ return unique_preserve_order(names)
61
+
62
+ return names
63
+
64
+
65
+ def _names_from_collection_items(items: list[Any]) -> list[str]:
66
+ names: list[str] = []
67
+ for item in items:
68
+ if not isinstance(item, dict):
69
+ continue
70
+ name = _name_from_collection_item(item)
71
+ if name and name != PARAM_COLLECTION_POINT_PROMPT:
72
+ names.append(name)
73
+ return names
74
+
75
+
76
+ def unique_preserve_order(names: list[str]) -> list[str]:
77
+ seen: set[str] = set()
78
+ ordered: list[str] = []
79
+ for name in names:
80
+ if name in seen:
81
+ continue
82
+ seen.add(name)
83
+ ordered.append(name)
84
+ return ordered
85
+
86
+
87
+ def build_collection_point_options(names: list[str]) -> list[str]:
88
+ """Build select options with placeholder first, preserving API order."""
89
+ return [PARAM_COLLECTION_POINT_PROMPT, *unique_preserve_order(names)]
@@ -23,6 +23,10 @@ API_ENDPOINT_GET_PRODUCT_MODEL = "/openapi/getProductModel"
23
23
  API_ENDPOINT_GET_IOT_DEVICE_DETAIL_INFO = "/openapi/getIotDeviceDetailInfo"
24
24
  API_ENDPOINT_WAKE_UP_DEVICE = "/openapi/wakeUpDevice"
25
25
  API_ENDPOINT_SET_MESSAGE_CALLBACK = "/openapi/setMessageCallback"
26
+ API_ENDPOINT_GET_COLLECTION = "/openapi/getCollection"
27
+ API_ENDPOINT_TURN_COLLECTION = "/openapi/turnCollection"
28
+ API_ENDPOINT_SIREN_START = "/openapi/sirenStart"
29
+ API_ENDPOINT_SIREN_STOP = "/openapi/sirenStop"
26
30
 
27
31
  # error_codes
28
32
  ERROR_CODE_SUCCESS = "0"
@@ -109,6 +113,11 @@ PARAM_MOTION_DETECT = "motion_detect"
109
113
  PARAM_STORAGE_USED = "storage_used"
110
114
  PARAM_RESTART_DEVICE = "restart_device"
111
115
  PARAM_NIGHT_VISION_MODE = "night_vision_mode"
116
+ PARAM_DEVICE_VOLUME = "device_volume"
117
+ PARAM_COLLECTION_POINT = "collection_point"
118
+ PARAM_COLLECTION_POINT_PROMPT = "select_collection_point"
119
+ PARAM_SIREN_START = "siren_start"
120
+ PARAM_SIREN_STOP = "siren_stop"
112
121
  PARAM_PTZ = "ptz"
113
122
  PARAM_TEMPERATURE_CURRENT = "temperature_current"
114
123
  PARAM_HUMIDITY_CURRENT = "humidity_current"
@@ -128,6 +137,18 @@ PARAM_ABILITY_REFS = "abilityRefs"
128
137
  PARAM_REF_TYPE = "ref_type"
129
138
  PARAM_EXPRESSION = "expression"
130
139
  PARAM_OUTPUT_DATA = "outputData"
140
+ PARAM_TURN_REF = "turn_ref"
141
+ PARAM_TURN_INPUT_REF = "turn_input_ref"
142
+ PARAM_INPUT_REF = "input_ref"
143
+ PARAM_COLLECTION_NAME = "name"
144
+
145
+ IOT_GET_COLLECTION_REF = "21500"
146
+ IOT_TURN_COLLECTION_REF = "22000"
147
+ IOT_TURN_COLLECTION_NAME_REF = "22001"
148
+ IOT_COLLECTION_NAME_REF = "21551"
149
+ IOT_SIREN_START_REF = "25500"
150
+ IOT_SIREN_START_INPUT_REF = "25501"
151
+ IOT_SIREN_STOP_REF = "22200"
131
152
  PARAM_VALUE_TYPE = "value_type"
132
153
  PARAM_ACCESS_TYPE = "accessType"
133
154
  PARAM_ABILITY = "ability"
@@ -294,6 +315,8 @@ SWITCH_TYPE_REF = {
294
315
  # Required capacity for various button types
295
316
  BUTTON_TYPE_ABILITY = {
296
317
  "restart_device": ["Reboot"],
318
+ "siren_start": ["Siren"],
319
+ "siren_stop": ["Siren"],
297
320
  "ptz_up": ["PT", "PTZ"],
298
321
  "ptz_down": ["PT", "PTZ"],
299
322
  "ptz_left": ["PT", "PTZ"],
@@ -331,6 +354,15 @@ BUTTON_TYPE_REF = {
331
354
  ],
332
355
  },
333
356
  ],
357
+ "siren_start": [
358
+ {
359
+ "ref": IOT_SIREN_START_REF,
360
+ "input_ref": IOT_SIREN_START_INPUT_REF,
361
+ },
362
+ ],
363
+ "siren_stop": [
364
+ {"ref": IOT_SIREN_STOP_REF},
365
+ ],
334
366
  "ptz_up": [
335
367
  {"ref": "22100"},
336
368
  {"ref": "88700"},
@@ -371,39 +403,61 @@ BUTTON_TYPE_REF = {
371
403
  # Required capacity for various select types
372
404
  SELECT_TYPE_ABILITY = {
373
405
  "night_vision_mode": ["NVM"],
406
+ "collection_point": ["CollectionPoint"],
374
407
  }
375
408
  SELECT_TYPE_REF = {
376
409
  "night_vision_mode": [
377
410
  {
378
411
  "ref": "17400",
379
- "default": "0",
380
- "options": ["0", "1", "2", "3"],
412
+ "default": "intelligent",
413
+ "options": ["intelligent", "fullcolor", "infrared", "off"],
381
414
  "value_type": "int",
382
415
  },
383
416
  {
384
417
  "ref": "139700",
385
- "default": "0",
386
- "options": ["0", "1", "2", "3", "4"],
418
+ "default": "intelligent",
419
+ "options": [
420
+ "intelligent",
421
+ "fullcolor",
422
+ "infrared",
423
+ "off",
424
+ "custom",
425
+ ],
426
+ "value_type": "int",
427
+ },
428
+ {
429
+ "ref": "112400",
430
+ "default": "infrared",
431
+ "options": ["infrared", "off"],
387
432
  "value_type": "int",
388
433
  },
389
- {"ref": "112400", "default": "2", "options": ["2", "3"], "value_type": "int"},
390
434
  ],
391
435
  "mode": [
392
436
  {
393
437
  "ref": "15200",
394
- "default": "0",
395
- "options": ["0", "1", "2"],
438
+ "default": "home",
439
+ "options": ["home", "away", "disarm"],
396
440
  "value_type": "int",
397
441
  }
398
442
  ],
399
443
  "device_volume": [
400
444
  {
401
445
  "ref": "15400",
402
- "default": "0",
403
- "options": ["99", "0", "1", "2"],
446
+ "default": "low",
447
+ "options": ["mute", "low", "medium", "high"],
404
448
  "value_type": "int",
405
449
  }
406
450
  ],
451
+ "collection_point": [
452
+ {
453
+ "ref": IOT_GET_COLLECTION_REF,
454
+ "turn_ref": IOT_TURN_COLLECTION_REF,
455
+ "turn_input_ref": IOT_TURN_COLLECTION_NAME_REF,
456
+ "ref_type": "services",
457
+ "default": PARAM_COLLECTION_POINT_PROMPT,
458
+ "options": [PARAM_COLLECTION_POINT_PROMPT],
459
+ }
460
+ ],
407
461
  }
408
462
  # Required capacity for various sensor types
409
463
  SENSOR_TYPE_ABILITY = {
@@ -10,6 +10,7 @@ from .const import (
10
10
  API_ENDPOINT_CONTROL_DEVICE_PTZ,
11
11
  API_ENDPOINT_DEVICE_SD_CARD_STATUS,
12
12
  API_ENDPOINT_DEVICE_STORAGE,
13
+ API_ENDPOINT_GET_COLLECTION,
13
14
  API_ENDPOINT_GET_DEVICE_ALARM_PARAM,
14
15
  API_ENDPOINT_GET_DEVICE_LIVE_INFO,
15
16
  API_ENDPOINT_GET_DEVICE_NIGHT_VISION_MODE,
@@ -27,6 +28,9 @@ from .const import (
27
28
  API_ENDPOINT_SET_DEVICE_SNAP,
28
29
  API_ENDPOINT_SET_DEVICE_STATUS,
29
30
  API_ENDPOINT_SET_IOT_DEVICE_PROPERTIES,
31
+ API_ENDPOINT_SIREN_START,
32
+ API_ENDPOINT_SIREN_STOP,
33
+ API_ENDPOINT_TURN_COLLECTION,
30
34
  API_ENDPOINT_WAKE_UP_DEVICE,
31
35
  NIGHT_VISION_MODE_MAP,
32
36
  PARAM_ABILITY_REFS,
@@ -39,6 +43,7 @@ from .const import (
39
43
  PARAM_CHANNEL_NUM,
40
44
  PARAM_CHANNEL_STATUS,
41
45
  PARAM_CHANNELS,
46
+ PARAM_COLLECTION_NAME,
42
47
  PARAM_CONTENT,
43
48
  PARAM_COUNT,
44
49
  PARAM_DEVICE_ABILITY,
@@ -206,6 +211,10 @@ class ImouDevice:
206
211
  def parent_device_id(self) -> str | None:
207
212
  return self._parent_device_id
208
213
 
214
+ @property
215
+ def channel_number(self) -> int:
216
+ return self._channel_number
217
+
209
218
  @property
210
219
  def is_ipc(self) -> bool:
211
220
  return (
@@ -641,6 +650,45 @@ class ImouDeviceManager:
641
650
  API_ENDPOINT_GET_IOT_DEVICE_DETAIL_INFO, params
642
651
  )
643
652
 
653
+ async def async_get_device_collection(
654
+ self, device_id: str, channel_id: str
655
+ ) -> dict[str, Any]:
656
+ params = {
657
+ PARAM_DEVICE_ID: device_id,
658
+ PARAM_CHANNEL_ID: channel_id,
659
+ }
660
+ return await self._imou_api_client.async_request_api(
661
+ API_ENDPOINT_GET_COLLECTION, params
662
+ )
663
+
664
+ async def async_turn_device_collection(
665
+ self, device_id: str, channel_id: str, name: str
666
+ ) -> None:
667
+ params = {
668
+ PARAM_DEVICE_ID: device_id,
669
+ PARAM_CHANNEL_ID: channel_id,
670
+ PARAM_COLLECTION_NAME: name,
671
+ }
672
+ await self._imou_api_client.async_request_api(
673
+ API_ENDPOINT_TURN_COLLECTION, params
674
+ )
675
+
676
+ async def async_siren_start(
677
+ self, device_id: str, channels: list[int] | None = None
678
+ ) -> None:
679
+ params: dict[str, Any] = {PARAM_DEVICE_ID: device_id}
680
+ if channels is not None:
681
+ params[PARAM_CHANNELS] = channels
682
+ await self._imou_api_client.async_request_api(API_ENDPOINT_SIREN_START, params)
683
+
684
+ async def async_siren_stop(
685
+ self, device_id: str, channels: list[int] | None = None
686
+ ) -> None:
687
+ params: dict[str, Any] = {PARAM_DEVICE_ID: device_id}
688
+ if channels is not None:
689
+ params[PARAM_CHANNELS] = channels
690
+ await self._imou_api_client.async_request_api(API_ENDPOINT_SIREN_STOP, params)
691
+
644
692
  async def _async_update_device_ability_refs(self, imou_device: ImouDevice) -> None:
645
693
  device_id = imou_device.device_id
646
694
  if imou_device.parent_product_id is not None: