diff --git a/axis/interfaces/mqtt.py b/axis/interfaces/mqtt.py index 2a3fa834..b1d5d5c2 100644 --- a/axis/interfaces/mqtt.py +++ b/axis/interfaces/mqtt.py @@ -2,8 +2,6 @@ from typing import Any -import orjson - from ..models.api_discovery import ApiId from ..models.mqtt import ( API_VERSION, @@ -25,28 +23,6 @@ DEFAULT_TOPICS = ["//."] -def mqtt_json_to_event(msg: bytes | str) -> dict[str, Any]: - """Convert JSON message from MQTT to event format.""" - message = orjson.loads(msg) - topic = message["topic"].replace("onvif", "tns1").replace("axis", "tnsaxis") - - source = source_idx = "" - if message["message"]["source"]: - source, source_idx = next(iter(message["message"]["source"].items())) - - data_type = data_value = "" - if message["message"]["data"]: - data_type, data_value = next(iter(message["message"]["data"].items())) - - return { - "topic": topic, - "source": source, - "source_idx": source_idx, - "type": data_type, - "value": data_value, - } - - class MqttClientHandler(ApiHandler[Any]): """MQTT Client for Axis devices.""" diff --git a/axis/models/event.py b/axis/models/event.py index 61e3d139..4598f8c5 100644 --- a/axis/models/event.py +++ b/axis/models/event.py @@ -10,18 +10,6 @@ LOGGER = logging.getLogger(__name__) -class EventGroup(enum.StrEnum): - """Logical grouping of events.""" - - INPUT = "input" - LIGHT = "light" - MOTION = "motion" - OUTPUT = "output" - PTZ = "ptz" - SOUND = "sound" - NONE = "none" - - class EventOperation(enum.StrEnum): """Possible operations of an event.""" @@ -41,22 +29,22 @@ def _missing_(cls, value: object) -> "EventOperation": class EventTopic(enum.StrEnum): """Supported event topics.""" - DAY_NIGHT_VISION = "tns1:VideoSource/tnsaxis:DayNightVision" - FENCE_GUARD = "tnsaxis:CameraApplicationPlatform/FenceGuard" - LIGHT_STATUS = "tns1:Device/tnsaxis:Light/Status" - LOITERING_GUARD = "tnsaxis:CameraApplicationPlatform/LoiteringGuard" - MOTION_DETECTION = "tns1:VideoAnalytics/tnsaxis:MotionDetection" - MOTION_DETECTION_3 = "tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_1" - MOTION_DETECTION_4 = "tnsaxis:CameraApplicationPlatform/VMD" - MOTION_GUARD = "tnsaxis:CameraApplicationPlatform/MotionGuard" - OBJECT_ANALYTICS = "tnsaxis:CameraApplicationPlatform/ObjectAnalytics" - PIR = "tns1:Device/tnsaxis:Sensor/PIR" - PORT_INPUT = "tns1:Device/tnsaxis:IO/Port" - PORT_SUPERVISED_INPUT = "tns1:Device/tnsaxis:IO/SupervisedPort" - PTZ_IS_MOVING = "tns1:PTZController/tnsaxis:Move" - PTZ_ON_PRESET = "tns1:PTZController/tnsaxis:PTZPresets" - RELAY = "tns1:Device/Trigger/Relay" - SOUND_TRIGGER_LEVEL = "tns1:AudioSource/tnsaxis:TriggerLevel" + DAY_NIGHT_VISION = "onvif:VideoSource/axis:DayNightVision" + FENCE_GUARD = "axis:CameraApplicationPlatform/FenceGuard" + LIGHT_STATUS = "onvif:Device/axis:Light/Status" + LOITERING_GUARD = "axis:CameraApplicationPlatform/LoiteringGuard" + MOTION_DETECTION = "onvif:VideoAnalytics/axis:MotionDetection" + MOTION_DETECTION_3 = "onvif:RuleEngine/axis:VMD3/vmd3_video_1" + MOTION_DETECTION_4 = "axis:CameraApplicationPlatform/VMD" + MOTION_GUARD = "axis:CameraApplicationPlatform/MotionGuard" + OBJECT_ANALYTICS = "axis:CameraApplicationPlatform/ObjectAnalytics" + PIR = "onvif:Device/axis:Sensor/PIR" + PORT_INPUT = "onvif:Device/axis:IO/Port" + PORT_SUPERVISED_INPUT = "onvif:Device/axis:IO/SupervisedPort" + PTZ_IS_MOVING = "onvif:PTZController/axis:Move" + PTZ_ON_PRESET = "onvif:PTZController/axis:PTZPresets" + RELAY = "onvif:Device/Trigger/Relay" + SOUND_TRIGGER_LEVEL = "onvif:AudioSource/axis:TriggerLevel" UNKNOWN = "unknown" @classmethod @@ -67,25 +55,6 @@ def _missing_(cls, value: object) -> "EventTopic": return EventTopic.UNKNOWN -TOPIC_TO_GROUP = { - EventTopic.DAY_NIGHT_VISION: EventGroup.LIGHT, - EventTopic.FENCE_GUARD: EventGroup.MOTION, - EventTopic.LIGHT_STATUS: EventGroup.LIGHT, - EventTopic.LOITERING_GUARD: EventGroup.MOTION, - EventTopic.MOTION_DETECTION: EventGroup.MOTION, - EventTopic.MOTION_DETECTION_3: EventGroup.MOTION, - EventTopic.MOTION_DETECTION_4: EventGroup.MOTION, - EventTopic.MOTION_GUARD: EventGroup.MOTION, - EventTopic.OBJECT_ANALYTICS: EventGroup.MOTION, - EventTopic.PIR: EventGroup.MOTION, - EventTopic.PORT_INPUT: EventGroup.INPUT, - EventTopic.PORT_SUPERVISED_INPUT: EventGroup.INPUT, - EventTopic.PTZ_IS_MOVING: EventGroup.PTZ, - EventTopic.PTZ_ON_PRESET: EventGroup.PTZ, - EventTopic.RELAY: EventGroup.OUTPUT, - EventTopic.SOUND_TRIGGER_LEVEL: EventGroup.SOUND, -} - TOPIC_TO_STATE = { EventTopic.LIGHT_STATUS: "ON", EventTopic.RELAY: "active", @@ -102,8 +71,8 @@ def _missing_(cls, value: object) -> "EventTopic": NOTIFICATION_MESSAGE = ("MetadataStream", "Event", "NotificationMessage") MESSAGE = (*NOTIFICATION_MESSAGE, "Message", "Message") TOPIC = (*NOTIFICATION_MESSAGE, "Topic", "#text") -TIMESTAMP = (*MESSAGE, "@UtcTime") -OPERATION = (*MESSAGE, "@PropertyOperation") +TIMESTAMP = (*MESSAGE, "UtcTime") +OPERATION = (*MESSAGE, "PropertyOperation") SOURCE = (*MESSAGE, "Source") DATA = (*MESSAGE, "Data") @@ -128,16 +97,13 @@ def extract_name_value( item = data.get("SimpleItem", {}) if isinstance(item, list): item = item[0] - return item.get("@Name", ""), item.get("@Value", "") - # return item.get("Name", ""), item.get("Value", "") + return item.get("Name", ""), item.get("Value", "") @dataclass class Event: """Event data from Axis device.""" - data: dict[str, Any] - group: EventGroup id: str is_tripped: bool operation: EventOperation @@ -145,6 +111,7 @@ class Event: state: str topic: str topic_base: EventTopic + data: dict[str, Any] @classmethod def decode(cls, data: bytes | dict[str, Any]) -> Self: @@ -162,18 +129,16 @@ def _decode_from_dict(cls, data: dict[str, Any]) -> Self: source_idx = data.get(EVENT_SOURCE_IDX, "") value = data.get(EVENT_VALUE, "") - if (topic_base := EventTopic(topic)) == EventTopic.UNKNOWN: + if (topic_base := EventTopic(topic)) is EventTopic.UNKNOWN: _topic_base, _, _source_idx = topic.rpartition("/") topic_base = EventTopic(_topic_base) if source_idx == "": source_idx = _source_idx if source_idx == "-1": - source_idx = "ANY" if source != "port" else "" + source_idx = "ANY" return cls( - data=data, - group=TOPIC_TO_GROUP.get(topic_base, EventGroup.NONE), id=source_idx, is_tripped=value == TOPIC_TO_STATE.get(topic_base, "1"), operation=operation, @@ -181,6 +146,7 @@ def _decode_from_dict(cls, data: dict[str, Any]) -> Self: state=value, topic=topic, topic_base=topic_base, + data=data, ) @classmethod @@ -188,7 +154,7 @@ def _decode_from_bytes(cls, data: bytes) -> Self: """Parse metadata xml.""" raw = xmltodict.parse( data, - # attr_prefix="", + attr_prefix="", process_namespaces=True, namespaces=XML_NAMESPACES, ) @@ -196,7 +162,11 @@ def _decode_from_bytes(cls, data: bytes) -> Self: if raw.get("MetadataStream") is None: return cls._decode_from_dict({}) - topic = traverse(raw, TOPIC) + topic = ( + str(traverse(raw, TOPIC)) + .replace("tns1", "onvif") + .replace("tnsaxis", "axis") + ) # timestamp = traverse(raw, TIMESTAMP) operation = traverse(raw, OPERATION) diff --git a/axis/models/mqtt.py b/axis/models/mqtt.py index 5e765b16..b27d21b2 100644 --- a/axis/models/mqtt.py +++ b/axis/models/mqtt.py @@ -4,16 +4,44 @@ from dataclasses import dataclass import enum -from typing import Literal, NotRequired, Self +from typing import Any, Literal, NotRequired, Self import orjson from typing_extensions import TypedDict from .api import CONTEXT, ApiRequest, ApiResponse +from .event import ( + EVENT_SOURCE, + EVENT_SOURCE_IDX, + EVENT_TOPIC, + EVENT_TYPE, + EVENT_VALUE, +) API_VERSION = "1.0" +def mqtt_json_to_event(msg: bytes | str) -> dict[str, Any]: + """Convert JSON message from MQTT to event format.""" + message = orjson.loads(msg) + + source = source_idx = "" + if message["message"]["source"]: + source, source_idx = next(iter(message["message"]["source"].items())) + + data_type = data_value = "" + if message["message"]["data"]: + data_type, data_value = next(iter(message["message"]["data"].items())) + + return { + EVENT_TOPIC: message["topic"], + EVENT_SOURCE: source, + EVENT_SOURCE_IDX: source_idx, + EVENT_TYPE: data_type, + EVENT_VALUE: data_value, + } + + class ErrorDataT(TypedDict): """Error data in response.""" diff --git a/tests/test_event.py b/tests/test_event.py index 06f2c690..ab159ee7 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -7,7 +7,7 @@ import pytest -from axis.models.event import Event, EventGroup, EventOperation +from axis.models.event import Event, EventOperation from .event_fixtures import ( AUDIO_INIT, @@ -43,7 +43,6 @@ "topic": "", "source": "", "source_idx": "", - "group": EventGroup.NONE, "type": "", "state": "", "tripped": False, @@ -52,10 +51,9 @@ ( AUDIO_INIT, { - "topic": "tns1:AudioSource/tnsaxis:TriggerLevel", + "topic": "onvif:AudioSource/axis:TriggerLevel", "source": "channel", "source_idx": "1", - "group": EventGroup.SOUND, "type": "Sound", "state": "0", "tripped": False, @@ -64,10 +62,9 @@ ( DAYNIGHT_INIT, { - "topic": "tns1:VideoSource/tnsaxis:DayNightVision", + "topic": "onvif:VideoSource/axis:DayNightVision", "source": "VideoSourceConfigurationToken", "source_idx": "1", - "group": EventGroup.LIGHT, "type": "DayNight", "state": "1", "tripped": True, @@ -76,10 +73,9 @@ ( FENCE_GUARD_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/FenceGuard/Camera1Profile1", + "topic": "axis:CameraApplicationPlatform/FenceGuard/Camera1Profile1", "source": "", "source_idx": "Camera1Profile1", - "group": EventGroup.MOTION, "type": "Fence Guard", "state": "0", "tripped": False, @@ -88,10 +84,9 @@ ( LIGHT_STATUS_INIT, { - "topic": "tns1:Device/tnsaxis:Light/Status", + "topic": "onvif:Device/axis:Light/Status", "source": "id", "source_idx": "0", - "group": EventGroup.LIGHT, "type": "Light", "state": "OFF", "tripped": False, @@ -100,10 +95,9 @@ ( LOITERING_GUARD_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/LoiteringGuard/Camera1Profile1", + "topic": "axis:CameraApplicationPlatform/LoiteringGuard/Camera1Profile1", "source": "", "source_idx": "Camera1Profile1", - "group": EventGroup.MOTION, "type": "Loitering Guard", "state": "0", "tripped": False, @@ -112,10 +106,9 @@ ( MOTION_GUARD_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/MotionGuard/Camera1ProfileANY", + "topic": "axis:CameraApplicationPlatform/MotionGuard/Camera1ProfileANY", "source": "", "source_idx": "Camera1ProfileANY", - "group": EventGroup.MOTION, "type": "Motion Guard", "state": "0", "tripped": False, @@ -124,10 +117,9 @@ ( OBJECT_ANALYTICS_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1", + "topic": "axis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1", "source": "", "source_idx": "Device1Scenario1", - "group": EventGroup.MOTION, "type": "Object Analytics", "state": "0", "tripped": False, @@ -136,10 +128,9 @@ ( PIR_INIT, { - "topic": "tns1:Device/tnsaxis:Sensor/PIR", + "topic": "onvif:Device/axis:Sensor/PIR", "source": "sensor", "source_idx": "0", - "group": EventGroup.MOTION, "type": "PIR", "state": "0", "tripped": False, @@ -148,10 +139,9 @@ ( PORT_0_INIT, { - "topic": "tns1:Device/tnsaxis:IO/Port", + "topic": "onvif:Device/axis:IO/Port", "source": "port", "source_idx": "1", - "group": EventGroup.INPUT, "type": "Input", "state": "0", "tripped": False, @@ -160,10 +150,9 @@ ( PORT_ANY_INIT, { - "topic": "tns1:Device/tnsaxis:IO/Port", + "topic": "onvif:Device/axis:IO/Port", "source": "port", - "source_idx": "", - "group": EventGroup.INPUT, + "source_idx": "ANY", "type": "Input", "state": "0", "tripped": False, @@ -172,10 +161,9 @@ ( PTZ_MOVE_INIT, { - "topic": "tns1:PTZController/tnsaxis:Move/Channel_1", + "topic": "onvif:PTZController/axis:Move/Channel_1", "source": "PTZConfigurationToken", "source_idx": "1", - "group": EventGroup.PTZ, "type": "is_moving", "state": "0", "tripped": False, @@ -184,10 +172,9 @@ ( PTZ_PRESET_INIT_1, { - "topic": "tns1:PTZController/tnsaxis:PTZPresets/Channel_1", + "topic": "onvif:PTZController/axis:PTZPresets/Channel_1", "source": "PresetToken", "source_idx": "1", - "group": EventGroup.PTZ, "type": "on_preset", "state": "1", "tripped": True, @@ -196,10 +183,9 @@ ( RELAY_INIT, { - "topic": "tns1:Device/Trigger/Relay", + "topic": "onvif:Device/Trigger/Relay", "source": "RelayToken", "source_idx": "3", - "group": EventGroup.OUTPUT, "type": "Relay", "state": "inactive", "tripped": False, @@ -208,10 +194,9 @@ ( VMD3_INIT, { - "topic": "tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_1", + "topic": "onvif:RuleEngine/axis:VMD3/vmd3_video_1", "source": "areaid", "source_idx": "0", - "group": EventGroup.MOTION, "type": "VMD3", "state": "0", "tripped": False, @@ -220,10 +205,9 @@ ( VMD4_ANY_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1ProfileANY", + "topic": "axis:CameraApplicationPlatform/VMD/Camera1ProfileANY", "source": "", "source_idx": "Camera1ProfileANY", - "group": EventGroup.MOTION, "type": "VMD4", "state": "0", "tripped": False, @@ -233,10 +217,9 @@ ( GLOBAL_SCENE_CHANGE, { - "topic": "tns1:VideoSource/GlobalSceneChange/ImagingService", + "topic": "onvif:VideoSource/GlobalSceneChange/ImagingService", "source": "Source", "source_idx": "0", - "group": EventGroup.NONE, "type": "VMD4", "state": "0", "tripped": False, @@ -251,7 +234,6 @@ def test_create_event(input: bytes, expected: tuple) -> None: assert event.topic == expected["topic"] assert event.source == expected["source"] assert event.id == expected["source_idx"] - assert event.group == expected["group"] assert event.state == expected["state"] assert event.is_tripped is expected["tripped"] @@ -267,7 +249,7 @@ def test_create_event(input: bytes, expected: tuple) -> None: PIR_INIT, { "operation": "Initialized", - "topic": "tns1:Device/tnsaxis:Sensor/PIR", + "topic": "onvif:Device/axis:Sensor/PIR", "source": "sensor", "source_idx": "0", "type": "state", @@ -278,7 +260,7 @@ def test_create_event(input: bytes, expected: tuple) -> None: PIR_CHANGE, { "operation": "Changed", - "topic": "tns1:Device/tnsaxis:Sensor/PIR", + "topic": "onvif:Device/axis:Sensor/PIR", "source": "sensor", "source_idx": "0", "type": "state", @@ -291,7 +273,7 @@ def test_create_event(input: bytes, expected: tuple) -> None: "operation": "Initialized", "source": "VideoSource", "source_idx": "0", - "topic": "tns1:RuleEngine/MotionRegionDetector/Motion", + "topic": "onvif:RuleEngine/MotionRegionDetector/Motion", "type": "State", "value": "0", }, @@ -302,7 +284,7 @@ def test_create_event(input: bytes, expected: tuple) -> None: "operation": "Initialized", "source": "disk_id", "source_idx": "NetworkShare", - "topic": "tnsaxis:Storage/Alert", + "topic": "axis:Storage/Alert", "type": "overall_health", "value": "-3", }, @@ -311,7 +293,7 @@ def test_create_event(input: bytes, expected: tuple) -> None: VMD4_ANY_INIT, { "operation": "Initialized", - "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1ProfileANY", + "topic": "axis:CameraApplicationPlatform/VMD/Camera1ProfileANY", "source": "", "source_idx": "", "type": "active", @@ -322,7 +304,7 @@ def test_create_event(input: bytes, expected: tuple) -> None: VMD4_ANY_CHANGE, { "operation": "Changed", - "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1ProfileANY", + "topic": "axis:CameraApplicationPlatform/VMD/Camera1ProfileANY", "source": "", "source_idx": "", "type": "active", diff --git a/tests/test_event_stream.py b/tests/test_event_stream.py index 3e5f3a43..74bc1d85 100644 --- a/tests/test_event_stream.py +++ b/tests/test_event_stream.py @@ -9,7 +9,7 @@ from axis.device import AxisDevice from axis.interfaces.event_manager import EventManager -from axis.models.event import Event, EventGroup, EventOperation, EventTopic +from axis.models.event import Event, EventOperation, EventTopic from .event_fixtures import ( AUDIO_INIT, @@ -63,10 +63,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( AUDIO_INIT, { - "topic": "tns1:AudioSource/tnsaxis:TriggerLevel", + "topic": "onvif:AudioSource/axis:TriggerLevel", "source": "channel", "source_idx": "1", - "group": EventGroup.SOUND, "type": "Sound", "state": "0", "tripped": False, @@ -75,10 +74,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( DAYNIGHT_INIT, { - "topic": "tns1:VideoSource/tnsaxis:DayNightVision", + "topic": "onvif:VideoSource/axis:DayNightVision", "source": "VideoSourceConfigurationToken", "source_idx": "1", - "group": EventGroup.LIGHT, "type": "DayNight", "state": "1", "tripped": True, @@ -87,10 +85,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( FENCE_GUARD_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/FenceGuard/Camera1Profile1", + "topic": "axis:CameraApplicationPlatform/FenceGuard/Camera1Profile1", "source": "", "source_idx": "Camera1Profile1", - "group": EventGroup.MOTION, "type": "Fence Guard", "state": "0", "tripped": False, @@ -99,10 +96,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( LIGHT_STATUS_INIT, { - "topic": "tns1:Device/tnsaxis:Light/Status", + "topic": "onvif:Device/axis:Light/Status", "source": "id", "source_idx": "0", - "group": EventGroup.LIGHT, "type": "Light", "state": "OFF", "tripped": False, @@ -111,10 +107,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( LOITERING_GUARD_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/LoiteringGuard/Camera1Profile1", + "topic": "axis:CameraApplicationPlatform/LoiteringGuard/Camera1Profile1", "source": "", "source_idx": "Camera1Profile1", - "group": EventGroup.MOTION, "type": "Loitering Guard", "state": "0", "tripped": False, @@ -123,10 +118,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( MOTION_GUARD_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/MotionGuard/Camera1ProfileANY", + "topic": "axis:CameraApplicationPlatform/MotionGuard/Camera1ProfileANY", "source": "", "source_idx": "Camera1ProfileANY", - "group": EventGroup.MOTION, "type": "Motion Guard", "state": "0", "tripped": False, @@ -135,10 +129,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( OBJECT_ANALYTICS_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1", + "topic": "axis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1", "source": "", "source_idx": "Device1Scenario1", - "group": EventGroup.MOTION, "type": "Object Analytics", "state": "0", "tripped": False, @@ -147,10 +140,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( PIR_INIT, { - "topic": "tns1:Device/tnsaxis:Sensor/PIR", + "topic": "onvif:Device/axis:Sensor/PIR", "source": "sensor", "source_idx": "0", - "group": EventGroup.MOTION, "type": "PIR", "state": "0", "tripped": False, @@ -159,10 +151,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( PORT_0_INIT, { - "topic": "tns1:Device/tnsaxis:IO/Port", + "topic": "onvif:Device/axis:IO/Port", "source": "port", "source_idx": "1", - "group": EventGroup.INPUT, "type": "Input", "state": "0", "tripped": False, @@ -171,10 +162,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( PORT_ANY_INIT, { - "topic": "tns1:Device/tnsaxis:IO/Port", + "topic": "onvif:Device/axis:IO/Port", "source": "port", - "source_idx": "", - "group": EventGroup.INPUT, + "source_idx": "ANY", "type": "Input", "state": "0", "tripped": False, @@ -183,10 +173,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( PTZ_MOVE_INIT, { - "topic": "tns1:PTZController/tnsaxis:Move/Channel_1", + "topic": "onvif:PTZController/axis:Move/Channel_1", "source": "PTZConfigurationToken", "source_idx": "1", - "group": EventGroup.PTZ, "type": "is_moving", "state": "0", "tripped": False, @@ -195,10 +184,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( PTZ_PRESET_INIT_1, { - "topic": "tns1:PTZController/tnsaxis:PTZPresets/Channel_1", + "topic": "onvif:PTZController/axis:PTZPresets/Channel_1", "source": "PresetToken", "source_idx": "1", - "group": EventGroup.PTZ, "type": "on_preset", "state": "1", "tripped": True, @@ -207,10 +195,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( RELAY_INIT, { - "topic": "tns1:Device/Trigger/Relay", + "topic": "onvif:Device/Trigger/Relay", "source": "RelayToken", "source_idx": "3", - "group": EventGroup.OUTPUT, "type": "Relay", "state": "inactive", "tripped": False, @@ -219,10 +206,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( VMD3_INIT, { - "topic": "tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_1", + "topic": "onvif:RuleEngine/axis:VMD3/vmd3_video_1", "source": "areaid", "source_idx": "0", - "group": EventGroup.MOTION, "type": "VMD3", "state": "0", "tripped": False, @@ -231,10 +217,9 @@ def subscriber(event_manager: EventManager) -> Mock: ( VMD4_ANY_INIT, { - "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1ProfileANY", + "topic": "axis:CameraApplicationPlatform/VMD/Camera1ProfileANY", "source": "", "source_idx": "Camera1ProfileANY", - "group": EventGroup.MOTION, "type": "VMD4", "state": "0", "tripped": False, @@ -253,7 +238,6 @@ def test_create_event( assert event.topic == expected["topic"] assert event.source == expected["source"] assert event.id == expected["source_idx"] - assert event.group == expected["group"] assert event.state == expected["state"] assert event.is_tripped is expected["tripped"] @@ -279,7 +263,7 @@ def test_ptz_preset(event_manager: EventManager, subscriber: Mock) -> None: assert subscriber.call_count == 1 event: Event = subscriber.call_args[0][0] - assert event.topic == "tns1:PTZController/tnsaxis:PTZPresets/Channel_1" + assert event.topic == "onvif:PTZController/axis:PTZPresets/Channel_1" assert event.id == "1" assert event.state == "1" @@ -287,7 +271,7 @@ def test_ptz_preset(event_manager: EventManager, subscriber: Mock) -> None: assert subscriber.call_count == 2 event: Event = subscriber.call_args[0][0] - assert event.topic == "tns1:PTZController/tnsaxis:PTZPresets/Channel_1" + assert event.topic == "onvif:PTZController/axis:PTZPresets/Channel_1" assert event.id == "2" assert event.state == "0" @@ -295,7 +279,7 @@ def test_ptz_preset(event_manager: EventManager, subscriber: Mock) -> None: assert subscriber.call_count == 3 event: Event = subscriber.call_args[0][0] - assert event.topic == "tns1:PTZController/tnsaxis:PTZPresets/Channel_1" + assert event.topic == "onvif:PTZController/axis:PTZPresets/Channel_1" assert event.id == "3" assert event.state == "0" @@ -320,17 +304,16 @@ def test_ptz_move(event_manager: EventManager, subscriber: Mock) -> None: assert subscriber.call_count == 1 event: Event = subscriber.call_args[0][0] - assert event.topic == "tns1:PTZController/tnsaxis:Move/Channel_1" + assert event.topic == "onvif:PTZController/axis:Move/Channel_1" assert event.source == "PTZConfigurationToken" assert event.id == "1" - assert event.group == EventGroup.PTZ assert event.state == "0" event_manager.handler(PTZ_MOVE_START) assert subscriber.call_count == 2 event: Event = subscriber.call_args[0][0] - assert event.topic == "tns1:PTZController/tnsaxis:Move/Channel_1" + assert event.topic == "onvif:PTZController/axis:Move/Channel_1" assert event.id == "1" assert event.state == "1" assert event.is_tripped @@ -339,7 +322,7 @@ def test_ptz_move(event_manager: EventManager, subscriber: Mock) -> None: assert subscriber.call_count == 3 event: Event = subscriber.call_args[0][0] - assert event.topic == "tns1:PTZController/tnsaxis:Move/Channel_1" + assert event.topic == "onvif:PTZController/axis:Move/Channel_1" assert event.id == "1" assert event.state == "0" assert not event.is_tripped @@ -348,7 +331,7 @@ def test_ptz_move(event_manager: EventManager, subscriber: Mock) -> None: def test_mqtt_event(event_manager: EventManager, subscriber: Mock) -> None: """Verify that unsupported events aren't signalled to subscribers.""" mqtt_event = { - "topic": "tns1:Device/tnsaxis:Sensor/PIR", + "topic": "onvif:Device/axis:Sensor/PIR", "source": "sensor", "source_idx": "0", "type": "state", @@ -359,7 +342,7 @@ def test_mqtt_event(event_manager: EventManager, subscriber: Mock) -> None: event: Event = subscriber.call_args[0][0] assert event.operation == EventOperation.INITIALIZED - assert event.topic == "tns1:Device/tnsaxis:Sensor/PIR" + assert event.topic == "onvif:Device/axis:Sensor/PIR" assert event.id == "0" assert event.state == "0" assert not event.is_tripped diff --git a/tests/test_mqtt.py b/tests/test_mqtt.py index 4d175b0b..17b418b6 100644 --- a/tests/test_mqtt.py +++ b/tests/test_mqtt.py @@ -9,8 +9,15 @@ import pytest from axis.device import AxisDevice -from axis.interfaces.mqtt import MqttClientHandler, mqtt_json_to_event -from axis.models.mqtt import ClientConfig, Message, Server, ServerProtocol, Ssl +from axis.interfaces.mqtt import MqttClientHandler +from axis.models.mqtt import ( + ClientConfig, + Message, + Server, + ServerProtocol, + Ssl, + mqtt_json_to_event, +) @pytest.fixture @@ -309,7 +316,7 @@ async def test_convert_json_to_event(): ) assert event == { - "topic": "tns1:Device/tnsaxis:Sensor/PIR", + "topic": "onvif:Device/axis:Sensor/PIR", "source": "sensor", "source_idx": "0", "type": "state",