axis 70__tar.gz → 71__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-70 → axis-71}/PKG-INFO +2 -2
- {axis-70 → axis-71}/axis/interfaces/mqtt.py +2 -0
- {axis-70 → axis-71}/axis/models/event.py +27 -1
- {axis-70 → axis-71}/axis/websocket.py +2 -0
- {axis-70 → axis-71}/axis.egg-info/PKG-INFO +2 -2
- {axis-70 → axis-71}/axis.egg-info/requires.txt +1 -1
- {axis-70 → axis-71}/pyproject.toml +2 -2
- {axis-70 → axis-71}/tests/test_event.py +74 -0
- {axis-70 → axis-71}/tests/test_event_stream.py +26 -0
- {axis-70 → axis-71}/tests/test_mqtt.py +15 -0
- {axis-70 → axis-71}/tests/test_websocket.py +49 -0
- {axis-70 → axis-71}/LICENSE +0 -0
- {axis-70 → axis-71}/README.md +0 -0
- {axis-70 → axis-71}/axis/__init__.py +0 -0
- {axis-70 → axis-71}/axis/__main__.py +0 -0
- {axis-70 → axis-71}/axis/device.py +0 -0
- {axis-70 → axis-71}/axis/errors.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/__init__.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/aiohttp_digest.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/api_discovery.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/api_handler.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/applications/__init__.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/applications/application_handler.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/applications/applications.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/applications/fence_guard.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/applications/loitering_guard.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/applications/motion_guard.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/applications/object_analytics.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/applications/vmd4.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/basic_device_info.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/event_instances.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/event_manager.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/light_control.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/parameters/__init__.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/parameters/brand.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/parameters/image.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/parameters/io_port.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/parameters/param_cgi.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/parameters/param_handler.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/parameters/properties.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/parameters/ptz.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/parameters/stream_profile.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/pir_sensor_configuration.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/port_cgi.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/port_management.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/ptz.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/pwdgrp_cgi.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/stream_profiles.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/user_groups.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/vapix.py +0 -0
- {axis-70 → axis-71}/axis/interfaces/view_areas.py +0 -0
- {axis-70 → axis-71}/axis/models/__init__.py +0 -0
- {axis-70 → axis-71}/axis/models/api.py +0 -0
- {axis-70 → axis-71}/axis/models/api_discovery.py +0 -0
- {axis-70 → axis-71}/axis/models/applications/__init__.py +0 -0
- {axis-70 → axis-71}/axis/models/applications/application.py +0 -0
- {axis-70 → axis-71}/axis/models/applications/fence_guard.py +0 -0
- {axis-70 → axis-71}/axis/models/applications/loitering_guard.py +0 -0
- {axis-70 → axis-71}/axis/models/applications/motion_guard.py +0 -0
- {axis-70 → axis-71}/axis/models/applications/object_analytics.py +0 -0
- {axis-70 → axis-71}/axis/models/applications/vmd4.py +0 -0
- {axis-70 → axis-71}/axis/models/basic_device_info.py +0 -0
- {axis-70 → axis-71}/axis/models/configuration.py +0 -0
- {axis-70 → axis-71}/axis/models/event_instance.py +0 -0
- {axis-70 → axis-71}/axis/models/light_control.py +0 -0
- {axis-70 → axis-71}/axis/models/mqtt.py +0 -0
- {axis-70 → axis-71}/axis/models/parameters/__init__.py +0 -0
- {axis-70 → axis-71}/axis/models/parameters/brand.py +0 -0
- {axis-70 → axis-71}/axis/models/parameters/image.py +0 -0
- {axis-70 → axis-71}/axis/models/parameters/io_port.py +0 -0
- {axis-70 → axis-71}/axis/models/parameters/param_cgi.py +0 -0
- {axis-70 → axis-71}/axis/models/parameters/properties.py +0 -0
- {axis-70 → axis-71}/axis/models/parameters/ptz.py +0 -0
- {axis-70 → axis-71}/axis/models/parameters/stream_profile.py +0 -0
- {axis-70 → axis-71}/axis/models/pir_sensor_configuration.py +0 -0
- {axis-70 → axis-71}/axis/models/port_cgi.py +0 -0
- {axis-70 → axis-71}/axis/models/port_management.py +0 -0
- {axis-70 → axis-71}/axis/models/ptz_cgi.py +0 -0
- {axis-70 → axis-71}/axis/models/pwdgrp_cgi.py +0 -0
- {axis-70 → axis-71}/axis/models/stream_profile.py +0 -0
- {axis-70 → axis-71}/axis/models/user_group.py +0 -0
- {axis-70 → axis-71}/axis/models/view_area.py +0 -0
- {axis-70 → axis-71}/axis/py.typed +0 -0
- {axis-70 → axis-71}/axis/rtsp.py +0 -0
- {axis-70 → axis-71}/axis/stream_manager.py +0 -0
- {axis-70 → axis-71}/axis/stream_transport.py +0 -0
- {axis-70 → axis-71}/axis.egg-info/SOURCES.txt +0 -0
- {axis-70 → axis-71}/axis.egg-info/dependency_links.txt +0 -0
- {axis-70 → axis-71}/axis.egg-info/entry_points.txt +0 -0
- {axis-70 → axis-71}/axis.egg-info/top_level.txt +0 -0
- {axis-70 → axis-71}/setup.cfg +0 -0
- {axis-70 → axis-71}/tests/test_api_discovery.py +0 -0
- {axis-70 → axis-71}/tests/test_api_handler.py +0 -0
- {axis-70 → axis-71}/tests/test_auth_scheme.py +0 -0
- {axis-70 → axis-71}/tests/test_basic_device_info.py +0 -0
- {axis-70 → axis-71}/tests/test_configuration.py +0 -0
- {axis-70 → axis-71}/tests/test_conftest.py +0 -0
- {axis-70 → axis-71}/tests/test_device.py +0 -0
- {axis-70 → axis-71}/tests/test_event_instances.py +0 -0
- {axis-70 → axis-71}/tests/test_http_client_compat.py +0 -0
- {axis-70 → axis-71}/tests/test_light_control.py +0 -0
- {axis-70 → axis-71}/tests/test_main_http_client.py +0 -0
- {axis-70 → axis-71}/tests/test_pir_sensor_configuration.py +0 -0
- {axis-70 → axis-71}/tests/test_port_cgi.py +0 -0
- {axis-70 → axis-71}/tests/test_port_management.py +0 -0
- {axis-70 → axis-71}/tests/test_ptz.py +0 -0
- {axis-70 → axis-71}/tests/test_pwdgrp_cgi.py +0 -0
- {axis-70 → axis-71}/tests/test_rtsp.py +0 -0
- {axis-70 → axis-71}/tests/test_stream_manager.py +0 -0
- {axis-70 → axis-71}/tests/test_stream_profiles.py +0 -0
- {axis-70 → axis-71}/tests/test_user_groups.py +0 -0
- {axis-70 → axis-71}/tests/test_vapix.py +0 -0
- {axis-70 → axis-71}/tests/test_view_areas.py +0 -0
{axis-70 → axis-71}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axis
|
|
3
|
-
Version:
|
|
3
|
+
Version: 71
|
|
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
|
|
@@ -34,7 +34,7 @@ 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
36
|
Requires-Dist: ruff==0.15.12; extra == "requirements-test"
|
|
37
|
-
Requires-Dist: types-xmltodict==v1.0.1.
|
|
37
|
+
Requires-Dist: types-xmltodict==v1.0.1.20260508; 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
|
|
@@ -42,6 +42,8 @@ def mqtt_json_to_event(msg: bytes | bytearray | memoryview | str) -> dict[str, A
|
|
|
42
42
|
source, source_idx = next(iter(source_dict.items()))
|
|
43
43
|
if data_dict := msg_message.get("data"):
|
|
44
44
|
data_type, data_value = next(iter(data_dict.items()))
|
|
45
|
+
if "active" in data_dict:
|
|
46
|
+
data_type, data_value = "active", data_dict["active"]
|
|
45
47
|
|
|
46
48
|
return {
|
|
47
49
|
"topic": topic,
|
|
@@ -91,6 +91,9 @@ TOPIC_TO_STATE = {
|
|
|
91
91
|
EventTopic.RELAY: "active",
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
KNOWN_FALSE_STATES = {"", "0", "false", "inactive", "low", "off"}
|
|
95
|
+
KNOWN_TRUE_STATES = {"1", "active", "high", "on", "true"}
|
|
96
|
+
|
|
94
97
|
EVENT_OPERATION = "operation"
|
|
95
98
|
EVENT_SOURCE = "source"
|
|
96
99
|
EVENT_SOURCE_IDX = "source_idx"
|
|
@@ -138,6 +141,28 @@ def extract_name_value(
|
|
|
138
141
|
return item.get("Name", ""), item.get("Value", "")
|
|
139
142
|
|
|
140
143
|
|
|
144
|
+
def is_tripped(value: object, topic_base: EventTopic, event_type: object) -> bool:
|
|
145
|
+
"""Return whether an event value should be considered active/tripped."""
|
|
146
|
+
if (expected_state := TOPIC_TO_STATE.get(topic_base)) is not None:
|
|
147
|
+
return str(value) == expected_state
|
|
148
|
+
|
|
149
|
+
value_text = str(value).strip()
|
|
150
|
+
state = value_text.casefold()
|
|
151
|
+
|
|
152
|
+
if state in KNOWN_FALSE_STATES:
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
if state in KNOWN_TRUE_STATES:
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
if str(event_type).casefold() == "active":
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
# Non-empty semantic values (for example classification values like "human")
|
|
162
|
+
# are treated as stateless event triggers.
|
|
163
|
+
return bool(value_text)
|
|
164
|
+
|
|
165
|
+
|
|
141
166
|
@dataclass
|
|
142
167
|
class Event:
|
|
143
168
|
"""Event data from Axis device."""
|
|
@@ -166,6 +191,7 @@ class Event:
|
|
|
166
191
|
topic = data.get(EVENT_TOPIC, "")
|
|
167
192
|
source = data.get(EVENT_SOURCE, "")
|
|
168
193
|
source_idx = data.get(EVENT_SOURCE_IDX, "")
|
|
194
|
+
event_type = data.get(EVENT_TYPE, "")
|
|
169
195
|
value = data.get(EVENT_VALUE, "")
|
|
170
196
|
|
|
171
197
|
if (topic_base := EventTopic(topic)) is EventTopic.UNKNOWN:
|
|
@@ -181,7 +207,7 @@ class Event:
|
|
|
181
207
|
data=data,
|
|
182
208
|
group=TOPIC_TO_GROUP.get(topic_base, EventGroup.NONE),
|
|
183
209
|
id=source_idx,
|
|
184
|
-
is_tripped=value
|
|
210
|
+
is_tripped=is_tripped(value, topic_base, event_type),
|
|
185
211
|
operation=operation,
|
|
186
212
|
source=source,
|
|
187
213
|
state=value,
|
|
@@ -113,6 +113,8 @@ def _parse_ws_notification(notification: dict[str, Any]) -> dict[str, Any]:
|
|
|
113
113
|
|
|
114
114
|
source, source_idx = next(iter(source_dict.items()), ("", ""))
|
|
115
115
|
data_type, data_value = next(iter(data_dict.items()), ("", ""))
|
|
116
|
+
if "active" in data_dict:
|
|
117
|
+
data_type, data_value = "active", data_dict["active"]
|
|
116
118
|
|
|
117
119
|
return {
|
|
118
120
|
EVENT_TOPIC: topic,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axis
|
|
3
|
-
Version:
|
|
3
|
+
Version: 71
|
|
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
|
|
@@ -34,7 +34,7 @@ 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
36
|
Requires-Dist: ruff==0.15.12; extra == "requirements-test"
|
|
37
|
-
Requires-Dist: types-xmltodict==v1.0.1.
|
|
37
|
+
Requires-Dist: types-xmltodict==v1.0.1.20260508; 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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "axis"
|
|
7
|
-
version = "
|
|
7
|
+
version = "71"
|
|
8
8
|
license = {text = "MIT"}
|
|
9
9
|
description = "A Python library for communicating with devices from Axis Communications"
|
|
10
10
|
readme = "README.md"
|
|
@@ -41,7 +41,7 @@ requirements-test = [
|
|
|
41
41
|
"pytest-asyncio==1.3.0",
|
|
42
42
|
"pytest-cov==7.1.0",
|
|
43
43
|
"ruff==0.15.12",
|
|
44
|
-
"types-xmltodict==v1.0.1.
|
|
44
|
+
"types-xmltodict==v1.0.1.20260508",
|
|
45
45
|
]
|
|
46
46
|
requirements-dev = [
|
|
47
47
|
"pre-commit==4.6.0"
|
|
@@ -365,3 +365,77 @@ def test_parse_event_xml(input: bytes, expected: dict):
|
|
|
365
365
|
def test_unknown_event_operation():
|
|
366
366
|
"""Verify unknown event operation is caught."""
|
|
367
367
|
assert EventOperation("") == EventOperation.UNKNOWN
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@pytest.mark.parametrize(
|
|
371
|
+
("event_data", "expected"),
|
|
372
|
+
[
|
|
373
|
+
(
|
|
374
|
+
{
|
|
375
|
+
"topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1",
|
|
376
|
+
"source": "",
|
|
377
|
+
"source_idx": "Device1Scenario1",
|
|
378
|
+
"type": "active",
|
|
379
|
+
"value": "0",
|
|
380
|
+
},
|
|
381
|
+
False,
|
|
382
|
+
),
|
|
383
|
+
(
|
|
384
|
+
{
|
|
385
|
+
"topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1",
|
|
386
|
+
"source": "",
|
|
387
|
+
"source_idx": "Device1Scenario1",
|
|
388
|
+
"type": "classType",
|
|
389
|
+
"value": "human",
|
|
390
|
+
},
|
|
391
|
+
True,
|
|
392
|
+
),
|
|
393
|
+
(
|
|
394
|
+
{
|
|
395
|
+
"topic": "tns1:Device/tnsaxis:Sensor/PIR",
|
|
396
|
+
"source": "sensor",
|
|
397
|
+
"source_idx": "0",
|
|
398
|
+
"type": "state",
|
|
399
|
+
"value": "0",
|
|
400
|
+
},
|
|
401
|
+
False,
|
|
402
|
+
),
|
|
403
|
+
(
|
|
404
|
+
{
|
|
405
|
+
"topic": "tns1:AudioSource/tnsaxis:TriggerLevel",
|
|
406
|
+
"source": "channel",
|
|
407
|
+
"source_idx": "1",
|
|
408
|
+
"type": "active",
|
|
409
|
+
"value": "high",
|
|
410
|
+
},
|
|
411
|
+
True,
|
|
412
|
+
),
|
|
413
|
+
(
|
|
414
|
+
{
|
|
415
|
+
"topic": "tns1:AudioSource/tnsaxis:TriggerLevel",
|
|
416
|
+
"source": "channel",
|
|
417
|
+
"source_idx": "1",
|
|
418
|
+
"type": "active",
|
|
419
|
+
"value": "unknown",
|
|
420
|
+
},
|
|
421
|
+
False,
|
|
422
|
+
),
|
|
423
|
+
(
|
|
424
|
+
{
|
|
425
|
+
"topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1",
|
|
426
|
+
"source": "",
|
|
427
|
+
"source_idx": "Device1Scenario1",
|
|
428
|
+
"type": "classType",
|
|
429
|
+
"value": " ",
|
|
430
|
+
},
|
|
431
|
+
False,
|
|
432
|
+
),
|
|
433
|
+
],
|
|
434
|
+
)
|
|
435
|
+
def test_decode_from_dict_type_aware_is_tripped(
|
|
436
|
+
event_data: dict[str, str], expected: bool
|
|
437
|
+
) -> None:
|
|
438
|
+
"""Verify state handling for binary and semantic event payloads."""
|
|
439
|
+
event = Event.decode(event_data)
|
|
440
|
+
|
|
441
|
+
assert event.is_tripped is expected
|
|
@@ -377,6 +377,32 @@ def test_mqtt_event(event_manager: EventManager, subscriber: Mock) -> None:
|
|
|
377
377
|
assert event.is_tripped
|
|
378
378
|
|
|
379
379
|
|
|
380
|
+
def test_mqtt_object_analytics_mixed_data_prefers_active(
|
|
381
|
+
event_manager: EventManager, subscriber: Mock
|
|
382
|
+
) -> None:
|
|
383
|
+
"""Verify object analytics event with semantic and active data uses active state."""
|
|
384
|
+
mqtt_event = {
|
|
385
|
+
"topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1",
|
|
386
|
+
"source": "device",
|
|
387
|
+
"source_idx": "1",
|
|
388
|
+
"type": "active",
|
|
389
|
+
"value": "0",
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
event_manager.handler(mqtt_event)
|
|
393
|
+
assert subscriber.call_count == 1
|
|
394
|
+
|
|
395
|
+
event: Event = subscriber.call_args[0][0]
|
|
396
|
+
assert (
|
|
397
|
+
event.topic
|
|
398
|
+
== "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1"
|
|
399
|
+
)
|
|
400
|
+
assert event.source == "device"
|
|
401
|
+
assert event.id == "1"
|
|
402
|
+
assert event.state == "0"
|
|
403
|
+
assert event.is_tripped is False
|
|
404
|
+
|
|
405
|
+
|
|
380
406
|
def test_unsupported_event(event_manager: EventManager, subscriber: Mock) -> None:
|
|
381
407
|
"""Verify that unsupported events aren't signalled to subscribers."""
|
|
382
408
|
event_manager.handler(GLOBAL_SCENE_CHANGE)
|
|
@@ -320,6 +320,21 @@ async def test_convert_json_to_event():
|
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
|
|
323
|
+
async def test_convert_json_to_event_prefers_active_value() -> None:
|
|
324
|
+
"""Verify conversion prefers binary active state when available."""
|
|
325
|
+
event = mqtt_json_to_event(
|
|
326
|
+
b'{"topic": "onvif:CameraApplicationPlatform/axis:ObjectAnalytics/Device1Scenario1", "message": {"source": {"device": "1"}, "data": {"classTypes": "human", "active": "0"}}}'
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
assert event == {
|
|
330
|
+
"topic": "tns1:CameraApplicationPlatform/tnsaxis:ObjectAnalytics/Device1Scenario1",
|
|
331
|
+
"source": "device",
|
|
332
|
+
"source_idx": "1",
|
|
333
|
+
"type": "active",
|
|
334
|
+
"value": "0",
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
|
|
323
338
|
def test_mqtt_json_to_event_missing_keys(caplog):
|
|
324
339
|
"""Test mqtt_json_to_event returns None and logs warning if keys are missing."""
|
|
325
340
|
# Missing 'topic'
|
|
@@ -185,6 +185,55 @@ async def test_websocket_stream_receives_data(axis_device):
|
|
|
185
185
|
)
|
|
186
186
|
|
|
187
187
|
|
|
188
|
+
async def test_websocket_stream_prefers_active_value(axis_device):
|
|
189
|
+
"""Verify websocket parsing prefers active when multiple data keys are present."""
|
|
190
|
+
callback = MagicMock()
|
|
191
|
+
notify_payload = orjson.dumps(
|
|
192
|
+
{
|
|
193
|
+
"apiVersion": "1.0",
|
|
194
|
+
"method": "events:notify",
|
|
195
|
+
"params": {
|
|
196
|
+
"notification": {
|
|
197
|
+
"timestamp": "2024-01-01T00:00:00Z",
|
|
198
|
+
"topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1",
|
|
199
|
+
"message": {
|
|
200
|
+
"source": {"device": "1"},
|
|
201
|
+
"key": {},
|
|
202
|
+
"data": {"classTypes": "human", "active": "0"},
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
ws = MockWebSocket(
|
|
209
|
+
_configure_ok_msg(),
|
|
210
|
+
[
|
|
211
|
+
SimpleNamespace(type=aiohttp.WSMsgType.TEXT, data=notify_payload.decode()),
|
|
212
|
+
SimpleNamespace(type=aiohttp.WSMsgType.CLOSED, data=None),
|
|
213
|
+
],
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
axis_device.vapix.request = AsyncMock(return_value=b"token123")
|
|
217
|
+
ws_connect = AsyncMock(return_value=ws)
|
|
218
|
+
|
|
219
|
+
with patch.object(axis_device.config.session, "ws_connect", ws_connect):
|
|
220
|
+
client = WebSocketClient(
|
|
221
|
+
axis_device,
|
|
222
|
+
"ws://127.0.0.1:80/vapix/ws-data-stream?sources=events",
|
|
223
|
+
callback,
|
|
224
|
+
)
|
|
225
|
+
await client.start()
|
|
226
|
+
await client._receiver_task
|
|
227
|
+
|
|
228
|
+
assert client.data == {
|
|
229
|
+
"topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1",
|
|
230
|
+
"source": "device",
|
|
231
|
+
"source_idx": "1",
|
|
232
|
+
"type": "active",
|
|
233
|
+
"value": "0",
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
188
237
|
async def test_websocket_configure_failure(axis_device):
|
|
189
238
|
"""Verify websocket client reports failed configure."""
|
|
190
239
|
callback = MagicMock()
|
{axis-70 → axis-71}/LICENSE
RENAMED
|
File without changes
|
{axis-70 → axis-71}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis-70 → axis-71}/axis/rtsp.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{axis-70 → axis-71}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|