1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Add Axis MQTT support (#36015)

* Working PoC

* Store

* Handle subscribing to MQTT and stopping stream when first telegram arrives

* Improve naming

* Now with test

* Better strings

* Fix Martins comments

* Improve mock device patching

* Bump dependency to v27
Add MQTT as after dependency
This commit is contained in:
Robert Svensson
2020-05-25 23:13:34 +02:00
committed by GitHub
parent db92ffdf89
commit 376e0e0e93
5 changed files with 115 additions and 22 deletions

View File

@@ -1,5 +1,6 @@
"""Test Axis device."""
from copy import deepcopy
from unittest import mock
import axis as axislib
from axis.event_stream import OPERATION_INITIALIZED
@@ -13,6 +14,7 @@ from homeassistant.components.axis.const import (
CONF_MODEL,
DOMAIN as AXIS_DOMAIN,
)
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.const import (
CONF_HOST,
CONF_MAC,
@@ -23,7 +25,11 @@ from homeassistant.const import (
)
from tests.async_mock import Mock, patch
from tests.common import MockConfigEntry
from tests.common import (
MockConfigEntry,
async_fire_mqtt_message,
async_mock_mqtt_component,
)
MAC = "00408C12345"
MODEL = "model"
@@ -41,6 +47,17 @@ ENTRY_CONFIG = {
CONF_NAME: NAME,
}
DEFAULT_API_DISCOVERY = {
"method": "getApiList",
"apiVersion": "1.0",
"data": {
"apiList": [
{"id": "api-discovery", "version": "1.0", "name": "API Discovery Service"},
{"id": "param-cgi", "version": "1.0", "name": "Legacy Parameter Handling"},
]
},
}
DEFAULT_BRAND = """root.Brand.Brand=AXIS
root.Brand.ProdFullName=AXIS M1065-LW Network Camera
root.Brand.ProdNbr=M1065-LW
@@ -76,6 +93,7 @@ async def setup_axis_integration(
hass,
config=ENTRY_CONFIG,
options=ENTRY_OPTIONS,
api_discovery=DEFAULT_API_DISCOVERY,
brand=DEFAULT_BRAND,
ports=DEFAULT_PORTS,
properties=DEFAULT_PROPERTIES,
@@ -91,6 +109,9 @@ async def setup_axis_integration(
)
config_entry.add_to_hass(hass)
def mock_update_api_discovery(self):
self.process_raw(api_discovery)
def mock_update_brand(self):
self.process_raw(brand)
@@ -100,15 +121,17 @@ async def setup_axis_integration(
def mock_update_properties(self):
self.process_raw(properties)
with patch("axis.param_cgi.Brand.update_brand", new=mock_update_brand), patch(
with patch(
"axis.api_discovery.ApiDiscovery.update", new=mock_update_api_discovery
), patch("axis.param_cgi.Brand.update_brand", new=mock_update_brand), patch(
"axis.param_cgi.Ports.update_ports", new=mock_update_ports
), patch(
"axis.param_cgi.Properties.update_properties", new=mock_update_properties
), patch(
"axis.AxisDevice.start", return_value=True
"axis.rtsp.RTSPClient.start", return_value=True,
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await hass.async_block_till_done()
return hass.data[AXIS_DOMAIN].get(config_entry.unique_id)
@@ -134,6 +157,36 @@ async def test_device_setup(hass):
assert device.serial == ENTRY_CONFIG[CONF_MAC]
async def test_device_support_mqtt(hass):
"""Successful setup."""
api_discovery = deepcopy(DEFAULT_API_DISCOVERY)
api_discovery["data"]["apiList"].append(
{"id": "mqtt-client", "version": "1.0", "name": "MQTT Client API"}
)
get_client_status = {"data": {"status": {"state": "active"}}}
mock_mqtt = await async_mock_mqtt_component(hass)
with patch(
"axis.mqtt.MqttClient.get_client_status", return_value=get_client_status
):
await setup_axis_integration(hass, api_discovery=api_discovery)
mock_mqtt.async_subscribe.assert_called_with(f"{MAC}/#", mock.ANY, 0, "utf-8")
topic = f"{MAC}/event/tns:onvif/Device/tns:axis/Sensor/PIR/$source/sensor/0"
message = b'{"timestamp": 1590258472044, "topic": "onvif:Device/axis:Sensor/PIR", "message": {"source": {"sensor": "0"}, "key": {}, "data": {"state": "1"}}}'
assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 0
async_fire_mqtt_message(hass, topic, message)
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 1
pir = hass.states.get(f"binary_sensor.{NAME}_pir_0")
assert pir.state == "on"
assert pir.name == f"{NAME} PIR 0"
async def test_update_address(hass):
"""Test update address works."""
device = await setup_axis_integration(hass)
@@ -208,13 +261,13 @@ async def test_shutdown():
axis_device.shutdown(None)
assert len(axis_device.api.stop.mock_calls) == 1
assert len(axis_device.api.stream.stop.mock_calls) == 1
async def test_get_device_fails(hass):
"""Device unauthorized yields authentication required error."""
with patch(
"axis.param_cgi.Params.update_brand", side_effect=axislib.Unauthorized
"axis.api_discovery.ApiDiscovery.update", side_effect=axislib.Unauthorized
), pytest.raises(axis.errors.AuthenticationRequired):
await axis.device.get_device(hass, host="", port="", username="", password="")
@@ -222,7 +275,7 @@ async def test_get_device_fails(hass):
async def test_get_device_device_unavailable(hass):
"""Device unavailable yields cannot connect error."""
with patch(
"axis.param_cgi.Params.update_brand", side_effect=axislib.RequestError
"axis.api_discovery.ApiDiscovery.update", side_effect=axislib.RequestError
), pytest.raises(axis.errors.CannotConnect):
await axis.device.get_device(hass, host="", port="", username="", password="")
@@ -230,6 +283,6 @@ async def test_get_device_device_unavailable(hass):
async def test_get_device_unknown_error(hass):
"""Device yield unknown error."""
with patch(
"axis.param_cgi.Params.update_brand", side_effect=axislib.AxisException
"axis.api_discovery.ApiDiscovery.update", side_effect=axislib.AxisException
), pytest.raises(axis.errors.AuthenticationRequired):
await axis.device.get_device(hass, host="", port="", username="", password="")