axis 71__tar.gz → 72__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.
- {axis-71 → axis-72}/PKG-INFO +39 -4
- {axis-71 → axis-72}/README.md +35 -0
- {axis-71 → axis-72}/axis/__main__.py +3 -3
- {axis-71 → axis-72}/axis/interfaces/aiohttp_digest.py +0 -2
- {axis-71 → axis-72}/axis/interfaces/api_discovery.py +3 -6
- {axis-71 → axis-72}/axis/interfaces/api_handler.py +8 -6
- {axis-71 → axis-72}/axis/interfaces/applications/application_handler.py +2 -2
- {axis-71 → axis-72}/axis/interfaces/applications/applications.py +1 -3
- {axis-71 → axis-72}/axis/interfaces/applications/fence_guard.py +1 -3
- {axis-71 → axis-72}/axis/interfaces/applications/loitering_guard.py +1 -3
- {axis-71 → axis-72}/axis/interfaces/applications/motion_guard.py +1 -3
- {axis-71 → axis-72}/axis/interfaces/applications/object_analytics.py +1 -3
- {axis-71 → axis-72}/axis/interfaces/applications/vmd4.py +1 -3
- {axis-71 → axis-72}/axis/interfaces/basic_device_info.py +2 -6
- axis-72/axis/interfaces/event_instances.py +43 -0
- {axis-71 → axis-72}/axis/interfaces/event_manager.py +13 -4
- {axis-71 → axis-72}/axis/interfaces/light_control.py +28 -37
- {axis-71 → axis-72}/axis/interfaces/mqtt.py +2 -37
- {axis-71 → axis-72}/axis/interfaces/parameters/param_cgi.py +4 -15
- {axis-71 → axis-72}/axis/interfaces/parameters/param_handler.py +2 -2
- {axis-71 → axis-72}/axis/interfaces/pir_sensor_configuration.py +3 -11
- {axis-71 → axis-72}/axis/interfaces/port_management.py +4 -6
- {axis-71 → axis-72}/axis/interfaces/ptz.py +6 -3
- {axis-71 → axis-72}/axis/interfaces/pwdgrp_cgi.py +2 -3
- {axis-71 → axis-72}/axis/interfaces/stream_profiles.py +2 -6
- {axis-71 → axis-72}/axis/interfaces/user_groups.py +3 -3
- {axis-71 → axis-72}/axis/interfaces/vapix.py +21 -59
- {axis-71 → axis-72}/axis/interfaces/view_areas.py +5 -14
- {axis-71 → axis-72}/axis/models/api.py +20 -16
- {axis-71 → axis-72}/axis/models/api_discovery.py +5 -4
- {axis-71 → axis-72}/axis/models/applications/application.py +10 -9
- {axis-71 → axis-72}/axis/models/applications/fence_guard.py +22 -21
- {axis-71 → axis-72}/axis/models/applications/loitering_guard.py +22 -21
- {axis-71 → axis-72}/axis/models/applications/motion_guard.py +22 -21
- {axis-71 → axis-72}/axis/models/applications/object_analytics.py +22 -21
- {axis-71 → axis-72}/axis/models/applications/vmd4.py +22 -21
- {axis-71 → axis-72}/axis/models/basic_device_info.py +5 -4
- {axis-71 → axis-72}/axis/models/configuration.py +1 -3
- {axis-71 → axis-72}/axis/models/event.py +13 -43
- axis-72/axis/models/event_instance.py +420 -0
- {axis-71 → axis-72}/axis/models/light_control.py +175 -151
- {axis-71 → axis-72}/axis/models/mqtt.py +84 -56
- {axis-71 → axis-72}/axis/models/parameters/brand.py +1 -3
- {axis-71 → axis-72}/axis/models/parameters/param_cgi.py +28 -4
- {axis-71 → axis-72}/axis/models/parameters/properties.py +1 -3
- {axis-71 → axis-72}/axis/models/parameters/ptz.py +1 -3
- {axis-71 → axis-72}/axis/models/parameters/stream_profile.py +1 -3
- {axis-71 → axis-72}/axis/models/pir_sensor_configuration.py +31 -28
- {axis-71 → axis-72}/axis/models/port_cgi.py +3 -2
- {axis-71 → axis-72}/axis/models/port_management.py +48 -44
- {axis-71 → axis-72}/axis/models/ptz_cgi.py +9 -5
- {axis-71 → axis-72}/axis/models/pwdgrp_cgi.py +23 -21
- {axis-71 → axis-72}/axis/models/stream_profile.py +26 -25
- {axis-71 → axis-72}/axis/models/user_group.py +10 -9
- {axis-71 → axis-72}/axis/models/view_area.py +30 -27
- {axis-71 → axis-72}/axis/stream_transport.py +0 -2
- {axis-71 → axis-72}/axis/websocket.py +0 -2
- {axis-71 → axis-72}/axis.egg-info/PKG-INFO +39 -4
- {axis-71 → axis-72}/axis.egg-info/SOURCES.txt +2 -0
- {axis-71 → axis-72}/axis.egg-info/requires.txt +3 -3
- {axis-71 → axis-72}/pyproject.toml +6 -7
- {axis-71 → axis-72}/tests/test_api_discovery.py +11 -9
- {axis-71 → axis-72}/tests/test_api_handler.py +24 -0
- {axis-71 → axis-72}/tests/test_basic_device_info.py +60 -24
- axis-72/tests/test_conftest.py +448 -0
- {axis-71 → axis-72}/tests/test_event.py +67 -20
- axis-72/tests/test_event_instances.py +519 -0
- axis-72/tests/test_event_instances_protocol_parity.py +115 -0
- {axis-71 → axis-72}/tests/test_event_stream.py +56 -18
- {axis-71 → axis-72}/tests/test_http_client_compat.py +3 -7
- {axis-71 → axis-72}/tests/test_light_control.py +212 -72
- {axis-71 → axis-72}/tests/test_mqtt.py +80 -20
- {axis-71 → axis-72}/tests/test_pir_sensor_configuration.py +46 -39
- {axis-71 → axis-72}/tests/test_port_cgi.py +39 -5
- {axis-71 → axis-72}/tests/test_port_management.py +92 -87
- {axis-71 → axis-72}/tests/test_ptz.py +184 -84
- {axis-71 → axis-72}/tests/test_pwdgrp_cgi.py +41 -17
- axis-72/tests/test_request_response_contracts.py +241 -0
- {axis-71 → axis-72}/tests/test_stream_profiles.py +49 -34
- {axis-71 → axis-72}/tests/test_vapix.py +179 -37
- {axis-71 → axis-72}/tests/test_view_areas.py +62 -32
- axis-71/axis/interfaces/event_instances.py +0 -23
- axis-71/axis/models/event_instance.py +0 -174
- axis-71/tests/test_conftest.py +0 -164
- axis-71/tests/test_event_instances.py +0 -271
- {axis-71 → axis-72}/LICENSE +0 -0
- {axis-71 → axis-72}/axis/__init__.py +0 -0
- {axis-71 → axis-72}/axis/device.py +0 -0
- {axis-71 → axis-72}/axis/errors.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/__init__.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/applications/__init__.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/parameters/__init__.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/parameters/brand.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/parameters/image.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/parameters/io_port.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/parameters/properties.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/parameters/ptz.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/parameters/stream_profile.py +0 -0
- {axis-71 → axis-72}/axis/interfaces/port_cgi.py +0 -0
- {axis-71 → axis-72}/axis/models/__init__.py +0 -0
- {axis-71 → axis-72}/axis/models/applications/__init__.py +0 -0
- {axis-71 → axis-72}/axis/models/parameters/__init__.py +0 -0
- {axis-71 → axis-72}/axis/models/parameters/image.py +0 -0
- {axis-71 → axis-72}/axis/models/parameters/io_port.py +0 -0
- {axis-71 → axis-72}/axis/py.typed +0 -0
- {axis-71 → axis-72}/axis/rtsp.py +0 -0
- {axis-71 → axis-72}/axis/stream_manager.py +0 -0
- {axis-71 → axis-72}/axis.egg-info/dependency_links.txt +0 -0
- {axis-71 → axis-72}/axis.egg-info/entry_points.txt +0 -0
- {axis-71 → axis-72}/axis.egg-info/top_level.txt +0 -0
- {axis-71 → axis-72}/setup.cfg +0 -0
- {axis-71 → axis-72}/tests/test_auth_scheme.py +0 -0
- {axis-71 → axis-72}/tests/test_configuration.py +0 -0
- {axis-71 → axis-72}/tests/test_device.py +0 -0
- {axis-71 → axis-72}/tests/test_main_http_client.py +0 -0
- {axis-71 → axis-72}/tests/test_rtsp.py +0 -0
- {axis-71 → axis-72}/tests/test_stream_manager.py +0 -0
- {axis-71 → axis-72}/tests/test_user_groups.py +0 -0
- {axis-71 → axis-72}/tests/test_websocket.py +0 -0
{axis-71 → axis-72}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axis
|
|
3
|
-
Version:
|
|
3
|
+
Version: 72
|
|
4
4
|
Summary: A Python library for communicating with devices from Axis Communications
|
|
5
5
|
Author-email: Robert Svensson <Kane610@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
@@ -28,13 +28,13 @@ Requires-Dist: orjson==3.11.9; extra == "requirements"
|
|
|
28
28
|
Requires-Dist: packaging==26.2; extra == "requirements"
|
|
29
29
|
Requires-Dist: xmltodict==1.0.4; extra == "requirements"
|
|
30
30
|
Provides-Extra: requirements-test
|
|
31
|
-
Requires-Dist: mypy==2.
|
|
31
|
+
Requires-Dist: mypy==2.1.0; extra == "requirements-test"
|
|
32
32
|
Requires-Dist: pytest==9.0.3; extra == "requirements-test"
|
|
33
33
|
Requires-Dist: pytest-aiohttp==1.1.0; extra == "requirements-test"
|
|
34
34
|
Requires-Dist: pytest-asyncio==1.3.0; extra == "requirements-test"
|
|
35
35
|
Requires-Dist: pytest-cov==7.1.0; extra == "requirements-test"
|
|
36
|
-
Requires-Dist: ruff==0.15.
|
|
37
|
-
Requires-Dist: types-xmltodict==v1.0.1.
|
|
36
|
+
Requires-Dist: ruff==0.15.13; extra == "requirements-test"
|
|
37
|
+
Requires-Dist: types-xmltodict==v1.0.1.20260518; extra == "requirements-test"
|
|
38
38
|
Provides-Extra: requirements-dev
|
|
39
39
|
Requires-Dist: pre-commit==4.6.0; extra == "requirements-dev"
|
|
40
40
|
Dynamic: license-file
|
|
@@ -89,7 +89,42 @@ Vapix initialization is phase-based and driven by handler metadata:
|
|
|
89
89
|
|
|
90
90
|
Handlers declare phase membership through `handler_groups` and may customize phase eligibility through `should_initialize_in_group`.
|
|
91
91
|
|
|
92
|
+
## Request and response typing
|
|
93
|
+
|
|
94
|
+
`Vapix.api_request()` is the single typed request entrypoint.
|
|
95
|
+
|
|
96
|
+
- Request models declare their decode contract with `ApiRequest[ResponseT]`.
|
|
97
|
+
- Every `ApiRequest` subclass must set `response_type` explicitly.
|
|
98
|
+
- Decoded/read requests use their concrete response model as `response_type`.
|
|
99
|
+
- Write-style requests use `BytesResponse` as `response_type`.
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
@dataclass
|
|
105
|
+
class ListApisRequest(ApiRequest[GetAllApisResponse]):
|
|
106
|
+
response_type = GetAllApisResponse
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class SetPortsRequest(ApiRequest[ApiResponse[bytes]]):
|
|
111
|
+
response_type = BytesResponse
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Handler methods may unwrap `.data` when they intentionally preserve a bytes-returning boundary.
|
|
115
|
+
|
|
92
116
|
Example fallback policy:
|
|
93
117
|
|
|
94
118
|
- `LightHandler` participates in both `API_DISCOVERY` and `PARAM_CGI_FALLBACK`.
|
|
95
119
|
- In `PARAM_CGI_FALLBACK`, it initializes only when not listed in API discovery and listed in parameters.
|
|
120
|
+
|
|
121
|
+
## Event Instance Model Notes
|
|
122
|
+
|
|
123
|
+
`EventInstance` keeps `name` as the raw device-provided `NiceName` value.
|
|
124
|
+
|
|
125
|
+
`EventInstance.source` and `EventInstance.data` are typed containers (`EventInstanceSource` and `EventInstanceData`) built from `SimpleItemInstance` payloads.
|
|
126
|
+
|
|
127
|
+
For compatibility with integrations that still need the historical raw payload shape, `EventInstance` also exposes:
|
|
128
|
+
|
|
129
|
+
- `raw_source`: returns `{}` or a dict or a list of dicts.
|
|
130
|
+
- `raw_data`: returns `{}` or a dict or a list of dicts.
|
{axis-71 → axis-72}/README.md
RENAMED
|
@@ -48,7 +48,42 @@ Vapix initialization is phase-based and driven by handler metadata:
|
|
|
48
48
|
|
|
49
49
|
Handlers declare phase membership through `handler_groups` and may customize phase eligibility through `should_initialize_in_group`.
|
|
50
50
|
|
|
51
|
+
## Request and response typing
|
|
52
|
+
|
|
53
|
+
`Vapix.api_request()` is the single typed request entrypoint.
|
|
54
|
+
|
|
55
|
+
- Request models declare their decode contract with `ApiRequest[ResponseT]`.
|
|
56
|
+
- Every `ApiRequest` subclass must set `response_type` explicitly.
|
|
57
|
+
- Decoded/read requests use their concrete response model as `response_type`.
|
|
58
|
+
- Write-style requests use `BytesResponse` as `response_type`.
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
@dataclass
|
|
64
|
+
class ListApisRequest(ApiRequest[GetAllApisResponse]):
|
|
65
|
+
response_type = GetAllApisResponse
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class SetPortsRequest(ApiRequest[ApiResponse[bytes]]):
|
|
70
|
+
response_type = BytesResponse
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Handler methods may unwrap `.data` when they intentionally preserve a bytes-returning boundary.
|
|
74
|
+
|
|
51
75
|
Example fallback policy:
|
|
52
76
|
|
|
53
77
|
- `LightHandler` participates in both `API_DISCOVERY` and `PARAM_CGI_FALLBACK`.
|
|
54
78
|
- In `PARAM_CGI_FALLBACK`, it initializes only when not listed in API discovery and listed in parameters.
|
|
79
|
+
|
|
80
|
+
## Event Instance Model Notes
|
|
81
|
+
|
|
82
|
+
`EventInstance` keeps `name` as the raw device-provided `NiceName` value.
|
|
83
|
+
|
|
84
|
+
`EventInstance.source` and `EventInstance.data` are typed containers (`EventInstanceSource` and `EventInstanceData`) built from `SimpleItemInstance` payloads.
|
|
85
|
+
|
|
86
|
+
For compatibility with integrations that still need the historical raw payload shape, `EventInstance` also exposes:
|
|
87
|
+
|
|
88
|
+
- `raw_source`: returns `{}` or a dict or a list of dicts.
|
|
89
|
+
- `raw_data`: returns `{}` or a dict or a list of dicts.
|
|
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
|
|
17
17
|
LOGGER = logging.getLogger(__name__)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def event_handler(event:
|
|
20
|
+
def event_handler(event: Event) -> None:
|
|
21
21
|
"""Receive and print events from RTSP stream."""
|
|
22
22
|
LOGGER.info(event)
|
|
23
23
|
|
|
@@ -61,8 +61,8 @@ async def axis_device(
|
|
|
61
61
|
"Connected to device at %s but not registered or user not admin.", host
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
-
except (TimeoutError, axis.RequestError):
|
|
65
|
-
LOGGER.error("Error connecting to the Axis device at %s", host)
|
|
64
|
+
except (TimeoutError, axis.RequestError) as err:
|
|
65
|
+
LOGGER.error("Error connecting to the Axis device at %s: %s", host, err)
|
|
66
66
|
|
|
67
67
|
except axis.AxisException:
|
|
68
68
|
LOGGER.exception("Unknown Axis communication error occurred")
|
|
@@ -8,9 +8,7 @@ from ..models.api_discovery import (
|
|
|
8
8
|
API_VERSION,
|
|
9
9
|
Api,
|
|
10
10
|
ApiId,
|
|
11
|
-
GetAllApisResponse,
|
|
12
11
|
GetSupportedVersionsRequest,
|
|
13
|
-
GetSupportedVersionsResponse,
|
|
14
12
|
ListApisRequest,
|
|
15
13
|
)
|
|
16
14
|
from .api_handler import ApiHandler
|
|
@@ -33,11 +31,10 @@ class ApiDiscoveryHandler(ApiHandler[Api]):
|
|
|
33
31
|
|
|
34
32
|
async def get_api_list(self) -> dict[str, Api]:
|
|
35
33
|
"""List all APIs registered on API Discovery service."""
|
|
36
|
-
|
|
37
|
-
return
|
|
34
|
+
response = await self.vapix.api_request(ListApisRequest())
|
|
35
|
+
return response.data
|
|
38
36
|
|
|
39
37
|
async def get_supported_versions(self) -> list[str]:
|
|
40
38
|
"""List supported API versions."""
|
|
41
|
-
|
|
42
|
-
response = GetSupportedVersionsResponse.decode(bytes_data)
|
|
39
|
+
response = await self.vapix.api_request(GetSupportedVersionsRequest())
|
|
43
40
|
return response.data
|
|
@@ -9,7 +9,7 @@ from collections.abc import (
|
|
|
9
9
|
ValuesView,
|
|
10
10
|
)
|
|
11
11
|
import enum
|
|
12
|
-
from typing import TYPE_CHECKING,
|
|
12
|
+
from typing import TYPE_CHECKING, final
|
|
13
13
|
|
|
14
14
|
from ..errors import Forbidden, PathNotFound, Unauthorized
|
|
15
15
|
|
|
@@ -17,11 +17,11 @@ if TYPE_CHECKING:
|
|
|
17
17
|
from ..models.api_discovery import ApiId
|
|
18
18
|
from .vapix import Vapix
|
|
19
19
|
|
|
20
|
-
from ..models.api import
|
|
20
|
+
from ..models.api import ApiItem
|
|
21
21
|
|
|
22
|
-
CallbackType = Callable[[str], None]
|
|
23
|
-
SubscriptionType = CallbackType
|
|
24
|
-
UnsubscribeType = Callable[[], None]
|
|
22
|
+
type CallbackType = Callable[[str], None]
|
|
23
|
+
type SubscriptionType = CallbackType
|
|
24
|
+
type UnsubscribeType = Callable[[], None]
|
|
25
25
|
|
|
26
26
|
ID_FILTER_ALL = "*"
|
|
27
27
|
|
|
@@ -62,6 +62,8 @@ class SubscriptionHandler:
|
|
|
62
62
|
_id_filter = (ID_FILTER_ALL,)
|
|
63
63
|
elif isinstance(id_filter, str):
|
|
64
64
|
_id_filter = (id_filter,)
|
|
65
|
+
else:
|
|
66
|
+
_id_filter = id_filter
|
|
65
67
|
|
|
66
68
|
for obj_id in _id_filter:
|
|
67
69
|
if obj_id not in self._subscribers:
|
|
@@ -79,7 +81,7 @@ class SubscriptionHandler:
|
|
|
79
81
|
return unsubscribe
|
|
80
82
|
|
|
81
83
|
|
|
82
|
-
class ApiHandler
|
|
84
|
+
class ApiHandler[ApiItemT: ApiItem](SubscriptionHandler):
|
|
83
85
|
"""Base class for a map of API Items."""
|
|
84
86
|
|
|
85
87
|
api_id: ApiId | None = None
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from abc import abstractmethod
|
|
4
4
|
|
|
5
|
-
from ...models.api import
|
|
5
|
+
from ...models.api import ApiItem
|
|
6
6
|
from ...models.applications.application import ApplicationName, ApplicationStatus
|
|
7
7
|
from ..api_handler import ApiHandler, HandlerGroup
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class ApplicationHandler(ApiHandler[ApiItemT]):
|
|
10
|
+
class ApplicationHandler[ApiItemT: ApiItem](ApiHandler[ApiItemT]):
|
|
11
11
|
"""Generic application handler."""
|
|
12
12
|
|
|
13
13
|
app_name: ApplicationName
|
|
@@ -9,7 +9,6 @@ from packaging import version
|
|
|
9
9
|
from ...models.applications.application import (
|
|
10
10
|
Application,
|
|
11
11
|
ListApplicationsRequest,
|
|
12
|
-
ListApplicationsResponse,
|
|
13
12
|
)
|
|
14
13
|
from ..api_handler import ApiHandler
|
|
15
14
|
|
|
@@ -36,6 +35,5 @@ class ApplicationsHandler(ApiHandler[Application]):
|
|
|
36
35
|
|
|
37
36
|
async def list_applications(self) -> dict[str, Application]:
|
|
38
37
|
"""List all APIs registered on API Discovery service."""
|
|
39
|
-
|
|
40
|
-
response = ListApplicationsResponse.decode(bytes_data)
|
|
38
|
+
response = await self.vapix.api_request(ListApplicationsRequest())
|
|
41
39
|
return response.data
|
|
@@ -10,7 +10,6 @@ from ...models.applications.application import ApplicationName
|
|
|
10
10
|
from ...models.applications.fence_guard import (
|
|
11
11
|
Configuration,
|
|
12
12
|
GetConfigurationRequest,
|
|
13
|
-
GetConfigurationResponse,
|
|
14
13
|
)
|
|
15
14
|
from .application_handler import ApplicationHandler
|
|
16
15
|
|
|
@@ -22,6 +21,5 @@ class FenceGuardHandler(ApplicationHandler[Configuration]):
|
|
|
22
21
|
|
|
23
22
|
async def get_configuration(self) -> Configuration:
|
|
24
23
|
"""Get configuration of VMD4 application."""
|
|
25
|
-
|
|
26
|
-
response = GetConfigurationResponse.decode(bytes_data)
|
|
24
|
+
response = await self.vapix.api_request(GetConfigurationRequest())
|
|
27
25
|
return response.data
|
|
@@ -8,7 +8,6 @@ from ...models.applications.application import ApplicationName
|
|
|
8
8
|
from ...models.applications.loitering_guard import (
|
|
9
9
|
Configuration,
|
|
10
10
|
GetConfigurationRequest,
|
|
11
|
-
GetConfigurationResponse,
|
|
12
11
|
)
|
|
13
12
|
from .application_handler import ApplicationHandler
|
|
14
13
|
|
|
@@ -20,6 +19,5 @@ class LoiteringGuardHandler(ApplicationHandler[Configuration]):
|
|
|
20
19
|
|
|
21
20
|
async def get_configuration(self) -> Configuration:
|
|
22
21
|
"""Get configuration of VMD4 application."""
|
|
23
|
-
|
|
24
|
-
response = GetConfigurationResponse.decode(bytes_data)
|
|
22
|
+
response = await self.vapix.api_request(GetConfigurationRequest())
|
|
25
23
|
return response.data
|
|
@@ -9,7 +9,6 @@ from ...models.applications.application import ApplicationName
|
|
|
9
9
|
from ...models.applications.motion_guard import (
|
|
10
10
|
Configuration,
|
|
11
11
|
GetConfigurationRequest,
|
|
12
|
-
GetConfigurationResponse,
|
|
13
12
|
)
|
|
14
13
|
from .application_handler import ApplicationHandler
|
|
15
14
|
|
|
@@ -21,6 +20,5 @@ class MotionGuardHandler(ApplicationHandler[Configuration]):
|
|
|
21
20
|
|
|
22
21
|
async def get_configuration(self) -> Configuration:
|
|
23
22
|
"""Get configuration of VMD4 application."""
|
|
24
|
-
|
|
25
|
-
response = GetConfigurationResponse.decode(bytes_data)
|
|
23
|
+
response = await self.vapix.api_request(GetConfigurationRequest())
|
|
26
24
|
return response.data
|
|
@@ -7,7 +7,6 @@ from ...models.applications.application import ApplicationName
|
|
|
7
7
|
from ...models.applications.object_analytics import (
|
|
8
8
|
Configuration,
|
|
9
9
|
GetConfigurationRequest,
|
|
10
|
-
GetConfigurationResponse,
|
|
11
10
|
)
|
|
12
11
|
from .application_handler import ApplicationHandler
|
|
13
12
|
|
|
@@ -19,6 +18,5 @@ class ObjectAnalyticsHandler(ApplicationHandler[Configuration]):
|
|
|
19
18
|
|
|
20
19
|
async def get_configuration(self) -> Configuration:
|
|
21
20
|
"""Get configuration of object analytics application."""
|
|
22
|
-
|
|
23
|
-
response = GetConfigurationResponse.decode(bytes_data)
|
|
21
|
+
response = await self.vapix.api_request(GetConfigurationRequest())
|
|
24
22
|
return response.data
|
|
@@ -4,7 +4,6 @@ from ...models.applications.application import ApplicationName
|
|
|
4
4
|
from ...models.applications.vmd4 import (
|
|
5
5
|
Configuration,
|
|
6
6
|
GetConfigurationRequest,
|
|
7
|
-
GetConfigurationResponse,
|
|
8
7
|
)
|
|
9
8
|
from .application_handler import ApplicationHandler
|
|
10
9
|
|
|
@@ -16,6 +15,5 @@ class Vmd4Handler(ApplicationHandler[Configuration]):
|
|
|
16
15
|
|
|
17
16
|
async def get_configuration(self) -> Configuration:
|
|
18
17
|
"""Get configuration of VMD4 application."""
|
|
19
|
-
|
|
20
|
-
response = GetConfigurationResponse.decode(bytes_data)
|
|
18
|
+
response = await self.vapix.api_request(GetConfigurationRequest())
|
|
21
19
|
return response.data
|
|
@@ -10,9 +10,7 @@ from ..models.basic_device_info import (
|
|
|
10
10
|
API_VERSION,
|
|
11
11
|
DeviceInformation,
|
|
12
12
|
GetAllPropertiesRequest,
|
|
13
|
-
GetAllPropertiesResponse,
|
|
14
13
|
GetSupportedVersionsRequest,
|
|
15
|
-
GetSupportedVersionsResponse,
|
|
16
14
|
)
|
|
17
15
|
from .api_handler import ApiHandler, HandlerGroup
|
|
18
16
|
|
|
@@ -30,14 +28,12 @@ class BasicDeviceInfoHandler(ApiHandler[DeviceInformation]):
|
|
|
30
28
|
|
|
31
29
|
async def get_all_properties(self) -> dict[str, DeviceInformation]:
|
|
32
30
|
"""List all properties of basic device info."""
|
|
33
|
-
|
|
31
|
+
response = await self.vapix.api_request(
|
|
34
32
|
GetAllPropertiesRequest(self.api_version)
|
|
35
33
|
)
|
|
36
|
-
response = GetAllPropertiesResponse.decode(bytes_data)
|
|
37
34
|
return {"0": response.data}
|
|
38
35
|
|
|
39
36
|
async def get_supported_versions(self) -> list[str]:
|
|
40
37
|
"""List supported API versions."""
|
|
41
|
-
|
|
42
|
-
response = GetSupportedVersionsResponse.decode(bytes_data)
|
|
38
|
+
response = await self.vapix.api_request(GetSupportedVersionsRequest())
|
|
43
39
|
return response.data
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Event service and action service APIs available in Axis network device."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ..models.event_instance import (
|
|
6
|
+
EventInstance,
|
|
7
|
+
ListEventInstancesRequest,
|
|
8
|
+
)
|
|
9
|
+
from .api_handler import ApiHandler
|
|
10
|
+
from .event_manager import BLACK_LISTED_TOPICS
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ..models.event import Event
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EventInstanceHandler(ApiHandler[EventInstance]):
|
|
17
|
+
"""Event instances for Axis devices."""
|
|
18
|
+
|
|
19
|
+
async def _api_request(self) -> dict[str, EventInstance]:
|
|
20
|
+
"""Get default data of API discovery."""
|
|
21
|
+
return await self.get_event_instances()
|
|
22
|
+
|
|
23
|
+
async def get_event_instances(self) -> dict[str, EventInstance]:
|
|
24
|
+
"""List all event instances."""
|
|
25
|
+
response = await self.vapix.api_request(ListEventInstancesRequest())
|
|
26
|
+
return response.data
|
|
27
|
+
|
|
28
|
+
def get_expected_events_per_topic(
|
|
29
|
+
self,
|
|
30
|
+
include_internal_topics: bool = False,
|
|
31
|
+
) -> dict[str, list[Event]]:
|
|
32
|
+
"""Return expected startup events grouped by topic.
|
|
33
|
+
|
|
34
|
+
Event instances are the protocol-agnostic bootstrap source for startup
|
|
35
|
+
predeclaration. Returned events are synthesized from schema data and represent
|
|
36
|
+
expected event identity/state (operation=Initialized), not live stream updates.
|
|
37
|
+
"""
|
|
38
|
+
grouped: dict[str, list[Event]] = {}
|
|
39
|
+
for item in self.values():
|
|
40
|
+
if not include_internal_topics and item.topic in BLACK_LISTED_TOPICS:
|
|
41
|
+
continue
|
|
42
|
+
grouped[item.topic] = item.to_events()
|
|
43
|
+
return grouped
|
|
@@ -6,17 +6,19 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
from ..models.event import Event, EventOperation, EventTopic
|
|
8
8
|
|
|
9
|
-
SubscriptionCallback = Callable[[Event], None]
|
|
10
|
-
SubscriptionType = tuple[
|
|
9
|
+
type SubscriptionCallback = Callable[[Event], None]
|
|
10
|
+
type SubscriptionType = tuple[
|
|
11
11
|
SubscriptionCallback,
|
|
12
12
|
tuple[EventTopic, ...] | None,
|
|
13
13
|
tuple[EventOperation, ...] | None,
|
|
14
14
|
]
|
|
15
|
-
UnsubscribeType = Callable[[], None]
|
|
15
|
+
type UnsubscribeType = Callable[[], None]
|
|
16
16
|
|
|
17
17
|
ID_FILTER_ALL = "*"
|
|
18
18
|
|
|
19
19
|
BLACK_LISTED_TOPICS = [
|
|
20
|
+
"tns1:RuleEngine/tnsaxis:VideoMotionDetection/timer",
|
|
21
|
+
"tns1:RuleEngine/tnsaxis:VMD3/timer",
|
|
20
22
|
"tnsaxis:CameraApplicationPlatform/VMD/xinternal_data",
|
|
21
23
|
"tnsaxis:CameraApplicationPlatform/ObjectAnalytics/xinternal_data",
|
|
22
24
|
]
|
|
@@ -29,6 +31,7 @@ class EventManager:
|
|
|
29
31
|
def __init__(self) -> None:
|
|
30
32
|
"""Ready information about events."""
|
|
31
33
|
self._known_topics: set[str] = set()
|
|
34
|
+
self._unsupported_topics: set[str] = set()
|
|
32
35
|
self._subscribers: dict[str, list[SubscriptionType]] = {ID_FILTER_ALL: []}
|
|
33
36
|
|
|
34
37
|
def handler(self, data: bytes | dict[str, Any]) -> None:
|
|
@@ -37,7 +40,13 @@ class EventManager:
|
|
|
37
40
|
if LOGGER.isEnabledFor(logging.DEBUG):
|
|
38
41
|
LOGGER.debug(event)
|
|
39
42
|
|
|
40
|
-
if event.topic_base == EventTopic.UNKNOWN
|
|
43
|
+
if event.topic_base == EventTopic.UNKNOWN:
|
|
44
|
+
if event.topic not in self._unsupported_topics:
|
|
45
|
+
LOGGER.warning("Ignoring unsupported event topic %s", event.topic)
|
|
46
|
+
self._unsupported_topics.add(event.topic)
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if event.topic in BLACK_LISTED_TOPICS:
|
|
41
50
|
return
|
|
42
51
|
|
|
43
52
|
known = (unique_topic := f"{event.topic}_{event.id}") not in self._known_topics
|
|
@@ -12,29 +12,18 @@ from ..models.light_control import (
|
|
|
12
12
|
DisableLightRequest,
|
|
13
13
|
EnableLightRequest,
|
|
14
14
|
GetCurrentAngleOfIlluminationRequest,
|
|
15
|
-
GetCurrentAngleOfIlluminationResponse,
|
|
16
15
|
GetCurrentIntensityRequest,
|
|
17
|
-
GetCurrentIntensityResponse,
|
|
18
16
|
GetIndividualIntensityRequest,
|
|
19
|
-
GetIndividualIntensityResponse,
|
|
20
17
|
GetLightInformationRequest,
|
|
21
|
-
GetLightInformationResponse,
|
|
22
18
|
GetLightStatusRequest,
|
|
23
|
-
GetLightStatusResponse,
|
|
24
19
|
GetLightSynchronizeDayNightModeRequest,
|
|
25
20
|
GetLightSynchronizeDayNightModeResponse,
|
|
26
21
|
GetManualAngleOfIlluminationRequest,
|
|
27
|
-
GetManualAngleOfIlluminationResponse,
|
|
28
22
|
GetManualIntensityRequest,
|
|
29
|
-
GetManualIntensityResponse,
|
|
30
23
|
GetServiceCapabilitiesRequest,
|
|
31
|
-
GetServiceCapabilitiesResponse,
|
|
32
24
|
GetSupportedVersionsRequest,
|
|
33
|
-
GetSupportedVersionsResponse,
|
|
34
25
|
GetValidAngleOfIlluminationRequest,
|
|
35
|
-
GetValidAngleOfIlluminationResponse,
|
|
36
26
|
GetValidIntensityRequest,
|
|
37
|
-
GetValidIntensityResponse,
|
|
38
27
|
LightInformation,
|
|
39
28
|
Range,
|
|
40
29
|
ServiceCapabilities,
|
|
@@ -77,17 +66,17 @@ class LightHandler(ApiHandler[LightInformation]):
|
|
|
77
66
|
|
|
78
67
|
async def get_light_information(self) -> dict[str, LightInformation]:
|
|
79
68
|
"""List the light control information."""
|
|
80
|
-
|
|
69
|
+
response = await self.vapix.api_request(
|
|
81
70
|
GetLightInformationRequest(api_version=self.api_version)
|
|
82
71
|
)
|
|
83
|
-
return
|
|
72
|
+
return response.data
|
|
84
73
|
|
|
85
74
|
async def get_service_capabilities(self) -> ServiceCapabilities:
|
|
86
75
|
"""List the light control information."""
|
|
87
|
-
|
|
76
|
+
response = await self.vapix.api_request(
|
|
88
77
|
GetServiceCapabilitiesRequest(api_version=self.api_version)
|
|
89
78
|
)
|
|
90
|
-
return
|
|
79
|
+
return response.data
|
|
91
80
|
|
|
92
81
|
async def activate_light(self, light_id: str) -> None:
|
|
93
82
|
"""Activate the light."""
|
|
@@ -115,10 +104,10 @@ class LightHandler(ApiHandler[LightInformation]):
|
|
|
115
104
|
|
|
116
105
|
async def get_light_status(self, light_id: str) -> bool:
|
|
117
106
|
"""Get light status if its on or off."""
|
|
118
|
-
|
|
107
|
+
response = await self.vapix.api_request(
|
|
119
108
|
GetLightStatusRequest(api_version=self.api_version, light_id=light_id)
|
|
120
109
|
)
|
|
121
|
-
return
|
|
110
|
+
return response.data
|
|
122
111
|
|
|
123
112
|
async def set_automatic_intensity_mode(self, light_id: str, enabled: bool) -> None:
|
|
124
113
|
"""Enable the automatic light intensity control."""
|
|
@@ -132,10 +121,10 @@ class LightHandler(ApiHandler[LightInformation]):
|
|
|
132
121
|
|
|
133
122
|
async def get_valid_intensity(self, light_id: str) -> Range:
|
|
134
123
|
"""Get valid intensity range for light."""
|
|
135
|
-
|
|
124
|
+
response = await self.vapix.api_request(
|
|
136
125
|
GetValidIntensityRequest(api_version=self.api_version, light_id=light_id)
|
|
137
126
|
)
|
|
138
|
-
return
|
|
127
|
+
return response.data
|
|
139
128
|
|
|
140
129
|
async def set_manual_intensity(self, light_id: str, intensity: int) -> None:
|
|
141
130
|
"""Manually sets the intensity."""
|
|
@@ -149,10 +138,10 @@ class LightHandler(ApiHandler[LightInformation]):
|
|
|
149
138
|
|
|
150
139
|
async def get_manual_intensity(self, light_id: str) -> int:
|
|
151
140
|
"""Enable the automatic light intensity control."""
|
|
152
|
-
|
|
141
|
+
response = await self.vapix.api_request(
|
|
153
142
|
GetManualIntensityRequest(api_version=self.api_version, light_id=light_id)
|
|
154
143
|
)
|
|
155
|
-
return
|
|
144
|
+
return response.data
|
|
156
145
|
|
|
157
146
|
async def set_individual_intensity(
|
|
158
147
|
self, light_id: str, led_id: int, intensity: int
|
|
@@ -169,21 +158,21 @@ class LightHandler(ApiHandler[LightInformation]):
|
|
|
169
158
|
|
|
170
159
|
async def get_individual_intensity(self, light_id: str, led_id: int) -> int:
|
|
171
160
|
"""Receives the intensity from the setIndividualIntensity request."""
|
|
172
|
-
|
|
161
|
+
response = await self.vapix.api_request(
|
|
173
162
|
GetIndividualIntensityRequest(
|
|
174
163
|
api_version=self.api_version,
|
|
175
164
|
light_id=light_id,
|
|
176
165
|
led_id=led_id,
|
|
177
166
|
)
|
|
178
167
|
)
|
|
179
|
-
return
|
|
168
|
+
return response.data
|
|
180
169
|
|
|
181
170
|
async def get_current_intensity(self, light_id: str) -> int:
|
|
182
171
|
"""Receives the intensity from the setIndividualIntensity request."""
|
|
183
|
-
|
|
172
|
+
response = await self.vapix.api_request(
|
|
184
173
|
GetCurrentIntensityRequest(api_version=self.api_version, light_id=light_id)
|
|
185
174
|
)
|
|
186
|
-
return
|
|
175
|
+
return response.data
|
|
187
176
|
|
|
188
177
|
async def set_automatic_angle_of_illumination_mode(
|
|
189
178
|
self, light_id: str, enabled: bool
|
|
@@ -203,12 +192,12 @@ class LightHandler(ApiHandler[LightInformation]):
|
|
|
203
192
|
|
|
204
193
|
async def get_valid_angle_of_illumination(self, light_id: str) -> list[Range]:
|
|
205
194
|
"""List the valid angle of illumination values."""
|
|
206
|
-
|
|
195
|
+
response = await self.vapix.api_request(
|
|
207
196
|
GetValidAngleOfIlluminationRequest(
|
|
208
197
|
api_version=self.api_version, light_id=light_id
|
|
209
198
|
)
|
|
210
199
|
)
|
|
211
|
-
return
|
|
200
|
+
return response.data
|
|
212
201
|
|
|
213
202
|
async def set_manual_angle_of_illumination(
|
|
214
203
|
self, light_id: str, angle_of_illumination: int
|
|
@@ -228,21 +217,21 @@ class LightHandler(ApiHandler[LightInformation]):
|
|
|
228
217
|
|
|
229
218
|
async def get_manual_angle_of_illumination(self, light_id: str) -> int:
|
|
230
219
|
"""Get the angle of illumination."""
|
|
231
|
-
|
|
220
|
+
response = await self.vapix.api_request(
|
|
232
221
|
GetManualAngleOfIlluminationRequest(
|
|
233
222
|
api_version=self.api_version, light_id=light_id
|
|
234
223
|
)
|
|
235
224
|
)
|
|
236
|
-
return
|
|
225
|
+
return response.data
|
|
237
226
|
|
|
238
227
|
async def get_current_angle_of_illumination(self, light_id: str) -> int:
|
|
239
228
|
"""Receive the current angle of illumination."""
|
|
240
|
-
|
|
229
|
+
response = await self.vapix.api_request(
|
|
241
230
|
GetCurrentAngleOfIlluminationRequest(
|
|
242
231
|
api_version=self.api_version, light_id=light_id
|
|
243
232
|
)
|
|
244
233
|
)
|
|
245
|
-
return
|
|
234
|
+
return response.data
|
|
246
235
|
|
|
247
236
|
async def set_light_synchronization_day_night_mode(
|
|
248
237
|
self, light_id: str, enabled: bool
|
|
@@ -258,14 +247,16 @@ class LightHandler(ApiHandler[LightInformation]):
|
|
|
258
247
|
|
|
259
248
|
async def get_light_synchronization_day_night_mode(self, light_id: str) -> bool:
|
|
260
249
|
"""Check if the automatic synchronization is enabled with the day/night mode."""
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
250
|
+
response: GetLightSynchronizeDayNightModeResponse = (
|
|
251
|
+
await self.vapix.api_request(
|
|
252
|
+
GetLightSynchronizeDayNightModeRequest(
|
|
253
|
+
api_version=self.api_version, light_id=light_id
|
|
254
|
+
)
|
|
264
255
|
)
|
|
265
256
|
)
|
|
266
|
-
return
|
|
257
|
+
return response.data
|
|
267
258
|
|
|
268
259
|
async def get_supported_versions(self) -> list[str]:
|
|
269
260
|
"""List supported API versions."""
|
|
270
|
-
|
|
271
|
-
return
|
|
261
|
+
response = await self.vapix.api_request(GetSupportedVersionsRequest())
|
|
262
|
+
return response.data
|