axis 59__tar.gz → 60__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-59 → axis-60}/PKG-INFO +1 -1
- {axis-59 → axis-60}/axis/interfaces/mqtt.py +7 -14
- {axis-59 → axis-60}/axis/interfaces/vapix.py +3 -1
- {axis-59 → axis-60}/axis/models/light_control.py +9 -7
- {axis-59 → axis-60}/axis/models/mqtt.py +178 -19
- {axis-59 → axis-60}/axis/models/parameters/properties.py +3 -2
- {axis-59 → axis-60}/axis.egg-info/PKG-INFO +1 -1
- {axis-59 → axis-60}/axis.egg-info/requires.txt +2 -2
- {axis-59 → axis-60}/pyproject.toml +3 -3
- {axis-59 → axis-60}/tests/test_light_control.py +18 -0
- {axis-59 → axis-60}/tests/test_mqtt.py +4 -1
- {axis-59 → axis-60}/LICENSE +0 -0
- {axis-59 → axis-60}/README.md +0 -0
- {axis-59 → axis-60}/axis/__init__.py +0 -0
- {axis-59 → axis-60}/axis/__main__.py +0 -0
- {axis-59 → axis-60}/axis/device.py +0 -0
- {axis-59 → axis-60}/axis/errors.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/__init__.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/api_discovery.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/api_handler.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/applications/__init__.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/applications/application_handler.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/applications/applications.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/applications/fence_guard.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/applications/loitering_guard.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/applications/motion_guard.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/applications/object_analytics.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/applications/vmd4.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/basic_device_info.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/event_instances.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/event_manager.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/light_control.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/parameters/__init__.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/parameters/brand.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/parameters/image.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/parameters/io_port.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/parameters/param_cgi.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/parameters/param_handler.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/parameters/properties.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/parameters/ptz.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/parameters/stream_profile.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/pir_sensor_configuration.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/port_cgi.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/port_management.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/ptz.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/pwdgrp_cgi.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/stream_profiles.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/user_groups.py +0 -0
- {axis-59 → axis-60}/axis/interfaces/view_areas.py +0 -0
- {axis-59 → axis-60}/axis/models/__init__.py +0 -0
- {axis-59 → axis-60}/axis/models/api.py +0 -0
- {axis-59 → axis-60}/axis/models/api_discovery.py +0 -0
- {axis-59 → axis-60}/axis/models/applications/__init__.py +0 -0
- {axis-59 → axis-60}/axis/models/applications/application.py +0 -0
- {axis-59 → axis-60}/axis/models/applications/fence_guard.py +0 -0
- {axis-59 → axis-60}/axis/models/applications/loitering_guard.py +0 -0
- {axis-59 → axis-60}/axis/models/applications/motion_guard.py +0 -0
- {axis-59 → axis-60}/axis/models/applications/object_analytics.py +0 -0
- {axis-59 → axis-60}/axis/models/applications/vmd4.py +0 -0
- {axis-59 → axis-60}/axis/models/basic_device_info.py +0 -0
- {axis-59 → axis-60}/axis/models/configuration.py +0 -0
- {axis-59 → axis-60}/axis/models/event.py +0 -0
- {axis-59 → axis-60}/axis/models/event_instance.py +0 -0
- {axis-59 → axis-60}/axis/models/parameters/__init__.py +0 -0
- {axis-59 → axis-60}/axis/models/parameters/brand.py +0 -0
- {axis-59 → axis-60}/axis/models/parameters/image.py +0 -0
- {axis-59 → axis-60}/axis/models/parameters/io_port.py +0 -0
- {axis-59 → axis-60}/axis/models/parameters/param_cgi.py +0 -0
- {axis-59 → axis-60}/axis/models/parameters/ptz.py +0 -0
- {axis-59 → axis-60}/axis/models/parameters/stream_profile.py +0 -0
- {axis-59 → axis-60}/axis/models/pir_sensor_configuration.py +0 -0
- {axis-59 → axis-60}/axis/models/port_cgi.py +0 -0
- {axis-59 → axis-60}/axis/models/port_management.py +0 -0
- {axis-59 → axis-60}/axis/models/ptz_cgi.py +0 -0
- {axis-59 → axis-60}/axis/models/pwdgrp_cgi.py +0 -0
- {axis-59 → axis-60}/axis/models/stream_profile.py +0 -0
- {axis-59 → axis-60}/axis/models/user_group.py +0 -0
- {axis-59 → axis-60}/axis/models/view_area.py +0 -0
- {axis-59 → axis-60}/axis/py.typed +0 -0
- {axis-59 → axis-60}/axis/rtsp.py +0 -0
- {axis-59 → axis-60}/axis/stream_manager.py +0 -0
- {axis-59 → axis-60}/axis.egg-info/SOURCES.txt +0 -0
- {axis-59 → axis-60}/axis.egg-info/dependency_links.txt +0 -0
- {axis-59 → axis-60}/axis.egg-info/entry_points.txt +0 -0
- {axis-59 → axis-60}/axis.egg-info/top_level.txt +0 -0
- {axis-59 → axis-60}/setup.cfg +0 -0
- {axis-59 → axis-60}/tests/test_api_discovery.py +0 -0
- {axis-59 → axis-60}/tests/test_api_handler.py +0 -0
- {axis-59 → axis-60}/tests/test_basic_device_info.py +0 -0
- {axis-59 → axis-60}/tests/test_configuration.py +0 -0
- {axis-59 → axis-60}/tests/test_device.py +0 -0
- {axis-59 → axis-60}/tests/test_event.py +0 -0
- {axis-59 → axis-60}/tests/test_event_instances.py +0 -0
- {axis-59 → axis-60}/tests/test_event_stream.py +0 -0
- {axis-59 → axis-60}/tests/test_pir_sensor_configuration.py +0 -0
- {axis-59 → axis-60}/tests/test_port_cgi.py +0 -0
- {axis-59 → axis-60}/tests/test_port_management.py +0 -0
- {axis-59 → axis-60}/tests/test_ptz.py +0 -0
- {axis-59 → axis-60}/tests/test_pwdgrp_cgi.py +0 -0
- {axis-59 → axis-60}/tests/test_rtsp.py +0 -0
- {axis-59 → axis-60}/tests/test_stream_manager.py +0 -0
- {axis-59 → axis-60}/tests/test_stream_profiles.py +0 -0
- {axis-59 → axis-60}/tests/test_user_groups.py +0 -0
- {axis-59 → axis-60}/tests/test_vapix.py +0 -0
- {axis-59 → axis-60}/tests/test_view_areas.py +0 -0
{axis-59 → axis-60}/PKG-INFO
RENAMED
|
@@ -6,6 +6,7 @@ import orjson
|
|
|
6
6
|
|
|
7
7
|
from ..models.api_discovery import ApiId
|
|
8
8
|
from ..models.mqtt import (
|
|
9
|
+
API_VERSION,
|
|
9
10
|
ActivateClientRequest,
|
|
10
11
|
ClientConfig,
|
|
11
12
|
ClientConfigStatus,
|
|
@@ -21,8 +22,6 @@ from ..models.mqtt import (
|
|
|
21
22
|
)
|
|
22
23
|
from .api_handler import ApiHandler
|
|
23
24
|
|
|
24
|
-
API_VERSION = "1.0"
|
|
25
|
-
|
|
26
25
|
DEFAULT_TOPICS = ["//."]
|
|
27
26
|
|
|
28
27
|
|
|
@@ -56,41 +55,36 @@ class MqttClientHandler(ApiHandler[Any]):
|
|
|
56
55
|
|
|
57
56
|
async def configure_client(self, client_config: ClientConfig) -> None:
|
|
58
57
|
"""Configure MQTT Client."""
|
|
59
|
-
discovery_item = self.vapix.api_discovery[self.api_id]
|
|
60
58
|
await self.vapix.api_request(
|
|
61
59
|
ConfigureClientRequest(
|
|
62
|
-
api_version=
|
|
60
|
+
api_version=self.default_api_version, client_config=client_config
|
|
63
61
|
)
|
|
64
62
|
)
|
|
65
63
|
|
|
66
64
|
async def activate(self) -> None:
|
|
67
65
|
"""Activate MQTT Client."""
|
|
68
|
-
discovery_item = self.vapix.api_discovery[self.api_id]
|
|
69
66
|
await self.vapix.api_request(
|
|
70
|
-
ActivateClientRequest(api_version=
|
|
67
|
+
ActivateClientRequest(api_version=self.default_api_version)
|
|
71
68
|
)
|
|
72
69
|
|
|
73
70
|
async def deactivate(self) -> None:
|
|
74
71
|
"""Deactivate MQTT Client."""
|
|
75
|
-
discovery_item = self.vapix.api_discovery[self.api_id]
|
|
76
72
|
await self.vapix.api_request(
|
|
77
|
-
DeactivateClientRequest(api_version=
|
|
73
|
+
DeactivateClientRequest(api_version=self.default_api_version)
|
|
78
74
|
)
|
|
79
75
|
|
|
80
76
|
async def get_client_status(self) -> ClientConfigStatus:
|
|
81
77
|
"""Get MQTT Client status."""
|
|
82
|
-
discovery_item = self.vapix.api_discovery[self.api_id]
|
|
83
78
|
bytes_data = await self.vapix.api_request(
|
|
84
|
-
GetClientStatusRequest(api_version=
|
|
79
|
+
GetClientStatusRequest(api_version=self.default_api_version)
|
|
85
80
|
)
|
|
86
81
|
response = GetClientStatusResponse.decode(bytes_data)
|
|
87
82
|
return response.data
|
|
88
83
|
|
|
89
84
|
async def get_event_publication_config(self) -> EventPublicationConfig:
|
|
90
85
|
"""Get MQTT Client event publication config."""
|
|
91
|
-
discovery_item = self.vapix.api_discovery[self.api_id]
|
|
92
86
|
bytes_data = await self.vapix.api_request(
|
|
93
|
-
GetEventPublicationConfigRequest(api_version=
|
|
87
|
+
GetEventPublicationConfigRequest(api_version=self.default_api_version)
|
|
94
88
|
)
|
|
95
89
|
response = GetEventPublicationConfigResponse.decode(bytes_data)
|
|
96
90
|
return response.data
|
|
@@ -99,13 +93,12 @@ class MqttClientHandler(ApiHandler[Any]):
|
|
|
99
93
|
self, topics: list[str] = DEFAULT_TOPICS
|
|
100
94
|
) -> None:
|
|
101
95
|
"""Configure MQTT Client event publication."""
|
|
102
|
-
discovery_item = self.vapix.api_discovery[self.api_id]
|
|
103
96
|
event_filters = EventFilter.from_list(
|
|
104
97
|
[{"topicFilter": topic} for topic in topics]
|
|
105
98
|
)
|
|
106
99
|
config = EventPublicationConfig(event_filter_list=event_filters)
|
|
107
100
|
await self.vapix.api_request(
|
|
108
101
|
ConfigureEventPublicationRequest(
|
|
109
|
-
api_version=
|
|
102
|
+
api_version=self.default_api_version, config=config
|
|
110
103
|
)
|
|
111
104
|
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Python library to enable Axis devices to integrate with Home Assistant."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import asyncio
|
|
4
6
|
import logging
|
|
5
7
|
from typing import TYPE_CHECKING, Any
|
|
@@ -49,7 +51,7 @@ TIME_OUT = 15
|
|
|
49
51
|
class Vapix:
|
|
50
52
|
"""Vapix parameter request."""
|
|
51
53
|
|
|
52
|
-
def __init__(self, device:
|
|
54
|
+
def __init__(self, device: AxisDevice) -> None:
|
|
53
55
|
"""Store local reference to device config."""
|
|
54
56
|
self.device = device
|
|
55
57
|
self.auth = httpx.DigestAuth(device.config.username, device.config.password)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Light Control API data model."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from dataclasses import dataclass
|
|
4
6
|
from typing import NotRequired, Self
|
|
5
7
|
|
|
@@ -225,14 +227,14 @@ class Range:
|
|
|
225
227
|
high: int
|
|
226
228
|
|
|
227
229
|
@classmethod
|
|
228
|
-
def from_dict(cls, data: RangeT) ->
|
|
230
|
+
def from_dict(cls, data: RangeT) -> Self:
|
|
229
231
|
"""Create range object from dict."""
|
|
230
|
-
return
|
|
232
|
+
return cls(low=data["low"], high=data["high"])
|
|
231
233
|
|
|
232
234
|
@classmethod
|
|
233
|
-
def from_list(cls, data: list[RangeT]) -> list[
|
|
235
|
+
def from_list(cls, data: list[RangeT]) -> list[Self]:
|
|
234
236
|
"""Create range object from dict."""
|
|
235
|
-
return [
|
|
237
|
+
return [cls.from_dict(range) for range in data]
|
|
236
238
|
|
|
237
239
|
|
|
238
240
|
@dataclass
|
|
@@ -277,7 +279,7 @@ class GetLightInformationResponse(ApiResponse[dict[str, LightInformation]]):
|
|
|
277
279
|
api_version=data["apiVersion"],
|
|
278
280
|
context=data["context"],
|
|
279
281
|
method=data["method"],
|
|
280
|
-
data=LightInformation.decode_to_dict(data
|
|
282
|
+
data=LightInformation.decode_to_dict(data.get("data", {}).get("items", [])),
|
|
281
283
|
)
|
|
282
284
|
|
|
283
285
|
|
|
@@ -294,9 +296,9 @@ class ServiceCapabilities:
|
|
|
294
296
|
day_night_synchronize_support: bool
|
|
295
297
|
|
|
296
298
|
@classmethod
|
|
297
|
-
def from_dict(cls, data: ServiceCapabilitiesT) ->
|
|
299
|
+
def from_dict(cls, data: ServiceCapabilitiesT) -> Self:
|
|
298
300
|
"""Create service capabilities object from dict."""
|
|
299
|
-
return
|
|
301
|
+
return cls(
|
|
300
302
|
automatic_intensity_support=data["automaticIntensitySupport"],
|
|
301
303
|
manual_intensity_support=data["manualIntensitySupport"],
|
|
302
304
|
get_current_intensity_support=data["getCurrentIntensitySupport"],
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""MQTT Client api."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from dataclasses import dataclass
|
|
4
6
|
import enum
|
|
5
7
|
from typing import Literal, NotRequired, Self
|
|
@@ -24,7 +26,7 @@ class MessageT(TypedDict):
|
|
|
24
26
|
|
|
25
27
|
useDefault: bool
|
|
26
28
|
message: NotRequired[str]
|
|
27
|
-
qos: NotRequired[
|
|
29
|
+
qos: NotRequired[Literal[0, 1, 2]]
|
|
28
30
|
retain: NotRequired[bool]
|
|
29
31
|
topic: NotRequired[str]
|
|
30
32
|
|
|
@@ -70,7 +72,7 @@ class ConfigT(TypedDict):
|
|
|
70
72
|
class StatusT(TypedDict):
|
|
71
73
|
"""Represent a status object."""
|
|
72
74
|
|
|
73
|
-
connectionStatus: Literal["connected", "disconnected"]
|
|
75
|
+
connectionStatus: Literal["connected", "disconnected", "not connected"]
|
|
74
76
|
state: Literal["active", "inactive"]
|
|
75
77
|
|
|
76
78
|
|
|
@@ -149,6 +151,14 @@ class ClientConnectionState(enum.StrEnum):
|
|
|
149
151
|
CONNECTED = "connected"
|
|
150
152
|
DISCONNECTED = "disconnected"
|
|
151
153
|
|
|
154
|
+
@classmethod
|
|
155
|
+
def _missing_(cls, value: object) -> ClientConnectionState:
|
|
156
|
+
"""Set default enum member if an unknown value is provided.
|
|
157
|
+
|
|
158
|
+
Some firmwares report "not connected" instead of "disconnected".
|
|
159
|
+
"""
|
|
160
|
+
return cls.DISCONNECTED
|
|
161
|
+
|
|
152
162
|
|
|
153
163
|
class ServerProtocol(enum.StrEnum):
|
|
154
164
|
"""Connection protocols used in the server configuration."""
|
|
@@ -164,10 +174,35 @@ class Message:
|
|
|
164
174
|
"""Message description."""
|
|
165
175
|
|
|
166
176
|
use_default: bool = True
|
|
177
|
+
"""Specifies if the default configuration should be used.
|
|
178
|
+
|
|
179
|
+
If set to true, other options in this parameter will be discarded.
|
|
180
|
+
If set to false, topic, messages, retained and qos options are required and used.
|
|
181
|
+
"""
|
|
167
182
|
topic: str | None = None
|
|
183
|
+
"""The topic that should be used with the message."""
|
|
168
184
|
message: str | None = None
|
|
185
|
+
"""The message that should be used with the message."""
|
|
169
186
|
retain: bool | None = None
|
|
170
|
-
|
|
187
|
+
"""The retained option that should be used with the message."""
|
|
188
|
+
qos: Literal[0, 1, 2] | None = None
|
|
189
|
+
"""The QoS option that should be used with the message."""
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def from_dict(cls, data: MessageT) -> Self:
|
|
193
|
+
"""Create message object from dict."""
|
|
194
|
+
return cls(
|
|
195
|
+
use_default=data["useDefault"],
|
|
196
|
+
topic=data.get("topic"),
|
|
197
|
+
message=data.get("message"),
|
|
198
|
+
retain=data.get("retain"),
|
|
199
|
+
qos=data.get("qos"),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def from_dict_or_none(cls, data: MessageT | None) -> Self | None:
|
|
204
|
+
"""Create class instance if data is not None."""
|
|
205
|
+
return cls.from_dict(data) if data is not None else None
|
|
171
206
|
|
|
172
207
|
def to_dict(self) -> MessageT:
|
|
173
208
|
"""Create json dict from object."""
|
|
@@ -188,15 +223,42 @@ class Server:
|
|
|
188
223
|
"""Represent server config."""
|
|
189
224
|
|
|
190
225
|
host: str
|
|
226
|
+
"""The Broker location, i.e. the Hostname or IP address."""
|
|
191
227
|
protocol: ServerProtocol = ServerProtocol.TCP
|
|
228
|
+
"""Contains the protocols used by params.server.
|
|
229
|
+
|
|
230
|
+
Possible values are:
|
|
231
|
+
- tcp: MQTT over TCP
|
|
232
|
+
- ssl: MQTT over SSL
|
|
233
|
+
- ws: MQTT over Websocket
|
|
234
|
+
- wss: MQTT over Websocket Secure
|
|
235
|
+
"""
|
|
192
236
|
alpn_protocol: str | None = None
|
|
237
|
+
"""The ALPN protocol that should be used if the selected protocol was ssl or wss.
|
|
238
|
+
|
|
239
|
+
If the string value is empty the ALPN will not be used.
|
|
240
|
+
Default value is empty and the maximum length of the protocol is 255 bytes.
|
|
241
|
+
"""
|
|
193
242
|
basepath: str | None = None
|
|
243
|
+
"""The path that should be used as a suffix for the constructed URL.
|
|
244
|
+
|
|
245
|
+
Will only be used for the ws and wss connections.
|
|
246
|
+
The default value is empty string.
|
|
247
|
+
"""
|
|
194
248
|
port: int | None = None
|
|
249
|
+
"""The port that should be used.
|
|
250
|
+
|
|
251
|
+
Default values for the protocols are:
|
|
252
|
+
- tcp: 1883
|
|
253
|
+
- ssl: 8883
|
|
254
|
+
- ws: 80
|
|
255
|
+
- wss: 443
|
|
256
|
+
"""
|
|
195
257
|
|
|
196
258
|
@classmethod
|
|
197
|
-
def from_dict(cls, data: ServerT) ->
|
|
259
|
+
def from_dict(cls, data: ServerT) -> Self:
|
|
198
260
|
"""Create server object from dict."""
|
|
199
|
-
return
|
|
261
|
+
return cls(
|
|
200
262
|
host=data["host"],
|
|
201
263
|
protocol=ServerProtocol(data["protocol"]),
|
|
202
264
|
alpn_protocol=data.get("alpnProtocol"),
|
|
@@ -221,8 +283,31 @@ class Ssl:
|
|
|
221
283
|
"""Represent SSL config."""
|
|
222
284
|
|
|
223
285
|
validate_server_cert: bool = False
|
|
286
|
+
"""Specifies if the server certificate shall be validated."""
|
|
224
287
|
ca_cert_id: str | None = None
|
|
288
|
+
"""Specifies the CA Certificate that should be used to validate the server certificate.
|
|
289
|
+
|
|
290
|
+
The certificates are managed through the user interface or via ONVIF services.
|
|
291
|
+
"""
|
|
225
292
|
client_cert_id: str | None = None
|
|
293
|
+
"""Specifies the client certificate and key that should be used.
|
|
294
|
+
|
|
295
|
+
The certificates are managed through the user interface or via ONVIF services.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
@classmethod
|
|
299
|
+
def from_dict(cls, data: SslT) -> Self:
|
|
300
|
+
"""Create client status object from dict."""
|
|
301
|
+
return cls(
|
|
302
|
+
validate_server_cert=data["validateServerCert"],
|
|
303
|
+
ca_cert_id=data.get("CACertID"),
|
|
304
|
+
client_cert_id=data.get("clientCertID"),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
@classmethod
|
|
308
|
+
def from_dict_or_none(cls, data: SslT | None) -> Self | None:
|
|
309
|
+
"""Create class instance if data is not None."""
|
|
310
|
+
return cls.from_dict(data) if data is not None else None
|
|
226
311
|
|
|
227
312
|
def to_dict(self) -> SslT:
|
|
228
313
|
"""Create json dict from object."""
|
|
@@ -239,23 +324,90 @@ class ClientConfig:
|
|
|
239
324
|
"""Represent client config."""
|
|
240
325
|
|
|
241
326
|
server: Server
|
|
327
|
+
"""Contains the address and protocol related information."""
|
|
242
328
|
activate_on_reboot: bool | None = None
|
|
243
329
|
auto_reconnect: bool | None = None
|
|
330
|
+
"""Specifies if the client should reconnect on an unintentional disconnect."""
|
|
244
331
|
clean_session: bool | None = None
|
|
332
|
+
"""This parameter controls the behavior during connection and disconnection time.
|
|
333
|
+
|
|
334
|
+
Affects both the client and the server when this parameters is true,
|
|
335
|
+
the state information is discarded when the client and server change state.
|
|
336
|
+
Setting the parameter to false means that the state information is kept.
|
|
337
|
+
"""
|
|
245
338
|
client_id: str | None = None
|
|
339
|
+
"""The client identifier sent to the server when the client connects to it."""
|
|
246
340
|
connect_message: Message | None = None
|
|
341
|
+
"""Specifies if a message should be sent when a connection is established.
|
|
342
|
+
|
|
343
|
+
Contains options related to connect announcements.
|
|
344
|
+
If this object is not defined this message won’t be sent.
|
|
345
|
+
"""
|
|
247
346
|
connect_timeout: int | None = None
|
|
347
|
+
"""The timed interval (in seconds) to allow a connect to finish.
|
|
348
|
+
|
|
349
|
+
The default value is 60.
|
|
350
|
+
"""
|
|
351
|
+
device_topic_prefix: str | None = None
|
|
352
|
+
"""Specifies a prefix on MQTT topics in various scenarios.
|
|
353
|
+
|
|
354
|
+
Such as when you want to configure the translation of events into MQTT messages
|
|
355
|
+
or prefix all published MQTT messages with a common prefix.
|
|
356
|
+
The default value is axis/{device serial number}.
|
|
357
|
+
"""
|
|
248
358
|
disconnect_message: Message | None = None
|
|
359
|
+
"""Specifies if a message should be sent when the client is manually disconnected.
|
|
360
|
+
|
|
361
|
+
Contains options related to manual disconnect announcements.
|
|
362
|
+
If this object is not defined this message won’t be sent.
|
|
363
|
+
This message should not be confused with LWT,
|
|
364
|
+
as it is used when the connection is lost and managed by the broker.
|
|
365
|
+
"""
|
|
249
366
|
keep_alive_interval: int | None = None
|
|
367
|
+
"""Defines the maximum time (in seconds).
|
|
368
|
+
|
|
369
|
+
Time intervalthat should pass without communication between the client and server.
|
|
370
|
+
At least one message will be sent over the network by the client
|
|
371
|
+
during each keep alive period and the interval makes it possible to detect
|
|
372
|
+
when the server is no longer available without having to
|
|
373
|
+
wait for the TCP/IP timeout.
|
|
374
|
+
The default value is 60."""
|
|
250
375
|
last_will_testament: Message | None = None
|
|
376
|
+
"""Contains the options related to LWT.
|
|
377
|
+
|
|
378
|
+
If LWT is not required, this parameter is not included.
|
|
379
|
+
"""
|
|
251
380
|
password: str | None = None
|
|
381
|
+
"""The password that should be used for authentication."""
|
|
252
382
|
ssl: Ssl | None = None
|
|
383
|
+
"""Contains the options related to the SSL connection.
|
|
384
|
+
|
|
385
|
+
This object should only be present if the connection type is ssl or wss.
|
|
386
|
+
"""
|
|
253
387
|
username: str | None = None
|
|
388
|
+
"""The user name that should be used for authentication and authorization."""
|
|
254
389
|
|
|
255
390
|
@classmethod
|
|
256
|
-
def from_dict(cls, data: ConfigT) ->
|
|
391
|
+
def from_dict(cls, data: ConfigT) -> Self:
|
|
257
392
|
"""Create client status object from dict."""
|
|
258
|
-
return
|
|
393
|
+
return cls(
|
|
394
|
+
server=Server.from_dict(data["server"]),
|
|
395
|
+
activate_on_reboot=data.get("activateOnReboot"),
|
|
396
|
+
auto_reconnect=data.get("autoReconnect"),
|
|
397
|
+
clean_session=data.get("cleanSession"),
|
|
398
|
+
client_id=data.get("clientId"),
|
|
399
|
+
connect_message=Message.from_dict_or_none(data.get("connectMessage")),
|
|
400
|
+
connect_timeout=data.get("connectTimeout"),
|
|
401
|
+
device_topic_prefix=data.get("deviceTopicPrefix"),
|
|
402
|
+
disconnect_message=Message.from_dict_or_none(data.get("disconnectMessage")),
|
|
403
|
+
keep_alive_interval=data.get("keepAliveInterval"),
|
|
404
|
+
last_will_testament=Message.from_dict_or_none(
|
|
405
|
+
data.get("lastWillTestament")
|
|
406
|
+
),
|
|
407
|
+
password=data.get("password"),
|
|
408
|
+
ssl=Ssl.from_dict_or_none(data.get("ssl")),
|
|
409
|
+
username=data.get("username"),
|
|
410
|
+
)
|
|
259
411
|
|
|
260
412
|
def to_dict(self) -> ConfigT:
|
|
261
413
|
"""Create json dict from object."""
|
|
@@ -293,14 +445,19 @@ class ClientStatus:
|
|
|
293
445
|
|
|
294
446
|
connection_status: ClientConnectionState
|
|
295
447
|
state: ClientState
|
|
448
|
+
active: bool
|
|
449
|
+
"""The current state of the client. Possible values are active and inactive."""
|
|
450
|
+
connected: bool
|
|
451
|
+
"""The current connection state of your client. Possible values are connected, disconnected."""
|
|
296
452
|
|
|
297
453
|
@classmethod
|
|
298
|
-
def from_dict(cls, data: StatusT) ->
|
|
454
|
+
def from_dict(cls, data: StatusT) -> Self:
|
|
299
455
|
"""Create client status object from dict."""
|
|
300
|
-
|
|
301
|
-
return ClientStatus(
|
|
456
|
+
return cls(
|
|
302
457
|
connection_status=ClientConnectionState(data["connectionStatus"].lower()),
|
|
303
458
|
state=ClientState(data["state"].lower()),
|
|
459
|
+
active=data["state"] == "active",
|
|
460
|
+
connected=data["connectionStatus"] == "connected",
|
|
304
461
|
)
|
|
305
462
|
|
|
306
463
|
|
|
@@ -309,12 +466,14 @@ class ClientConfigStatus:
|
|
|
309
466
|
"""GetClientStatus response."""
|
|
310
467
|
|
|
311
468
|
config: ClientConfig
|
|
469
|
+
"""The Config of the MQTT client."""
|
|
312
470
|
status: ClientStatus
|
|
471
|
+
"""The Status of the MQTT client."""
|
|
313
472
|
|
|
314
473
|
@classmethod
|
|
315
|
-
def from_dict(cls, data: ClientStatusDataT) ->
|
|
474
|
+
def from_dict(cls, data: ClientStatusDataT) -> Self:
|
|
316
475
|
"""Create client config status object from dict."""
|
|
317
|
-
return
|
|
476
|
+
return cls(
|
|
318
477
|
config=ClientConfig.from_dict(data["config"]),
|
|
319
478
|
status=ClientStatus.from_dict(data["status"]),
|
|
320
479
|
)
|
|
@@ -329,18 +488,18 @@ class EventFilter:
|
|
|
329
488
|
retain: str | None = None
|
|
330
489
|
|
|
331
490
|
@classmethod
|
|
332
|
-
def from_dict(cls, data: EventFilterT) ->
|
|
491
|
+
def from_dict(cls, data: EventFilterT) -> Self:
|
|
333
492
|
"""Create event filter object from dict."""
|
|
334
|
-
return
|
|
493
|
+
return cls(
|
|
335
494
|
topic_filter=data["topicFilter"],
|
|
336
495
|
qos=data.get("qos"),
|
|
337
496
|
retain=data.get("retain"),
|
|
338
497
|
)
|
|
339
498
|
|
|
340
499
|
@classmethod
|
|
341
|
-
def from_list(cls, data: list[EventFilterT]) ->
|
|
500
|
+
def from_list(cls, data: list[EventFilterT]) -> list[Self]:
|
|
342
501
|
"""Create event filter object from dict."""
|
|
343
|
-
return [
|
|
502
|
+
return [cls.from_dict(item) for item in data]
|
|
344
503
|
|
|
345
504
|
def to_dict(self) -> EventFilterT:
|
|
346
505
|
"""Create json dict from object."""
|
|
@@ -352,7 +511,7 @@ class EventFilter:
|
|
|
352
511
|
return data
|
|
353
512
|
|
|
354
513
|
@classmethod
|
|
355
|
-
def to_list(cls, data:
|
|
514
|
+
def to_list(cls, data: list[EventFilter]) -> list[EventFilterT]:
|
|
356
515
|
"""Create json list from object."""
|
|
357
516
|
return [item.to_dict() for item in data]
|
|
358
517
|
|
|
@@ -369,9 +528,9 @@ class EventPublicationConfig:
|
|
|
369
528
|
event_filter_list: list[EventFilter] | None = None
|
|
370
529
|
|
|
371
530
|
@classmethod
|
|
372
|
-
def from_dict(cls, data: EventPublicationConfigT) ->
|
|
531
|
+
def from_dict(cls, data: EventPublicationConfigT) -> Self:
|
|
373
532
|
"""Create client config status object from dict."""
|
|
374
|
-
return
|
|
533
|
+
return cls(
|
|
375
534
|
topic_prefix=data["topicPrefix"],
|
|
376
535
|
custom_topic_prefix=data["customTopicPrefix"],
|
|
377
536
|
append_event_topic=data["appendEventTopic"],
|
|
@@ -188,8 +188,9 @@ class PropertyParam(ParamItem):
|
|
|
188
188
|
.get("PTZ", {})
|
|
189
189
|
.get("Presets", {})
|
|
190
190
|
.get("Version", False),
|
|
191
|
-
embedded_development=data
|
|
192
|
-
|
|
191
|
+
embedded_development=data.get("EmbeddedDevelopment", {}).get(
|
|
192
|
+
"Version", "0.0"
|
|
193
|
+
),
|
|
193
194
|
firmware_build_date=data["Firmware"]["BuildDate"],
|
|
194
195
|
firmware_build_number=data["Firmware"]["BuildNumber"],
|
|
195
196
|
firmware_version=data["Firmware"]["Version"],
|
|
@@ -5,7 +5,7 @@ xmltodict>=0.13.0
|
|
|
5
5
|
|
|
6
6
|
[requirements]
|
|
7
7
|
httpx==0.27.0
|
|
8
|
-
orjson==3.
|
|
8
|
+
orjson==3.10.0
|
|
9
9
|
packaging==24.0
|
|
10
10
|
xmltodict==0.13.0
|
|
11
11
|
|
|
@@ -18,7 +18,7 @@ pytest==8.1.1
|
|
|
18
18
|
pytest-aiohttp==1.0.5
|
|
19
19
|
pytest-asyncio==0.23.6
|
|
20
20
|
pytest-cov==5.0.0
|
|
21
|
-
respx==0.21.
|
|
21
|
+
respx==0.21.1
|
|
22
22
|
ruff==0.3.4
|
|
23
23
|
types-orjson==3.6.2
|
|
24
24
|
types-xmltodict==v0.13.0.3
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "axis"
|
|
7
|
-
version = "
|
|
7
|
+
version = "60"
|
|
8
8
|
license = {text = "MIT"}
|
|
9
9
|
description = "A Python library for communicating with devices from Axis Communications"
|
|
10
10
|
readme = "README.md"
|
|
@@ -29,7 +29,7 @@ dependencies = [
|
|
|
29
29
|
[project.optional-dependencies]
|
|
30
30
|
requirements = [
|
|
31
31
|
"httpx==0.27.0",
|
|
32
|
-
"orjson==3.
|
|
32
|
+
"orjson==3.10.0",
|
|
33
33
|
"packaging==24.0",
|
|
34
34
|
"xmltodict==0.13.0",
|
|
35
35
|
]
|
|
@@ -39,7 +39,7 @@ requirements_test = [
|
|
|
39
39
|
"pytest-aiohttp==1.0.5",
|
|
40
40
|
"pytest-asyncio==0.23.6",
|
|
41
41
|
"pytest-cov==5.0.0",
|
|
42
|
-
"respx==0.21.
|
|
42
|
+
"respx==0.21.1",
|
|
43
43
|
"ruff==0.3.4",
|
|
44
44
|
"types-orjson==3.6.2",
|
|
45
45
|
"types-xmltodict==v0.13.0.3",
|
|
@@ -174,6 +174,24 @@ async def test_get_light_information(respx_mock, light_control: LightHandler):
|
|
|
174
174
|
assert light.error_info == ""
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
async def test_get_light_information_error(respx_mock, light_control: LightHandler):
|
|
178
|
+
"""Test get light information API return error."""
|
|
179
|
+
respx_mock.post("/axis-cgi/lightcontrol.cgi").respond(
|
|
180
|
+
json={
|
|
181
|
+
"apiVersion": "1.1",
|
|
182
|
+
"context": "Axis library",
|
|
183
|
+
"method": "getLightInformation",
|
|
184
|
+
"error": {
|
|
185
|
+
"code": 1005,
|
|
186
|
+
"message": "No light hardware found, could not complete request.",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
response = await light_control.get_light_information()
|
|
192
|
+
assert len(response) == 0
|
|
193
|
+
|
|
194
|
+
|
|
177
195
|
async def test_activate_light(respx_mock, light_control):
|
|
178
196
|
"""Test activating light API."""
|
|
179
197
|
route = respx_mock.post("/axis-cgi/lightcontrol.cgi").respond(
|
|
@@ -183,7 +183,7 @@ async def test_get_client_status(respx_mock, mqtt_client: MqttClientHandler):
|
|
|
183
183
|
json=GET_CLIENT_STATUS_RESPONSE,
|
|
184
184
|
)
|
|
185
185
|
|
|
186
|
-
await mqtt_client.get_client_status()
|
|
186
|
+
client_status = await mqtt_client.get_client_status()
|
|
187
187
|
|
|
188
188
|
assert route.called
|
|
189
189
|
assert route.calls.last.request.method == "POST"
|
|
@@ -194,6 +194,9 @@ async def test_get_client_status(respx_mock, mqtt_client: MqttClientHandler):
|
|
|
194
194
|
"method": "getClientStatus",
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
assert client_status.status.active is True
|
|
198
|
+
assert client_status.status.connected is False
|
|
199
|
+
|
|
197
200
|
|
|
198
201
|
async def test_get_event_publication_config_small(
|
|
199
202
|
respx_mock, mqtt_client: MqttClientHandler
|
{axis-59 → axis-60}/LICENSE
RENAMED
|
File without changes
|
{axis-59 → axis-60}/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis-59 → axis-60}/axis/rtsp.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis-59 → axis-60}/setup.cfg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|