pyimouapi 1.3.2__tar.gz → 1.3.3__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.
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/PKG-INFO +4 -3
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/README.md +3 -2
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi/__init__.py +1 -1
- pyimouapi-1.3.3/pyimouapi/collection_point.py +89 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi/const.py +42 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi/device.py +48 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi/ha_device.py +161 -24
- pyimouapi-1.3.3/pyimouapi/siren.py +15 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi.egg-info/PKG-INFO +4 -3
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi.egg-info/SOURCES.txt +6 -1
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyproject.toml +1 -1
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/setup.py +1 -1
- pyimouapi-1.3.3/tests/test_collection_point.py +205 -0
- pyimouapi-1.3.3/tests/test_siren.py +272 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/tests/test_update_from_detail.py +3 -5
- pyimouapi-1.3.3/tests/test_write_optimistic_state.py +60 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/LICENSE +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi/exceptions.py +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi/openapi.py +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi/sensor.py +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi.egg-info/dependency_links.txt +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi.egg-info/requires.txt +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/pyimouapi.egg-info/top_level.txt +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/setup.cfg +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/tests/test_channel_id_and_api_guards.py +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/tests/test_collect_property_entities.py +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/tests/test_device_summaries.py +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/tests/test_event_identifier_map.py +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/tests/test_lookup_property.py +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/tests/test_message_callback.py +0 -0
- {pyimouapi-1.3.2 → pyimouapi-1.3.3}/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.
|
|
3
|
+
Version: 1.3.3
|
|
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.
|
|
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.
|
|
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`.
|
|
@@ -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,10 @@ 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_COLLECTION_POINT = "collection_point"
|
|
117
|
+
PARAM_COLLECTION_POINT_PROMPT = "select_collection_point"
|
|
118
|
+
PARAM_SIREN_START = "siren_start"
|
|
119
|
+
PARAM_SIREN_STOP = "siren_stop"
|
|
112
120
|
PARAM_PTZ = "ptz"
|
|
113
121
|
PARAM_TEMPERATURE_CURRENT = "temperature_current"
|
|
114
122
|
PARAM_HUMIDITY_CURRENT = "humidity_current"
|
|
@@ -128,6 +136,18 @@ PARAM_ABILITY_REFS = "abilityRefs"
|
|
|
128
136
|
PARAM_REF_TYPE = "ref_type"
|
|
129
137
|
PARAM_EXPRESSION = "expression"
|
|
130
138
|
PARAM_OUTPUT_DATA = "outputData"
|
|
139
|
+
PARAM_TURN_REF = "turn_ref"
|
|
140
|
+
PARAM_TURN_INPUT_REF = "turn_input_ref"
|
|
141
|
+
PARAM_INPUT_REF = "input_ref"
|
|
142
|
+
PARAM_COLLECTION_NAME = "name"
|
|
143
|
+
|
|
144
|
+
IOT_GET_COLLECTION_REF = "21500"
|
|
145
|
+
IOT_TURN_COLLECTION_REF = "22000"
|
|
146
|
+
IOT_TURN_COLLECTION_NAME_REF = "22001"
|
|
147
|
+
IOT_COLLECTION_NAME_REF = "21551"
|
|
148
|
+
IOT_SIREN_START_REF = "25500"
|
|
149
|
+
IOT_SIREN_START_INPUT_REF = "25501"
|
|
150
|
+
IOT_SIREN_STOP_REF = "22200"
|
|
131
151
|
PARAM_VALUE_TYPE = "value_type"
|
|
132
152
|
PARAM_ACCESS_TYPE = "accessType"
|
|
133
153
|
PARAM_ABILITY = "ability"
|
|
@@ -294,6 +314,8 @@ SWITCH_TYPE_REF = {
|
|
|
294
314
|
# Required capacity for various button types
|
|
295
315
|
BUTTON_TYPE_ABILITY = {
|
|
296
316
|
"restart_device": ["Reboot"],
|
|
317
|
+
"siren_start": ["Siren"],
|
|
318
|
+
"siren_stop": ["Siren"],
|
|
297
319
|
"ptz_up": ["PT", "PTZ"],
|
|
298
320
|
"ptz_down": ["PT", "PTZ"],
|
|
299
321
|
"ptz_left": ["PT", "PTZ"],
|
|
@@ -331,6 +353,15 @@ BUTTON_TYPE_REF = {
|
|
|
331
353
|
],
|
|
332
354
|
},
|
|
333
355
|
],
|
|
356
|
+
"siren_start": [
|
|
357
|
+
{
|
|
358
|
+
"ref": IOT_SIREN_START_REF,
|
|
359
|
+
"input_ref": IOT_SIREN_START_INPUT_REF,
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
"siren_stop": [
|
|
363
|
+
{"ref": IOT_SIREN_STOP_REF},
|
|
364
|
+
],
|
|
334
365
|
"ptz_up": [
|
|
335
366
|
{"ref": "22100"},
|
|
336
367
|
{"ref": "88700"},
|
|
@@ -371,6 +402,7 @@ BUTTON_TYPE_REF = {
|
|
|
371
402
|
# Required capacity for various select types
|
|
372
403
|
SELECT_TYPE_ABILITY = {
|
|
373
404
|
"night_vision_mode": ["NVM"],
|
|
405
|
+
"collection_point": ["CollectionPoint"],
|
|
374
406
|
}
|
|
375
407
|
SELECT_TYPE_REF = {
|
|
376
408
|
"night_vision_mode": [
|
|
@@ -404,6 +436,16 @@ SELECT_TYPE_REF = {
|
|
|
404
436
|
"value_type": "int",
|
|
405
437
|
}
|
|
406
438
|
],
|
|
439
|
+
"collection_point": [
|
|
440
|
+
{
|
|
441
|
+
"ref": IOT_GET_COLLECTION_REF,
|
|
442
|
+
"turn_ref": IOT_TURN_COLLECTION_REF,
|
|
443
|
+
"turn_input_ref": IOT_TURN_COLLECTION_NAME_REF,
|
|
444
|
+
"ref_type": "services",
|
|
445
|
+
"default": PARAM_COLLECTION_POINT_PROMPT,
|
|
446
|
+
"options": [PARAM_COLLECTION_POINT_PROMPT],
|
|
447
|
+
}
|
|
448
|
+
],
|
|
407
449
|
}
|
|
408
450
|
# Required capacity for various sensor types
|
|
409
451
|
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:
|
|
@@ -6,6 +6,11 @@ from typing import Any
|
|
|
6
6
|
import aiohttp
|
|
7
7
|
from simpleeval import SimpleEval
|
|
8
8
|
|
|
9
|
+
from .collection_point import (
|
|
10
|
+
build_collection_point_options,
|
|
11
|
+
parse_iot_collection_names,
|
|
12
|
+
parse_paas_collection_names,
|
|
13
|
+
)
|
|
9
14
|
from .const import (
|
|
10
15
|
BINARY_SENSOR_TYPE_ABILITY,
|
|
11
16
|
BINARY_SENSOR_TYPE_REF,
|
|
@@ -21,6 +26,8 @@ from .const import (
|
|
|
21
26
|
PARAM_BATTERY,
|
|
22
27
|
PARAM_CHANNEL_ID,
|
|
23
28
|
PARAM_CHANNELS,
|
|
29
|
+
PARAM_COLLECTION_POINT,
|
|
30
|
+
PARAM_COLLECTION_POINT_PROMPT,
|
|
24
31
|
PARAM_CONTENT,
|
|
25
32
|
PARAM_CURRENT_OPTION,
|
|
26
33
|
PARAM_DEFAULT,
|
|
@@ -32,6 +39,7 @@ from .const import (
|
|
|
32
39
|
PARAM_HD,
|
|
33
40
|
PARAM_HLS,
|
|
34
41
|
PARAM_HUMIDITY_CURRENT,
|
|
42
|
+
PARAM_INPUT_REF,
|
|
35
43
|
PARAM_LITELEC,
|
|
36
44
|
PARAM_MODE,
|
|
37
45
|
PARAM_MODES,
|
|
@@ -47,6 +55,8 @@ from .const import (
|
|
|
47
55
|
PARAM_REF_TYPE,
|
|
48
56
|
PARAM_RESTART_DEVICE,
|
|
49
57
|
PARAM_SERVICES,
|
|
58
|
+
PARAM_SIREN_START,
|
|
59
|
+
PARAM_SIREN_STOP,
|
|
50
60
|
PARAM_STATE,
|
|
51
61
|
PARAM_STATUS,
|
|
52
62
|
PARAM_STORAGE_USED,
|
|
@@ -54,6 +64,8 @@ from .const import (
|
|
|
54
64
|
PARAM_STREAMS,
|
|
55
65
|
PARAM_TEMPERATURE_CURRENT,
|
|
56
66
|
PARAM_TOTAL_BYTES,
|
|
67
|
+
PARAM_TURN_INPUT_REF,
|
|
68
|
+
PARAM_TURN_REF,
|
|
57
69
|
PARAM_URL,
|
|
58
70
|
PARAM_USED_BYTES,
|
|
59
71
|
PARAM_VALUE_TYPE,
|
|
@@ -68,6 +80,7 @@ from .const import (
|
|
|
68
80
|
from .device import ImouDevice, ImouDeviceManager
|
|
69
81
|
from .exceptions import RequestFailedException
|
|
70
82
|
from .sensor import apply_sensor_state
|
|
83
|
+
from .siren import build_siren_start_iot_content
|
|
71
84
|
|
|
72
85
|
_LOGGER: logging.Logger = logging.getLogger(__package__)
|
|
73
86
|
|
|
@@ -155,6 +168,7 @@ class ImouHaDevice:
|
|
|
155
168
|
self._texts = {}
|
|
156
169
|
self._channel_id = None
|
|
157
170
|
self._channel_name = None
|
|
171
|
+
self._is_ipc = False
|
|
158
172
|
self._product_id = None
|
|
159
173
|
self._parent_product_id = None
|
|
160
174
|
self._parent_device_id = None
|
|
@@ -171,6 +185,10 @@ class ImouHaDevice:
|
|
|
171
185
|
def channel_name(self):
|
|
172
186
|
return self._channel_name
|
|
173
187
|
|
|
188
|
+
@property
|
|
189
|
+
def is_ipc(self) -> bool:
|
|
190
|
+
return self._is_ipc
|
|
191
|
+
|
|
174
192
|
@property
|
|
175
193
|
def manufacturer(self):
|
|
176
194
|
return self._manufacturer
|
|
@@ -245,6 +263,9 @@ class ImouHaDevice:
|
|
|
245
263
|
def set_channel_name(self, channel_name):
|
|
246
264
|
self._channel_name = channel_name
|
|
247
265
|
|
|
266
|
+
def set_is_ipc(self, is_ipc: bool) -> None:
|
|
267
|
+
self._is_ipc = is_ipc
|
|
268
|
+
|
|
248
269
|
|
|
249
270
|
class ImouHaDeviceManager:
|
|
250
271
|
def __init__(self, device_manager: ImouDeviceManager):
|
|
@@ -454,13 +475,79 @@ class ImouHaDeviceManager:
|
|
|
454
475
|
async def _async_update_device_select_status(self, device: ImouHaDevice):
|
|
455
476
|
"""UPDATE SELECT STATUS"""
|
|
456
477
|
for select_type, value in device.selects.items():
|
|
457
|
-
if
|
|
478
|
+
if select_type == PARAM_COLLECTION_POINT:
|
|
479
|
+
await self._async_update_device_collection_points(device)
|
|
480
|
+
elif PARAM_REF in value:
|
|
458
481
|
continue
|
|
459
482
|
else:
|
|
460
483
|
await self._async_update_device_select_status_by_type(
|
|
461
484
|
device, select_type
|
|
462
485
|
)
|
|
463
486
|
|
|
487
|
+
async def _async_update_device_collection_points(self, device: ImouHaDevice):
|
|
488
|
+
"""Refresh collection point preset names."""
|
|
489
|
+
if device.channel_id is None:
|
|
490
|
+
return
|
|
491
|
+
try:
|
|
492
|
+
names: list[str]
|
|
493
|
+
select_state = device.selects[PARAM_COLLECTION_POINT]
|
|
494
|
+
if select_state.get(PARAM_TURN_REF):
|
|
495
|
+
output = await self._get_state_from_properties_or_services(
|
|
496
|
+
device,
|
|
497
|
+
self._resolve_device_id(device),
|
|
498
|
+
select_state,
|
|
499
|
+
kind="select",
|
|
500
|
+
key=PARAM_COLLECTION_POINT,
|
|
501
|
+
)
|
|
502
|
+
names = parse_iot_collection_names(output) if output is not None else []
|
|
503
|
+
else:
|
|
504
|
+
data = await self.delegate.async_get_device_collection(
|
|
505
|
+
device.device_id, str(device.channel_id)
|
|
506
|
+
)
|
|
507
|
+
names = parse_paas_collection_names(data)
|
|
508
|
+
except Exception as err:
|
|
509
|
+
_LOGGER.warning(
|
|
510
|
+
"Failed to fetch collection points for %s: %s",
|
|
511
|
+
device.device_name,
|
|
512
|
+
err,
|
|
513
|
+
)
|
|
514
|
+
return
|
|
515
|
+
|
|
516
|
+
device.selects[PARAM_COLLECTION_POINT][PARAM_OPTIONS] = (
|
|
517
|
+
build_collection_point_options(names)
|
|
518
|
+
)
|
|
519
|
+
device.selects[PARAM_COLLECTION_POINT][PARAM_CURRENT_OPTION] = (
|
|
520
|
+
PARAM_COLLECTION_POINT_PROMPT
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
async def _async_select_collection_point_option(
|
|
524
|
+
self, device: ImouHaDevice, option: str
|
|
525
|
+
) -> None:
|
|
526
|
+
if option == PARAM_COLLECTION_POINT_PROMPT:
|
|
527
|
+
return
|
|
528
|
+
if device.channel_id is None:
|
|
529
|
+
raise RequestFailedException("collection_point requires channel")
|
|
530
|
+
|
|
531
|
+
select_state = device.selects[PARAM_COLLECTION_POINT]
|
|
532
|
+
turn_ref = select_state.get(PARAM_TURN_REF)
|
|
533
|
+
if turn_ref:
|
|
534
|
+
if device.product_id is None:
|
|
535
|
+
raise RequestFailedException("collection_point iot requires product_id")
|
|
536
|
+
input_ref = select_state.get(PARAM_TURN_INPUT_REF)
|
|
537
|
+
if not input_ref:
|
|
538
|
+
raise RequestFailedException("collection_point missing turn input ref")
|
|
539
|
+
await self.delegate.async_iot_device_control(
|
|
540
|
+
self._resolve_device_id(device),
|
|
541
|
+
device.product_id,
|
|
542
|
+
turn_ref,
|
|
543
|
+
{input_ref: option},
|
|
544
|
+
)
|
|
545
|
+
else:
|
|
546
|
+
await self.delegate.async_turn_device_collection(
|
|
547
|
+
device.device_id, str(device.channel_id), option
|
|
548
|
+
)
|
|
549
|
+
select_state[PARAM_CURRENT_OPTION] = PARAM_COLLECTION_POINT_PROMPT
|
|
550
|
+
|
|
464
551
|
async def _async_update_device_sensor_status(self, device: ImouHaDevice):
|
|
465
552
|
"""UPDATE SENSOR STATUS"""
|
|
466
553
|
for sensor_type, value in device.sensors.items():
|
|
@@ -651,6 +738,7 @@ class ImouHaDeviceManager:
|
|
|
651
738
|
imou_ha_device.set_parent_product_id(device.parent_product_id)
|
|
652
739
|
if device.parent_device_id is not None:
|
|
653
740
|
imou_ha_device.set_parent_device_id(device.parent_device_id)
|
|
741
|
+
imou_ha_device.set_is_ipc(device.is_ipc)
|
|
654
742
|
return imou_ha_device
|
|
655
743
|
|
|
656
744
|
async def async_press_button(
|
|
@@ -665,10 +753,37 @@ class ImouHaDeviceManager:
|
|
|
665
753
|
BUTTON_TYPE_PARAM_VALUE[button_type],
|
|
666
754
|
duration,
|
|
667
755
|
)
|
|
756
|
+
elif button_type in (PARAM_SIREN_START, PARAM_SIREN_STOP):
|
|
757
|
+
await self._async_press_siren_button(device, button_type)
|
|
668
758
|
elif device.buttons[button_type].get(PARAM_REF):
|
|
669
759
|
ref_id = device.buttons[button_type].get(PARAM_REF)
|
|
670
760
|
await self._async_press_button_by_ref(device, ref_id)
|
|
671
761
|
|
|
762
|
+
async def _async_press_siren_button(
|
|
763
|
+
self, device: ImouHaDevice, button_type: str
|
|
764
|
+
) -> None:
|
|
765
|
+
button_state = device.buttons[button_type]
|
|
766
|
+
ref = button_state.get(PARAM_REF)
|
|
767
|
+
if ref:
|
|
768
|
+
content: dict[str, str] = {}
|
|
769
|
+
if button_type == PARAM_SIREN_START:
|
|
770
|
+
input_ref = button_state.get(PARAM_INPUT_REF)
|
|
771
|
+
if not input_ref:
|
|
772
|
+
raise RequestFailedException("siren_start missing input ref")
|
|
773
|
+
content = build_siren_start_iot_content(input_ref)
|
|
774
|
+
await self._async_press_button_by_ref(device, ref, content)
|
|
775
|
+
return
|
|
776
|
+
if device.is_ipc:
|
|
777
|
+
channels = None
|
|
778
|
+
else:
|
|
779
|
+
if device.channel_id is None:
|
|
780
|
+
raise RequestFailedException(f"{button_type} requires channel")
|
|
781
|
+
channels = [int(device.channel_id)]
|
|
782
|
+
if button_type == PARAM_SIREN_START:
|
|
783
|
+
await self.delegate.async_siren_start(device.device_id, channels)
|
|
784
|
+
elif button_type == PARAM_SIREN_STOP:
|
|
785
|
+
await self.delegate.async_siren_stop(device.device_id, channels)
|
|
786
|
+
|
|
672
787
|
async def async_set_text_value(
|
|
673
788
|
self, device: ImouHaDevice, text_type: str, text_value: str
|
|
674
789
|
):
|
|
@@ -736,6 +851,7 @@ class ImouHaDeviceManager:
|
|
|
736
851
|
# Request all failed, consider this operation a failure
|
|
737
852
|
if all(isinstance(result_item, Exception) for result_item in result):
|
|
738
853
|
raise result[0]
|
|
854
|
+
device.switches[switch_type][PARAM_STATE] = enable
|
|
739
855
|
|
|
740
856
|
async def async_select_option(
|
|
741
857
|
self,
|
|
@@ -743,17 +859,27 @@ class ImouHaDeviceManager:
|
|
|
743
859
|
select_type: str,
|
|
744
860
|
option: str,
|
|
745
861
|
):
|
|
862
|
+
if select_type == PARAM_COLLECTION_POINT:
|
|
863
|
+
await self._async_select_collection_point_option(device, option)
|
|
864
|
+
return
|
|
746
865
|
if device.selects[select_type].get(PARAM_REF):
|
|
747
866
|
ref_id = device.selects[select_type].get(PARAM_REF)
|
|
748
867
|
value_type = device.selects[select_type].get(PARAM_VALUE_TYPE)
|
|
868
|
+
write_option = option
|
|
749
869
|
# 兼容下音量15400值为-1的情况
|
|
750
870
|
if ref_id == "15400" and option == "99":
|
|
751
|
-
|
|
752
|
-
await self._async_select_option_by_ref(
|
|
871
|
+
write_option = "-1"
|
|
872
|
+
await self._async_select_option_by_ref(
|
|
873
|
+
device, write_option, ref_id, value_type
|
|
874
|
+
)
|
|
875
|
+
device.selects[select_type][PARAM_CURRENT_OPTION] = option
|
|
753
876
|
elif select_type == PARAM_NIGHT_VISION_MODE:
|
|
754
877
|
await self.delegate.async_set_device_night_vision_mode(
|
|
755
878
|
device.device_id, device.channel_id, option
|
|
756
879
|
)
|
|
880
|
+
device.selects[PARAM_NIGHT_VISION_MODE][PARAM_CURRENT_OPTION] = (
|
|
881
|
+
option.lower()
|
|
882
|
+
)
|
|
757
883
|
|
|
758
884
|
async def _async_get_device_switch_status_by_ability(
|
|
759
885
|
self, device: ImouHaDevice, ability_type: str
|
|
@@ -886,10 +1012,16 @@ class ImouHaDeviceManager:
|
|
|
886
1012
|
select_type,
|
|
887
1013
|
imou_ha_device.selects,
|
|
888
1014
|
):
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1015
|
+
if select_type == PARAM_COLLECTION_POINT:
|
|
1016
|
+
imou_ha_device.selects[select_type] = {
|
|
1017
|
+
PARAM_CURRENT_OPTION: PARAM_COLLECTION_POINT_PROMPT,
|
|
1018
|
+
PARAM_OPTIONS: [PARAM_COLLECTION_POINT_PROMPT],
|
|
1019
|
+
}
|
|
1020
|
+
else:
|
|
1021
|
+
imou_ha_device.selects[select_type] = {
|
|
1022
|
+
PARAM_CURRENT_OPTION: "",
|
|
1023
|
+
PARAM_OPTIONS: [],
|
|
1024
|
+
}
|
|
893
1025
|
|
|
894
1026
|
@staticmethod
|
|
895
1027
|
def configure_button_by_ability(
|
|
@@ -1083,9 +1215,10 @@ class ImouHaDeviceManager:
|
|
|
1083
1215
|
imou_ha_device.product_id,
|
|
1084
1216
|
ref.get(PARAM_EXCEPTS, []),
|
|
1085
1217
|
):
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1218
|
+
button_entry = {PARAM_REF: ref[PARAM_REF]}
|
|
1219
|
+
if ref.get(PARAM_INPUT_REF):
|
|
1220
|
+
button_entry[PARAM_INPUT_REF] = ref[PARAM_INPUT_REF]
|
|
1221
|
+
imou_ha_device.buttons[button_type] = button_entry
|
|
1089
1222
|
break
|
|
1090
1223
|
|
|
1091
1224
|
@staticmethod
|
|
@@ -1108,12 +1241,21 @@ class ImouHaDeviceManager:
|
|
|
1108
1241
|
imou_ha_device.product_id,
|
|
1109
1242
|
ref.get(PARAM_EXCEPTS, []),
|
|
1110
1243
|
):
|
|
1111
|
-
|
|
1244
|
+
select_entry = {
|
|
1112
1245
|
PARAM_REF: ref[PARAM_REF],
|
|
1113
1246
|
PARAM_OPTIONS: ref[PARAM_OPTIONS],
|
|
1114
1247
|
PARAM_CURRENT_OPTION: ref[PARAM_DEFAULT],
|
|
1115
1248
|
PARAM_VALUE_TYPE: ref.get(PARAM_VALUE_TYPE, "str"),
|
|
1116
1249
|
}
|
|
1250
|
+
if ref.get(PARAM_TURN_REF):
|
|
1251
|
+
select_entry[PARAM_TURN_REF] = ref[PARAM_TURN_REF]
|
|
1252
|
+
select_entry[PARAM_TURN_INPUT_REF] = ref.get(
|
|
1253
|
+
PARAM_TURN_INPUT_REF
|
|
1254
|
+
)
|
|
1255
|
+
select_entry[PARAM_REF_TYPE] = ref.get(
|
|
1256
|
+
PARAM_REF_TYPE, PARAM_PROPERTIES
|
|
1257
|
+
)
|
|
1258
|
+
imou_ha_device.selects[select_type] = select_entry
|
|
1117
1259
|
break
|
|
1118
1260
|
|
|
1119
1261
|
@staticmethod
|
|
@@ -1285,18 +1427,14 @@ class ImouHaDeviceManager:
|
|
|
1285
1427
|
state = data
|
|
1286
1428
|
return state
|
|
1287
1429
|
|
|
1288
|
-
async def _async_press_button_by_ref(
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
+ device.parent_device_id
|
|
1295
|
-
+ "_"
|
|
1296
|
-
+ device.parent_product_id
|
|
1297
|
-
)
|
|
1430
|
+
async def _async_press_button_by_ref(
|
|
1431
|
+
self, device: ImouHaDevice, ref: str, content: dict | None = None
|
|
1432
|
+
):
|
|
1433
|
+
if content is None:
|
|
1434
|
+
content = {}
|
|
1435
|
+
device_id = self._resolve_device_id(device)
|
|
1298
1436
|
await self.delegate.async_iot_device_control(
|
|
1299
|
-
device_id, device.product_id, ref,
|
|
1437
|
+
device_id, device.product_id, ref, content
|
|
1300
1438
|
)
|
|
1301
1439
|
|
|
1302
1440
|
async def _async_select_option_by_ref(
|
|
@@ -1345,8 +1483,7 @@ class ImouHaDeviceManager:
|
|
|
1345
1483
|
await self.delegate.async_set_iot_device_properties(
|
|
1346
1484
|
device.device_id, None, device.product_id, {ref: 1 if enable else 0}
|
|
1347
1485
|
)
|
|
1348
|
-
|
|
1349
|
-
await self._async_update_device_switch_status_by_ref(device, switch_type, ref)
|
|
1486
|
+
device.switches[switch_type][PARAM_STATE] = enable
|
|
1350
1487
|
|
|
1351
1488
|
@staticmethod
|
|
1352
1489
|
def configure_binary_sensor_by_ability(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Siren button helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def client_local_time_iso() -> str:
|
|
9
|
+
"""Return client local time for IoT SirenStart (yyyyMMdd'T'HHmmss)."""
|
|
10
|
+
return datetime.now().astimezone().strftime("%Y%m%dT%H%M%S")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_siren_start_iot_content(input_ref: str) -> dict[str, str]:
|
|
14
|
+
"""Build iotDeviceControl content for SirenStart."""
|
|
15
|
+
return {input_ref: client_local_time_iso()}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyimouapi
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.3
|
|
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.
|
|
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`.
|