mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Add support for onvif tplink person and vehicle events (#130769)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
de0ffea52d
commit
106c5d4248
@ -370,6 +370,63 @@ async def async_parse_vehicle_detector(uid: str, msg) -> Event | None:
|
||||
return None
|
||||
|
||||
|
||||
@PARSERS.register("tns1:RuleEngine/TPSmartEventDetector/TPSmartEvent")
|
||||
@PARSERS.register("tns1:RuleEngine/PeopleDetector/People")
|
||||
async def async_parse_tplink_detector(uid: str, msg) -> Event | None:
|
||||
"""Handle parsing tplink smart event messages.
|
||||
|
||||
Topic: tns1:RuleEngine/TPSmartEventDetector/TPSmartEvent
|
||||
Topic: tns1:RuleEngine/PeopleDetector/People
|
||||
"""
|
||||
video_source = ""
|
||||
video_analytics = ""
|
||||
rule = ""
|
||||
topic = ""
|
||||
vehicle = False
|
||||
person = False
|
||||
enabled = False
|
||||
try:
|
||||
topic, payload = extract_message(msg)
|
||||
for source in payload.Source.SimpleItem:
|
||||
if source.Name == "VideoSourceConfigurationToken":
|
||||
video_source = _normalize_video_source(source.Value)
|
||||
if source.Name == "VideoAnalyticsConfigurationToken":
|
||||
video_analytics = source.Value
|
||||
if source.Name == "Rule":
|
||||
rule = source.Value
|
||||
|
||||
for item in payload.Data.SimpleItem:
|
||||
if item.Name == "IsVehicle":
|
||||
vehicle = True
|
||||
enabled = item.Value == "true"
|
||||
if item.Name == "IsPeople":
|
||||
person = True
|
||||
enabled = item.Value == "true"
|
||||
except (AttributeError, KeyError):
|
||||
return None
|
||||
|
||||
if vehicle:
|
||||
return Event(
|
||||
f"{uid}_{topic}_{video_source}_{video_analytics}_{rule}",
|
||||
"Vehicle Detection",
|
||||
"binary_sensor",
|
||||
"motion",
|
||||
None,
|
||||
enabled,
|
||||
)
|
||||
if person:
|
||||
return Event(
|
||||
f"{uid}_{topic}_{video_source}_{video_analytics}_{rule}",
|
||||
"Person Detection",
|
||||
"binary_sensor",
|
||||
"motion",
|
||||
None,
|
||||
enabled,
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@PARSERS.register("tns1:RuleEngine/MyRuleDetector/PeopleDetect")
|
||||
async def async_parse_person_detector(uid: str, msg) -> Event | None:
|
||||
"""Handle parsing event message.
|
||||
|
335
tests/components/onvif/test_parsers.py
Normal file
335
tests/components/onvif/test_parsers.py
Normal file
@ -0,0 +1,335 @@
|
||||
"""Test ONVIF parsers."""
|
||||
|
||||
import datetime
|
||||
import os
|
||||
|
||||
import onvif
|
||||
import onvif.settings
|
||||
from zeep import Client
|
||||
from zeep.transports import Transport
|
||||
|
||||
from homeassistant.components.onvif import models, parsers
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
TEST_UID = "test-unique-id"
|
||||
|
||||
|
||||
async def get_event(notification_data: dict) -> models.Event:
|
||||
"""Take in a zeep dict, run it through the parser, and return an Event.
|
||||
|
||||
When the parser encounters an unknown topic that it doesn't know how to parse,
|
||||
it outputs a message 'No registered handler for event from ...' along with a
|
||||
print out of the serialized xml message from zeep. If it tries to parse and
|
||||
can't, it prints out 'Unable to parse event from ...' along with the same
|
||||
serialized message. This method can take the output directly from these log
|
||||
messages and run them through the parser, which makes it easy to add new unit
|
||||
tests that verify the message can now be parsed.
|
||||
"""
|
||||
zeep_client = Client(
|
||||
f"{os.path.dirname(onvif.__file__)}/wsdl/events.wsdl",
|
||||
wsse=None,
|
||||
transport=Transport(),
|
||||
)
|
||||
|
||||
notif_msg_type = zeep_client.get_type("ns5:NotificationMessageHolderType")
|
||||
assert notif_msg_type is not None
|
||||
notif_msg = notif_msg_type(**notification_data)
|
||||
assert notif_msg is not None
|
||||
|
||||
# The xsd:any type embedded inside the message doesn't parse, so parse it manually.
|
||||
msg_elem = zeep_client.get_element("ns8:Message")
|
||||
assert msg_elem is not None
|
||||
msg_data = msg_elem(**notification_data["Message"]["_value_1"])
|
||||
assert msg_data is not None
|
||||
notif_msg.Message._value_1 = msg_data
|
||||
|
||||
parser = parsers.PARSERS.get(notif_msg.Topic._value_1)
|
||||
assert parser is not None
|
||||
|
||||
return await parser(TEST_UID, notif_msg)
|
||||
|
||||
|
||||
async def test_line_detector_crossed(hass: HomeAssistant) -> None:
|
||||
"""Tests tns1:RuleEngine/LineDetector/Crossed."""
|
||||
event = await get_event(
|
||||
{
|
||||
"SubscriptionReference": {
|
||||
"Address": {"_value_1": None, "_attr_1": None},
|
||||
"ReferenceParameters": None,
|
||||
"Metadata": None,
|
||||
"_value_1": None,
|
||||
"_attr_1": None,
|
||||
},
|
||||
"Topic": {
|
||||
"_value_1": "tns1:RuleEngine/LineDetector/Crossed",
|
||||
"Dialect": "http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet",
|
||||
"_attr_1": {},
|
||||
},
|
||||
"ProducerReference": {
|
||||
"Address": {
|
||||
"_value_1": "xx.xx.xx.xx/onvif/event/alarm",
|
||||
"_attr_1": None,
|
||||
},
|
||||
"ReferenceParameters": None,
|
||||
"Metadata": None,
|
||||
"_value_1": None,
|
||||
"_attr_1": None,
|
||||
},
|
||||
"Message": {
|
||||
"_value_1": {
|
||||
"Source": {
|
||||
"SimpleItem": [
|
||||
{
|
||||
"Name": "VideoSourceConfigurationToken",
|
||||
"Value": "video_source_config1",
|
||||
},
|
||||
{
|
||||
"Name": "VideoAnalyticsConfigurationToken",
|
||||
"Value": "analytics_video_source",
|
||||
},
|
||||
{"Name": "Rule", "Value": "MyLineDetectorRule"},
|
||||
],
|
||||
"ElementItem": [],
|
||||
"Extension": None,
|
||||
"_attr_1": None,
|
||||
},
|
||||
"Key": None,
|
||||
"Data": {
|
||||
"SimpleItem": [{"Name": "ObjectId", "Value": "0"}],
|
||||
"ElementItem": [],
|
||||
"Extension": None,
|
||||
"_attr_1": None,
|
||||
},
|
||||
"Extension": None,
|
||||
"UtcTime": datetime.datetime(2020, 5, 24, 7, 24, 47),
|
||||
"PropertyOperation": "Initialized",
|
||||
"_attr_1": {},
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
assert event is not None
|
||||
assert event.name == "Line Detector Crossed"
|
||||
assert event.platform == "sensor"
|
||||
assert event.value == "0"
|
||||
assert event.uid == (
|
||||
f"{TEST_UID}_tns1:RuleEngine/LineDetector/"
|
||||
"Crossed_video_source_config1_analytics_video_source_MyLineDetectorRule"
|
||||
)
|
||||
|
||||
|
||||
async def test_tapo_vehicle(hass: HomeAssistant) -> None:
|
||||
"""Tests tns1:RuleEngine/TPSmartEventDetector/TPSmartEvent - vehicle."""
|
||||
event = await get_event(
|
||||
{
|
||||
"Message": {
|
||||
"_value_1": {
|
||||
"Data": {
|
||||
"ElementItem": [],
|
||||
"Extension": None,
|
||||
"SimpleItem": [{"Name": "IsVehicle", "Value": "true"}],
|
||||
"_attr_1": None,
|
||||
},
|
||||
"Extension": None,
|
||||
"Key": None,
|
||||
"PropertyOperation": "Changed",
|
||||
"Source": {
|
||||
"ElementItem": [],
|
||||
"Extension": None,
|
||||
"SimpleItem": [
|
||||
{
|
||||
"Name": "VideoSourceConfigurationToken",
|
||||
"Value": "vsconf",
|
||||
},
|
||||
{
|
||||
"Name": "VideoAnalyticsConfigurationToken",
|
||||
"Value": "VideoAnalyticsToken",
|
||||
},
|
||||
{
|
||||
"Name": "Rule",
|
||||
"Value": "MyTPSmartEventDetectorRule",
|
||||
},
|
||||
],
|
||||
"_attr_1": None,
|
||||
},
|
||||
"UtcTime": datetime.datetime(
|
||||
2024, 11, 2, 0, 33, 11, tzinfo=datetime.UTC
|
||||
),
|
||||
"_attr_1": {},
|
||||
}
|
||||
},
|
||||
"ProducerReference": {
|
||||
"Address": {
|
||||
"_attr_1": None,
|
||||
"_value_1": "http://192.168.56.127:5656/event",
|
||||
},
|
||||
"Metadata": None,
|
||||
"ReferenceParameters": None,
|
||||
"_attr_1": None,
|
||||
"_value_1": None,
|
||||
},
|
||||
"SubscriptionReference": {
|
||||
"Address": {
|
||||
"_attr_1": None,
|
||||
"_value_1": "http://192.168.56.127:2020/event-0_2020",
|
||||
},
|
||||
"Metadata": None,
|
||||
"ReferenceParameters": None,
|
||||
"_attr_1": None,
|
||||
"_value_1": None,
|
||||
},
|
||||
"Topic": {
|
||||
"Dialect": "http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet",
|
||||
"_attr_1": {},
|
||||
"_value_1": "tns1:RuleEngine/TPSmartEventDetector/TPSmartEvent",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
assert event is not None
|
||||
assert event.name == "Vehicle Detection"
|
||||
assert event.platform == "binary_sensor"
|
||||
assert event.device_class == "motion"
|
||||
assert event.value
|
||||
assert event.uid == (
|
||||
f"{TEST_UID}_tns1:RuleEngine/TPSmartEventDetector/"
|
||||
"TPSmartEvent_VideoSourceToken_VideoAnalyticsToken_MyTPSmartEventDetectorRule"
|
||||
)
|
||||
|
||||
|
||||
async def test_tapo_person(hass: HomeAssistant) -> None:
|
||||
"""Tests tns1:RuleEngine/TPSmartEventDetector/TPSmartEvent - person."""
|
||||
event = await get_event(
|
||||
{
|
||||
"Message": {
|
||||
"_value_1": {
|
||||
"Data": {
|
||||
"ElementItem": [],
|
||||
"Extension": None,
|
||||
"SimpleItem": [{"Name": "IsPeople", "Value": "true"}],
|
||||
"_attr_1": None,
|
||||
},
|
||||
"Extension": None,
|
||||
"Key": None,
|
||||
"PropertyOperation": "Changed",
|
||||
"Source": {
|
||||
"ElementItem": [],
|
||||
"Extension": None,
|
||||
"SimpleItem": [
|
||||
{
|
||||
"Name": "VideoSourceConfigurationToken",
|
||||
"Value": "vsconf",
|
||||
},
|
||||
{
|
||||
"Name": "VideoAnalyticsConfigurationToken",
|
||||
"Value": "VideoAnalyticsToken",
|
||||
},
|
||||
{"Name": "Rule", "Value": "MyPeopleDetectorRule"},
|
||||
],
|
||||
"_attr_1": None,
|
||||
},
|
||||
"UtcTime": datetime.datetime(
|
||||
2024, 11, 3, 18, 40, 43, tzinfo=datetime.UTC
|
||||
),
|
||||
"_attr_1": {},
|
||||
}
|
||||
},
|
||||
"ProducerReference": {
|
||||
"Address": {
|
||||
"_attr_1": None,
|
||||
"_value_1": "http://192.168.56.127:5656/event",
|
||||
},
|
||||
"Metadata": None,
|
||||
"ReferenceParameters": None,
|
||||
"_attr_1": None,
|
||||
"_value_1": None,
|
||||
},
|
||||
"SubscriptionReference": {
|
||||
"Address": {
|
||||
"_attr_1": None,
|
||||
"_value_1": "http://192.168.56.127:2020/event-0_2020",
|
||||
},
|
||||
"Metadata": None,
|
||||
"ReferenceParameters": None,
|
||||
"_attr_1": None,
|
||||
"_value_1": None,
|
||||
},
|
||||
"Topic": {
|
||||
"Dialect": "http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet",
|
||||
"_attr_1": {},
|
||||
"_value_1": "tns1:RuleEngine/PeopleDetector/People",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
assert event is not None
|
||||
assert event.name == "Person Detection"
|
||||
assert event.platform == "binary_sensor"
|
||||
assert event.device_class == "motion"
|
||||
assert event.value
|
||||
assert event.uid == (
|
||||
f"{TEST_UID}_tns1:RuleEngine/PeopleDetector/"
|
||||
"People_VideoSourceToken_VideoAnalyticsToken_MyPeopleDetectorRule"
|
||||
)
|
||||
|
||||
|
||||
async def test_tapo_missing_attributes(hass: HomeAssistant) -> None:
|
||||
"""Tests async_parse_tplink_detector with missing fields."""
|
||||
event = await get_event(
|
||||
{
|
||||
"Message": {
|
||||
"_value_1": {
|
||||
"Data": {
|
||||
"ElementItem": [],
|
||||
"Extension": None,
|
||||
"SimpleItem": [{"Name": "IsPeople", "Value": "true"}],
|
||||
"_attr_1": None,
|
||||
},
|
||||
}
|
||||
},
|
||||
"Topic": {
|
||||
"_value_1": "tns1:RuleEngine/PeopleDetector/People",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
assert event is None
|
||||
|
||||
|
||||
async def test_tapo_unknown_type(hass: HomeAssistant) -> None:
|
||||
"""Tests async_parse_tplink_detector with unknown event type."""
|
||||
event = await get_event(
|
||||
{
|
||||
"Message": {
|
||||
"_value_1": {
|
||||
"Data": {
|
||||
"ElementItem": [],
|
||||
"Extension": None,
|
||||
"SimpleItem": [{"Name": "IsNotPerson", "Value": "true"}],
|
||||
"_attr_1": None,
|
||||
},
|
||||
"Source": {
|
||||
"ElementItem": [],
|
||||
"Extension": None,
|
||||
"SimpleItem": [
|
||||
{
|
||||
"Name": "VideoSourceConfigurationToken",
|
||||
"Value": "vsconf",
|
||||
},
|
||||
{
|
||||
"Name": "VideoAnalyticsConfigurationToken",
|
||||
"Value": "VideoAnalyticsToken",
|
||||
},
|
||||
{"Name": "Rule", "Value": "MyPeopleDetectorRule"},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
"Topic": {
|
||||
"_value_1": "tns1:RuleEngine/PeopleDetector/People",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
assert event is None
|
Loading…
x
Reference in New Issue
Block a user