Adapt Axis integration to library refactoring (#110898)
* Adapt Axis integration to library refactoring * Bump axis to v49
This commit is contained in:
parent
2b3630b054
commit
c478b1416c
17 changed files with 403 additions and 199 deletions
|
@ -5,6 +5,10 @@ from collections.abc import Callable
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from axis.models.event import Event, EventGroup, EventOperation, EventTopic
|
from axis.models.event import Event, EventGroup, EventOperation, EventTopic
|
||||||
|
from axis.vapix.interfaces.applications.fence_guard import FenceGuardHandler
|
||||||
|
from axis.vapix.interfaces.applications.loitering_guard import LoiteringGuardHandler
|
||||||
|
from axis.vapix.interfaces.applications.motion_guard import MotionGuardHandler
|
||||||
|
from axis.vapix.interfaces.applications.vmd4 import Vmd4Handler
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
BinarySensorDeviceClass,
|
BinarySensorDeviceClass,
|
||||||
|
@ -111,17 +115,33 @@ class AxisBinarySensor(AxisEventEntity, BinarySensorEntity):
|
||||||
self._attr_name = self.device.api.vapix.ports[event.id].name
|
self._attr_name = self.device.api.vapix.ports[event.id].name
|
||||||
|
|
||||||
elif event.group == EventGroup.MOTION:
|
elif event.group == EventGroup.MOTION:
|
||||||
for event_topic, event_data in (
|
event_data: FenceGuardHandler | LoiteringGuardHandler | MotionGuardHandler | Vmd4Handler | None = None
|
||||||
(EventTopic.FENCE_GUARD, self.device.api.vapix.fence_guard),
|
if event.topic_base == EventTopic.FENCE_GUARD:
|
||||||
(EventTopic.LOITERING_GUARD, self.device.api.vapix.loitering_guard),
|
event_data = self.device.api.vapix.fence_guard
|
||||||
(EventTopic.MOTION_GUARD, self.device.api.vapix.motion_guard),
|
elif event.topic_base == EventTopic.LOITERING_GUARD:
|
||||||
(EventTopic.OBJECT_ANALYTICS, self.device.api.vapix.object_analytics),
|
event_data = self.device.api.vapix.loitering_guard
|
||||||
(EventTopic.MOTION_DETECTION_4, self.device.api.vapix.vmd4),
|
elif event.topic_base == EventTopic.MOTION_GUARD:
|
||||||
|
event_data = self.device.api.vapix.motion_guard
|
||||||
|
elif event.topic_base == EventTopic.MOTION_DETECTION_4:
|
||||||
|
event_data = self.device.api.vapix.vmd4
|
||||||
|
if (
|
||||||
|
event_data
|
||||||
|
and event_data.initialized
|
||||||
|
and (profiles := event_data["0"].profiles)
|
||||||
):
|
):
|
||||||
if (
|
for profile_id, profile in profiles.items():
|
||||||
event.topic_base == event_topic
|
camera_id = profile.camera
|
||||||
and event_data
|
if event.id == f"Camera{camera_id}Profile{profile_id}":
|
||||||
and event.id in event_data
|
self._attr_name = f"{self._event_type} {profile.name}"
|
||||||
):
|
return
|
||||||
self._attr_name = f"{self._event_type} {event_data[event.id].name}"
|
|
||||||
break
|
if (
|
||||||
|
event.topic_base == EventTopic.OBJECT_ANALYTICS
|
||||||
|
and self.device.api.vapix.object_analytics.initialized
|
||||||
|
and (scenarios := self.device.api.vapix.object_analytics["0"].scenarios)
|
||||||
|
):
|
||||||
|
for scenario_id, scenario in scenarios.items():
|
||||||
|
device_id = scenario.devices[0]["id"]
|
||||||
|
if event.id == f"Device{device_id}Scenario{scenario_id}":
|
||||||
|
self._attr_name = f"{self._event_type} {scenario.name}"
|
||||||
|
break
|
||||||
|
|
|
@ -24,7 +24,10 @@ async def async_setup_entry(
|
||||||
|
|
||||||
device: AxisNetworkDevice = hass.data[AXIS_DOMAIN][config_entry.entry_id]
|
device: AxisNetworkDevice = hass.data[AXIS_DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
if not device.api.vapix.params.image_format:
|
if (
|
||||||
|
not (prop := device.api.vapix.params.property_handler.get("0"))
|
||||||
|
or not prop.image_format
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
async_add_entities([AxisCamera(device)])
|
async_add_entities([AxisCamera(device)])
|
||||||
|
|
|
@ -249,7 +249,10 @@ class AxisOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry):
|
||||||
|
|
||||||
# Stream profiles
|
# Stream profiles
|
||||||
|
|
||||||
if vapix.stream_profiles or vapix.params.stream_profiles_max_groups > 0:
|
if vapix.stream_profiles or (
|
||||||
|
(profiles := vapix.params.stream_profile_handler.get("0"))
|
||||||
|
and profiles.max_groups > 0
|
||||||
|
):
|
||||||
stream_profiles = [DEFAULT_STREAM_PROFILE]
|
stream_profiles = [DEFAULT_STREAM_PROFILE]
|
||||||
for profile in vapix.streaming_profiles:
|
for profile in vapix.streaming_profiles:
|
||||||
stream_profiles.append(profile.name)
|
stream_profiles.append(profile.name)
|
||||||
|
@ -262,14 +265,17 @@ class AxisOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry):
|
||||||
|
|
||||||
# Video sources
|
# Video sources
|
||||||
|
|
||||||
if vapix.params.image_nbrofviews > 0:
|
if (
|
||||||
await vapix.params.update_image()
|
properties := vapix.params.property_handler.get("0")
|
||||||
|
) and properties.image_number_of_views > 0:
|
||||||
video_sources = {DEFAULT_VIDEO_SOURCE: DEFAULT_VIDEO_SOURCE}
|
await vapix.params.image_handler.update()
|
||||||
for idx, video_source in vapix.params.image_sources.items():
|
video_sources: dict[int | str, str] = {
|
||||||
if not video_source["Enabled"]:
|
DEFAULT_VIDEO_SOURCE: DEFAULT_VIDEO_SOURCE
|
||||||
|
}
|
||||||
|
for idx, video_source in vapix.params.image_handler.items():
|
||||||
|
if not video_source.enabled:
|
||||||
continue
|
continue
|
||||||
video_sources[idx + 1] = video_source["Name"]
|
video_sources[int(idx) + 1] = video_source.name
|
||||||
|
|
||||||
schema[
|
schema[
|
||||||
vol.Optional(CONF_VIDEO_SOURCE, default=self.device.option_video_source)
|
vol.Optional(CONF_VIDEO_SOURCE, default=self.device.option_video_source)
|
||||||
|
|
|
@ -9,6 +9,7 @@ from axis.configuration import Configuration
|
||||||
from axis.errors import Unauthorized
|
from axis.errors import Unauthorized
|
||||||
from axis.stream_manager import Signal, State
|
from axis.stream_manager import Signal, State
|
||||||
from axis.vapix.interfaces.mqtt import mqtt_json_to_event
|
from axis.vapix.interfaces.mqtt import mqtt_json_to_event
|
||||||
|
from axis.vapix.models.mqtt import ClientState
|
||||||
|
|
||||||
from homeassistant.components import mqtt
|
from homeassistant.components import mqtt
|
||||||
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
|
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
|
||||||
|
@ -188,9 +189,8 @@ class AxisNetworkDevice:
|
||||||
status = await self.api.vapix.mqtt.get_client_status()
|
status = await self.api.vapix.mqtt.get_client_status()
|
||||||
except Unauthorized:
|
except Unauthorized:
|
||||||
# This means the user has too low privileges
|
# This means the user has too low privileges
|
||||||
status = {}
|
return
|
||||||
|
if status.status.state == ClientState.ACTIVE:
|
||||||
if status.get("data", {}).get("status", {}).get("state") == "active":
|
|
||||||
self.config_entry.async_on_unload(
|
self.config_entry.async_on_unload(
|
||||||
await mqtt.async_subscribe(
|
await mqtt.async_subscribe(
|
||||||
hass, f"{self.api.vapix.serial_number}/#", self.mqtt_message
|
hass, f"{self.api.vapix.serial_number}/#", self.mqtt_message
|
||||||
|
@ -209,7 +209,6 @@ class AxisNetworkDevice:
|
||||||
|
|
||||||
def async_setup_events(self) -> None:
|
def async_setup_events(self) -> None:
|
||||||
"""Set up the device events."""
|
"""Set up the device events."""
|
||||||
|
|
||||||
if self.option_events:
|
if self.option_events:
|
||||||
self.api.stream.connection_status_callback.append(
|
self.api.stream.connection_status_callback.append(
|
||||||
self.async_connection_status_callback
|
self.async_connection_status_callback
|
||||||
|
@ -217,7 +216,7 @@ class AxisNetworkDevice:
|
||||||
self.api.enable_events()
|
self.api.enable_events()
|
||||||
self.api.stream.start()
|
self.api.stream.start()
|
||||||
|
|
||||||
if self.api.vapix.mqtt:
|
if self.api.vapix.mqtt.supported:
|
||||||
async_when_setup(self.hass, MQTT_DOMAIN, self.async_use_mqtt)
|
async_when_setup(self.hass, MQTT_DOMAIN, self.async_use_mqtt)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
|
|
@ -33,13 +33,13 @@ async def async_get_config_entry_diagnostics(
|
||||||
|
|
||||||
if device.api.vapix.basic_device_info:
|
if device.api.vapix.basic_device_info:
|
||||||
diag["basic_device_info"] = async_redact_data(
|
diag["basic_device_info"] = async_redact_data(
|
||||||
{attr.id: attr.raw for attr in device.api.vapix.basic_device_info.values()},
|
device.api.vapix.basic_device_info["0"],
|
||||||
REDACT_BASIC_DEVICE_INFO,
|
REDACT_BASIC_DEVICE_INFO,
|
||||||
)
|
)
|
||||||
|
|
||||||
if device.api.vapix.params:
|
if device.api.vapix.params:
|
||||||
diag["params"] = async_redact_data(
|
diag["params"] = async_redact_data(
|
||||||
{param.id: param.raw for param in device.api.vapix.params.values()},
|
device.api.vapix.params.items(),
|
||||||
REDACT_VAPIX_PARAMS,
|
REDACT_VAPIX_PARAMS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -69,12 +69,12 @@ class AxisLight(AxisEventEntity, LightEntity):
|
||||||
self._light_id
|
self._light_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.current_intensity = current_intensity["data"]["intensity"]
|
self.current_intensity = current_intensity
|
||||||
|
|
||||||
max_intensity = await self.device.api.vapix.light_control.get_valid_intensity(
|
max_intensity = await self.device.api.vapix.light_control.get_valid_intensity(
|
||||||
self._light_id
|
self._light_id
|
||||||
)
|
)
|
||||||
self.max_intensity = max_intensity["data"]["ranges"][0]["high"]
|
self.max_intensity = max_intensity.high
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_event_callback(self, event: Event) -> None:
|
def async_event_callback(self, event: Event) -> None:
|
||||||
|
@ -110,4 +110,4 @@ class AxisLight(AxisEventEntity, LightEntity):
|
||||||
self._light_id
|
self._light_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.current_intensity = current_intensity["data"]["intensity"]
|
self.current_intensity = current_intensity
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["axis"],
|
"loggers": ["axis"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["axis==48"],
|
"requirements": ["axis==49"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "AXIS"
|
"manufacturer": "AXIS"
|
||||||
|
|
|
@ -39,7 +39,6 @@ class AxisSwitch(AxisEventEntity, SwitchEntity):
|
||||||
def __init__(self, event: Event, device: AxisNetworkDevice) -> None:
|
def __init__(self, event: Event, device: AxisNetworkDevice) -> None:
|
||||||
"""Initialize the Axis switch."""
|
"""Initialize the Axis switch."""
|
||||||
super().__init__(event, device)
|
super().__init__(event, device)
|
||||||
|
|
||||||
if event.id and device.api.vapix.ports[event.id].name:
|
if event.id and device.api.vapix.ports[event.id].name:
|
||||||
self._attr_name = device.api.vapix.ports[event.id].name
|
self._attr_name = device.api.vapix.ports[event.id].name
|
||||||
self._attr_is_on = event.is_tripped
|
self._attr_is_on = event.is_tripped
|
||||||
|
@ -52,8 +51,8 @@ class AxisSwitch(AxisEventEntity, SwitchEntity):
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn on switch."""
|
"""Turn on switch."""
|
||||||
await self.device.api.vapix.ports[self._event_id].close()
|
await self.device.api.vapix.ports.close(self._event_id)
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn off switch."""
|
"""Turn off switch."""
|
||||||
await self.device.api.vapix.ports[self._event_id].open()
|
await self.device.api.vapix.ports.open(self._event_id)
|
||||||
|
|
|
@ -514,7 +514,7 @@ aurorapy==0.2.7
|
||||||
# avion==0.10
|
# avion==0.10
|
||||||
|
|
||||||
# homeassistant.components.axis
|
# homeassistant.components.axis
|
||||||
axis==48
|
axis==49
|
||||||
|
|
||||||
# homeassistant.components.azure_event_hub
|
# homeassistant.components.azure_event_hub
|
||||||
azure-eventhub==5.11.1
|
azure-eventhub==5.11.1
|
||||||
|
|
|
@ -454,7 +454,7 @@ auroranoaa==0.0.3
|
||||||
aurorapy==0.2.7
|
aurorapy==0.2.7
|
||||||
|
|
||||||
# homeassistant.components.axis
|
# homeassistant.components.axis
|
||||||
axis==48
|
axis==49
|
||||||
|
|
||||||
# homeassistant.components.azure_event_hub
|
# homeassistant.components.azure_event_hub
|
||||||
azure-eventhub==5.11.1
|
azure-eventhub==5.11.1
|
||||||
|
|
|
@ -99,7 +99,9 @@ def options_fixture(request):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mock_vapix_requests")
|
@pytest.fixture(name="mock_vapix_requests")
|
||||||
def default_request_fixture(respx_mock):
|
def default_request_fixture(
|
||||||
|
respx_mock, port_management_payload, param_properties_payload, param_ports_payload
|
||||||
|
):
|
||||||
"""Mock default Vapix requests responses."""
|
"""Mock default Vapix requests responses."""
|
||||||
|
|
||||||
def __mock_default_requests(host):
|
def __mock_default_requests(host):
|
||||||
|
@ -113,7 +115,7 @@ def default_request_fixture(respx_mock):
|
||||||
json=BASIC_DEVICE_INFO_RESPONSE,
|
json=BASIC_DEVICE_INFO_RESPONSE,
|
||||||
)
|
)
|
||||||
respx.post(f"{path}/axis-cgi/io/portmanagement.cgi").respond(
|
respx.post(f"{path}/axis-cgi/io/portmanagement.cgi").respond(
|
||||||
json=PORT_MANAGEMENT_RESPONSE,
|
json=port_management_payload,
|
||||||
)
|
)
|
||||||
respx.post(f"{path}/axis-cgi/mqtt/client.cgi").respond(
|
respx.post(f"{path}/axis-cgi/mqtt/client.cgi").respond(
|
||||||
json=MQTT_CLIENT_RESPONSE,
|
json=MQTT_CLIENT_RESPONSE,
|
||||||
|
@ -124,38 +126,58 @@ def default_request_fixture(respx_mock):
|
||||||
respx.post(f"{path}/axis-cgi/viewarea/info.cgi").respond(
|
respx.post(f"{path}/axis-cgi/viewarea/info.cgi").respond(
|
||||||
json=VIEW_AREAS_RESPONSE
|
json=VIEW_AREAS_RESPONSE
|
||||||
)
|
)
|
||||||
respx.get(f"{path}/axis-cgi/param.cgi?action=list&group=root.Brand").respond(
|
respx.post(
|
||||||
|
f"{path}/axis-cgi/param.cgi",
|
||||||
|
data={"action": "list", "group": "root.Brand"},
|
||||||
|
).respond(
|
||||||
text=BRAND_RESPONSE,
|
text=BRAND_RESPONSE,
|
||||||
headers={"Content-Type": "text/plain"},
|
headers={"Content-Type": "text/plain"},
|
||||||
)
|
)
|
||||||
respx.get(f"{path}/axis-cgi/param.cgi?action=list&group=root.Image").respond(
|
respx.post(
|
||||||
|
f"{path}/axis-cgi/param.cgi",
|
||||||
|
data={"action": "list", "group": "root.Image"},
|
||||||
|
).respond(
|
||||||
text=IMAGE_RESPONSE,
|
text=IMAGE_RESPONSE,
|
||||||
headers={"Content-Type": "text/plain"},
|
headers={"Content-Type": "text/plain"},
|
||||||
)
|
)
|
||||||
respx.get(f"{path}/axis-cgi/param.cgi?action=list&group=root.Input").respond(
|
respx.post(
|
||||||
text=PORTS_RESPONSE,
|
f"{path}/axis-cgi/param.cgi",
|
||||||
headers={"Content-Type": "text/plain"},
|
data={"action": "list", "group": "root.Input"},
|
||||||
)
|
|
||||||
respx.get(f"{path}/axis-cgi/param.cgi?action=list&group=root.IOPort").respond(
|
|
||||||
text=PORTS_RESPONSE,
|
|
||||||
headers={"Content-Type": "text/plain"},
|
|
||||||
)
|
|
||||||
respx.get(f"{path}/axis-cgi/param.cgi?action=list&group=root.Output").respond(
|
|
||||||
text=PORTS_RESPONSE,
|
|
||||||
headers={"Content-Type": "text/plain"},
|
|
||||||
)
|
|
||||||
respx.get(
|
|
||||||
f"{path}/axis-cgi/param.cgi?action=list&group=root.Properties"
|
|
||||||
).respond(
|
).respond(
|
||||||
text=PROPERTIES_RESPONSE,
|
text=PORTS_RESPONSE,
|
||||||
headers={"Content-Type": "text/plain"},
|
headers={"Content-Type": "text/plain"},
|
||||||
)
|
)
|
||||||
respx.get(f"{path}/axis-cgi/param.cgi?action=list&group=root.PTZ").respond(
|
respx.post(
|
||||||
|
f"{path}/axis-cgi/param.cgi",
|
||||||
|
data={"action": "list", "group": "root.IOPort"},
|
||||||
|
).respond(
|
||||||
|
text=param_ports_payload,
|
||||||
|
headers={"Content-Type": "text/plain"},
|
||||||
|
)
|
||||||
|
respx.post(
|
||||||
|
f"{path}/axis-cgi/param.cgi",
|
||||||
|
data={"action": "list", "group": "root.Output"},
|
||||||
|
).respond(
|
||||||
|
text=PORTS_RESPONSE,
|
||||||
|
headers={"Content-Type": "text/plain"},
|
||||||
|
)
|
||||||
|
respx.post(
|
||||||
|
f"{path}/axis-cgi/param.cgi",
|
||||||
|
data={"action": "list", "group": "root.Properties"},
|
||||||
|
).respond(
|
||||||
|
text=param_properties_payload,
|
||||||
|
headers={"Content-Type": "text/plain"},
|
||||||
|
)
|
||||||
|
respx.post(
|
||||||
|
f"{path}/axis-cgi/param.cgi",
|
||||||
|
data={"action": "list", "group": "root.PTZ"},
|
||||||
|
).respond(
|
||||||
text=PTZ_RESPONSE,
|
text=PTZ_RESPONSE,
|
||||||
headers={"Content-Type": "text/plain"},
|
headers={"Content-Type": "text/plain"},
|
||||||
)
|
)
|
||||||
respx.get(
|
respx.post(
|
||||||
f"{path}/axis-cgi/param.cgi?action=list&group=root.StreamProfile"
|
f"{path}/axis-cgi/param.cgi",
|
||||||
|
data={"action": "list", "group": "root.StreamProfile"},
|
||||||
).respond(
|
).respond(
|
||||||
text=STREAM_PROFILES_RESPONSE,
|
text=STREAM_PROFILES_RESPONSE,
|
||||||
headers={"Content-Type": "text/plain"},
|
headers={"Content-Type": "text/plain"},
|
||||||
|
@ -184,6 +206,24 @@ def api_discovery_fixture(api_discovery_items):
|
||||||
respx.post(f"http://{DEFAULT_HOST}:80/axis-cgi/apidiscovery.cgi").respond(json=data)
|
respx.post(f"http://{DEFAULT_HOST}:80/axis-cgi/apidiscovery.cgi").respond(json=data)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="port_management_payload")
|
||||||
|
def io_port_management_data_fixture():
|
||||||
|
"""Property parameter data."""
|
||||||
|
return PORT_MANAGEMENT_RESPONSE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="param_properties_payload")
|
||||||
|
def param_properties_data_fixture():
|
||||||
|
"""Property parameter data."""
|
||||||
|
return PROPERTIES_RESPONSE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="param_ports_payload")
|
||||||
|
def param_ports_data_fixture():
|
||||||
|
"""Property parameter data."""
|
||||||
|
return PORTS_RESPONSE
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="setup_default_vapix_requests")
|
@pytest.fixture(name="setup_default_vapix_requests")
|
||||||
def default_vapix_requests_fixture(mock_vapix_requests):
|
def default_vapix_requests_fixture(mock_vapix_requests):
|
||||||
"""Mock default Vapix requests responses."""
|
"""Mock default Vapix requests responses."""
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Constants for Axis integration tests."""
|
"""Constants for Axis integration tests."""
|
||||||
|
|
||||||
|
from axis.vapix.models.api import CONTEXT
|
||||||
|
|
||||||
MAC = "00408C123456"
|
MAC = "00408C123456"
|
||||||
FORMATTED_MAC = "00:40:8c:12:34:56"
|
FORMATTED_MAC = "00:40:8c:12:34:56"
|
||||||
|
@ -12,6 +13,7 @@ DEFAULT_HOST = "1.2.3.4"
|
||||||
API_DISCOVERY_RESPONSE = {
|
API_DISCOVERY_RESPONSE = {
|
||||||
"method": "getApiList",
|
"method": "getApiList",
|
||||||
"apiVersion": "1.0",
|
"apiVersion": "1.0",
|
||||||
|
"context": CONTEXT,
|
||||||
"data": {
|
"data": {
|
||||||
"apiList": [
|
"apiList": [
|
||||||
{"id": "api-discovery", "version": "1.0", "name": "API Discovery Service"},
|
{"id": "api-discovery", "version": "1.0", "name": "API Discovery Service"},
|
||||||
|
@ -38,27 +40,45 @@ APPLICATIONS_LIST_RESPONSE = """<reply result="ok">
|
||||||
|
|
||||||
BASIC_DEVICE_INFO_RESPONSE = {
|
BASIC_DEVICE_INFO_RESPONSE = {
|
||||||
"apiVersion": "1.1",
|
"apiVersion": "1.1",
|
||||||
|
"context": CONTEXT,
|
||||||
"data": {
|
"data": {
|
||||||
"propertyList": {
|
"propertyList": {
|
||||||
"ProdNbr": "M1065-LW",
|
"ProdNbr": "M1065-LW",
|
||||||
"ProdType": "Network Camera",
|
"ProdType": "Network Camera",
|
||||||
"SerialNumber": MAC,
|
"SerialNumber": MAC,
|
||||||
"Version": "9.80.1",
|
"Version": "9.80.1",
|
||||||
|
"Architecture": "str",
|
||||||
|
"Brand": "str",
|
||||||
|
"BuildDate": "str",
|
||||||
|
"HardwareID": "str",
|
||||||
|
"ProdFullName": "str",
|
||||||
|
"ProdShortName": "str",
|
||||||
|
"ProdVariant": "str",
|
||||||
|
"Soc": "str",
|
||||||
|
"SocSerialNumber": "str",
|
||||||
|
"WebURL": "str",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
MQTT_CLIENT_RESPONSE = {
|
MQTT_CLIENT_RESPONSE = {
|
||||||
"apiVersion": "1.0",
|
|
||||||
"context": "some context",
|
|
||||||
"method": "getClientStatus",
|
"method": "getClientStatus",
|
||||||
"data": {"status": {"state": "active", "connectionStatus": "Connected"}},
|
"apiVersion": "1.0",
|
||||||
|
"context": CONTEXT,
|
||||||
|
"data": {
|
||||||
|
"status": {"state": "active", "connectionStatus": "Connected"},
|
||||||
|
"config": {
|
||||||
|
"server": {"protocol": "tcp", "host": "192.168.0.90", "port": 1883},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PORT_MANAGEMENT_RESPONSE = {
|
PORT_MANAGEMENT_RESPONSE = {
|
||||||
"apiVersion": "1.0",
|
"apiVersion": "1.0",
|
||||||
"method": "getPorts",
|
"method": "getPorts",
|
||||||
|
"context": CONTEXT,
|
||||||
"data": {
|
"data": {
|
||||||
"numberOfPorts": 1,
|
"numberOfPorts": 1,
|
||||||
"items": [
|
"items": [
|
||||||
|
@ -78,12 +98,13 @@ PORT_MANAGEMENT_RESPONSE = {
|
||||||
VMD4_RESPONSE = {
|
VMD4_RESPONSE = {
|
||||||
"apiVersion": "1.4",
|
"apiVersion": "1.4",
|
||||||
"method": "getConfiguration",
|
"method": "getConfiguration",
|
||||||
"context": "Axis library",
|
"context": CONTEXT,
|
||||||
"data": {
|
"data": {
|
||||||
"cameras": [{"id": 1, "rotation": 0, "active": True}],
|
"cameras": [{"id": 1, "rotation": 0, "active": True}],
|
||||||
"profiles": [
|
"profiles": [
|
||||||
{"filters": [], "camera": 1, "triggers": [], "name": "Profile 1", "uid": 1}
|
{"filters": [], "camera": 1, "triggers": [], "name": "Profile 1", "uid": 1}
|
||||||
],
|
],
|
||||||
|
"configurationStatus": 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +123,95 @@ root.Image.I0.Source=0
|
||||||
root.Image.I1.Enabled=no
|
root.Image.I1.Enabled=no
|
||||||
root.Image.I1.Name=View Area 2
|
root.Image.I1.Name=View Area 2
|
||||||
root.Image.I1.Source=0
|
root.Image.I1.Source=0
|
||||||
|
root.Image.I0.Appearance.ColorEnabled=yes
|
||||||
|
root.Image.I0.Appearance.Compression=30
|
||||||
|
root.Image.I0.Appearance.MirrorEnabled=no
|
||||||
|
root.Image.I0.Appearance.Resolution=1920x1080
|
||||||
|
root.Image.I0.Appearance.Rotation=0
|
||||||
|
root.Image.I0.MPEG.Complexity=50
|
||||||
|
root.Image.I0.MPEG.ConfigHeaderInterval=1
|
||||||
|
root.Image.I0.MPEG.FrameSkipMode=drop
|
||||||
|
root.Image.I0.MPEG.ICount=1
|
||||||
|
root.Image.I0.MPEG.PCount=31
|
||||||
|
root.Image.I0.MPEG.UserDataEnabled=no
|
||||||
|
root.Image.I0.MPEG.UserDataInterval=1
|
||||||
|
root.Image.I0.MPEG.ZChromaQPMode=off
|
||||||
|
root.Image.I0.MPEG.ZFpsMode=fixed
|
||||||
|
root.Image.I0.MPEG.ZGopMode=fixed
|
||||||
|
root.Image.I0.MPEG.ZMaxGopLength=300
|
||||||
|
root.Image.I0.MPEG.ZMinFps=0
|
||||||
|
root.Image.I0.MPEG.ZStrength=10
|
||||||
|
root.Image.I0.MPEG.H264.Profile=high
|
||||||
|
root.Image.I0.MPEG.H264.PSEnabled=no
|
||||||
|
root.Image.I0.Overlay.Enabled=no
|
||||||
|
root.Image.I0.Overlay.XPos=0
|
||||||
|
root.Image.I0.Overlay.YPos=0
|
||||||
|
root.Image.I0.Overlay.MaskWindows.Color=black
|
||||||
|
root.Image.I0.RateControl.MaxBitrate=0
|
||||||
|
root.Image.I0.RateControl.Mode=vbr
|
||||||
|
root.Image.I0.RateControl.Priority=framerate
|
||||||
|
root.Image.I0.RateControl.TargetBitrate=0
|
||||||
|
root.Image.I0.SizeControl.MaxFrameSize=0
|
||||||
|
root.Image.I0.Stream.Duration=0
|
||||||
|
root.Image.I0.Stream.FPS=0
|
||||||
|
root.Image.I0.Stream.NbrOfFrames=0
|
||||||
|
root.Image.I0.Text.BGColor=black
|
||||||
|
root.Image.I0.Text.ClockEnabled=no
|
||||||
|
root.Image.I0.Text.Color=white
|
||||||
|
root.Image.I0.Text.DateEnabled=no
|
||||||
|
root.Image.I0.Text.Position=top
|
||||||
|
root.Image.I0.Text.String=
|
||||||
|
root.Image.I0.Text.TextEnabled=no
|
||||||
|
root.Image.I0.Text.TextSize=medium
|
||||||
|
root.Image.I0.TriggerData.AudioEnabled=yes
|
||||||
|
root.Image.I0.TriggerData.MotionDetectionEnabled=yes
|
||||||
|
root.Image.I0.TriggerData.MotionLevelEnabled=no
|
||||||
|
root.Image.I0.TriggerData.TamperingEnabled=yes
|
||||||
|
root.Image.I0.TriggerData.UserTriggers=
|
||||||
|
root.Image.I1.Appearance.ColorEnabled=yes
|
||||||
|
root.Image.I1.Appearance.Compression=30
|
||||||
|
root.Image.I1.Appearance.MirrorEnabled=no
|
||||||
|
root.Image.I1.Appearance.Resolution=1920x1080
|
||||||
|
root.Image.I1.Appearance.Rotation=0
|
||||||
|
root.Image.I1.MPEG.Complexity=50
|
||||||
|
root.Image.I1.MPEG.ConfigHeaderInterval=1
|
||||||
|
root.Image.I1.MPEG.FrameSkipMode=drop
|
||||||
|
root.Image.I1.MPEG.ICount=1
|
||||||
|
root.Image.I1.MPEG.PCount=31
|
||||||
|
root.Image.I1.MPEG.UserDataEnabled=no
|
||||||
|
root.Image.I1.MPEG.UserDataInterval=1
|
||||||
|
root.Image.I1.MPEG.ZChromaQPMode=off
|
||||||
|
root.Image.I1.MPEG.ZFpsMode=fixed
|
||||||
|
root.Image.I1.MPEG.ZGopMode=fixed
|
||||||
|
root.Image.I1.MPEG.ZMaxGopLength=300
|
||||||
|
root.Image.I1.MPEG.ZMinFps=0
|
||||||
|
root.Image.I1.MPEG.ZStrength=10
|
||||||
|
root.Image.I1.MPEG.H264.Profile=high
|
||||||
|
root.Image.I1.MPEG.H264.PSEnabled=no
|
||||||
|
root.Image.I1.Overlay.Enabled=no
|
||||||
|
root.Image.I1.Overlay.XPos=0
|
||||||
|
root.Image.I1.Overlay.YPos=0
|
||||||
|
root.Image.I1.RateControl.MaxBitrate=0
|
||||||
|
root.Image.I1.RateControl.Mode=vbr
|
||||||
|
root.Image.I1.RateControl.Priority=framerate
|
||||||
|
root.Image.I1.RateControl.TargetBitrate=0
|
||||||
|
root.Image.I1.SizeControl.MaxFrameSize=0
|
||||||
|
root.Image.I1.Stream.Duration=0
|
||||||
|
root.Image.I1.Stream.FPS=0
|
||||||
|
root.Image.I1.Stream.NbrOfFrames=0
|
||||||
|
root.Image.I1.Text.BGColor=black
|
||||||
|
root.Image.I1.Text.ClockEnabled=no
|
||||||
|
root.Image.I1.Text.Color=white
|
||||||
|
root.Image.I1.Text.DateEnabled=no
|
||||||
|
root.Image.I1.Text.Position=top
|
||||||
|
root.Image.I1.Text.String=
|
||||||
|
root.Image.I1.Text.TextEnabled=no
|
||||||
|
root.Image.I1.Text.TextSize=medium
|
||||||
|
root.Image.I1.TriggerData.AudioEnabled=yes
|
||||||
|
root.Image.I1.TriggerData.MotionDetectionEnabled=yes
|
||||||
|
root.Image.I1.TriggerData.MotionLevelEnabled=no
|
||||||
|
root.Image.I1.TriggerData.TamperingEnabled=yes
|
||||||
|
root.Image.I1.TriggerData.UserTriggers=
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PORTS_RESPONSE = """root.Input.NbrOfInputs=1
|
PORTS_RESPONSE = """root.Input.NbrOfInputs=1
|
||||||
|
|
|
@ -19,10 +19,8 @@
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
'basic_device_info': dict({
|
'basic_device_info': dict({
|
||||||
'ProdNbr': 'M1065-LW',
|
'__type': "<class 'axis.vapix.models.basic_device_info.DeviceInformation'>",
|
||||||
'ProdType': 'Network Camera',
|
'repr': "DeviceInformation(id='0', architecture='str', brand='str', build_date='str', firmware_version='9.80.1', hardware_id='str', product_full_name='str', product_number='M1065-LW', product_short_name='str', product_type='Network Camera', product_variant='str', serial_number='00408C123456', soc='str', soc_serial_number='str', web_url='str')",
|
||||||
'SerialNumber': '**REDACTED**',
|
|
||||||
'Version': '9.80.1',
|
|
||||||
}),
|
}),
|
||||||
'camera_sources': dict({
|
'camera_sources': dict({
|
||||||
'Image': 'http://1.2.3.4:80/axis-cgi/jpg/image.cgi',
|
'Image': 'http://1.2.3.4:80/axis-cgi/jpg/image.cgi',
|
||||||
|
@ -53,41 +51,8 @@
|
||||||
'version': 3,
|
'version': 3,
|
||||||
}),
|
}),
|
||||||
'params': dict({
|
'params': dict({
|
||||||
'root.IOPort': dict({
|
'__type': "<class 'dict_items'>",
|
||||||
'I0.Configurable': 'no',
|
'repr': "dict_items([('Properties', {'API': {'HTTP': {'Version': 3}, 'Metadata': {'Metadata': True, 'Version': '1.0'}}, 'EmbeddedDevelopment': {'Version': '2.16'}, 'Firmware': {'BuildDate': 'Feb 15 2019 09:42', 'BuildNumber': 26, 'Version': '9.10.1'}, 'Image': {'Format': 'jpeg,mjpeg,h264', 'NbrOfViews': 2, 'Resolution': '1920x1080,1280x960,1280x720,1024x768,1024x576,800x600,640x480,640x360,352x240,320x240', 'Rotation': '0,180'}, 'System': {'SerialNumber': '00408C123456'}}), ('Input', {'NbrOfInputs': 1}), ('IOPort', {'I0': {'Configurable': False, 'Direction': 'input', 'Input': {'Name': 'PIR sensor', 'Trig': 'closed'}}}), ('Output', {'NbrOfOutputs': 0}), ('StreamProfile', {'MaxGroups': 26, 'S0': {'Description': 'profile_1_description', 'Name': 'profile_1', 'Parameters': 'videocodec=h264'}, 'S1': {'Description': 'profile_2_description', 'Name': 'profile_2', 'Parameters': 'videocodec=h265'}})])",
|
||||||
'I0.Direction': 'input',
|
|
||||||
'I0.Input.Name': 'PIR sensor',
|
|
||||||
'I0.Input.Trig': 'closed',
|
|
||||||
}),
|
|
||||||
'root.Input': dict({
|
|
||||||
'NbrOfInputs': '1',
|
|
||||||
}),
|
|
||||||
'root.Output': dict({
|
|
||||||
'NbrOfOutputs': '0',
|
|
||||||
}),
|
|
||||||
'root.Properties': dict({
|
|
||||||
'API.HTTP.Version': '3',
|
|
||||||
'API.Metadata.Metadata': 'yes',
|
|
||||||
'API.Metadata.Version': '1.0',
|
|
||||||
'EmbeddedDevelopment.Version': '2.16',
|
|
||||||
'Firmware.BuildDate': 'Feb 15 2019 09:42',
|
|
||||||
'Firmware.BuildNumber': '26',
|
|
||||||
'Firmware.Version': '9.10.1',
|
|
||||||
'Image.Format': 'jpeg,mjpeg,h264',
|
|
||||||
'Image.NbrOfViews': '2',
|
|
||||||
'Image.Resolution': '1920x1080,1280x960,1280x720,1024x768,1024x576,800x600,640x480,640x360,352x240,320x240',
|
|
||||||
'Image.Rotation': '0,180',
|
|
||||||
'System.SerialNumber': '**REDACTED**',
|
|
||||||
}),
|
|
||||||
'root.StreamProfile': dict({
|
|
||||||
'MaxGroups': '26',
|
|
||||||
'S0.Description': 'profile_1_description',
|
|
||||||
'S0.Name': 'profile_1',
|
|
||||||
'S0.Parameters': 'videocodec=h264',
|
|
||||||
'S1.Description': 'profile_2_description',
|
|
||||||
'S1.Name': 'profile_2',
|
|
||||||
'S1.Parameters': 'videocodec=h265',
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
"""Axis camera platform tests."""
|
"""Axis camera platform tests."""
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ from homeassistant.const import STATE_IDLE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .const import NAME
|
from .const import MAC, NAME
|
||||||
|
|
||||||
|
|
||||||
async def test_platform_manually_configured(hass: HomeAssistant) -> None:
|
async def test_platform_manually_configured(hass: HomeAssistant) -> None:
|
||||||
|
@ -72,9 +71,19 @@ async def test_camera_with_stream_profile(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
property_data = f"""root.Properties.API.HTTP.Version=3
|
||||||
|
root.Properties.API.Metadata.Metadata=yes
|
||||||
|
root.Properties.API.Metadata.Version=1.0
|
||||||
|
root.Properties.EmbeddedDevelopment.Version=2.16
|
||||||
|
root.Properties.Firmware.BuildDate=Feb 15 2019 09:42
|
||||||
|
root.Properties.Firmware.BuildNumber=26
|
||||||
|
root.Properties.Firmware.Version=9.10.1
|
||||||
|
root.Properties.System.SerialNumber={MAC}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("param_properties_payload", [property_data])
|
||||||
async def test_camera_disabled(hass: HomeAssistant, prepare_config_entry) -> None:
|
async def test_camera_disabled(hass: HomeAssistant, prepare_config_entry) -> None:
|
||||||
"""Test that Axis camera platform is loaded properly but does not create camera entity."""
|
"""Test that Axis camera platform is loaded properly but does not create camera entity."""
|
||||||
with patch("axis.vapix.vapix.Params.image_format", new=None):
|
await prepare_config_entry()
|
||||||
await prepare_config_entry()
|
|
||||||
|
|
||||||
assert len(hass.states.async_entity_ids(CAMERA_DOMAIN)) == 0
|
assert len(hass.states.async_entity_ids(CAMERA_DOMAIN)) == 0
|
||||||
|
|
|
@ -227,7 +227,7 @@ async def test_shutdown(config) -> None:
|
||||||
async def test_get_device_fails(hass: HomeAssistant, config) -> None:
|
async def test_get_device_fails(hass: HomeAssistant, config) -> None:
|
||||||
"""Device unauthorized yields authentication required error."""
|
"""Device unauthorized yields authentication required error."""
|
||||||
with patch(
|
with patch(
|
||||||
"axis.vapix.vapix.Vapix.request", side_effect=axislib.Unauthorized
|
"axis.vapix.vapix.Vapix.initialize", side_effect=axislib.Unauthorized
|
||||||
), pytest.raises(axis.errors.AuthenticationRequired):
|
), pytest.raises(axis.errors.AuthenticationRequired):
|
||||||
await axis.device.get_axis_device(hass, config)
|
await axis.device.get_axis_device(hass, config)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Axis light platform tests."""
|
"""Axis light platform tests."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from axis.vapix.models.api import CONTEXT
|
||||||
import pytest
|
import pytest
|
||||||
import respx
|
import respx
|
||||||
|
|
||||||
|
@ -49,10 +50,18 @@ def light_control_fixture(light_control_items):
|
||||||
"""Light control mock response."""
|
"""Light control mock response."""
|
||||||
data = {
|
data = {
|
||||||
"apiVersion": "1.1",
|
"apiVersion": "1.1",
|
||||||
|
"context": CONTEXT,
|
||||||
"method": "getLightInformation",
|
"method": "getLightInformation",
|
||||||
"data": {"items": light_control_items},
|
"data": {"items": light_control_items},
|
||||||
}
|
}
|
||||||
respx.post(f"http://{DEFAULT_HOST}:80/axis-cgi/lightcontrol.cgi").respond(
|
respx.post(
|
||||||
|
f"http://{DEFAULT_HOST}:80/axis-cgi/lightcontrol.cgi",
|
||||||
|
json={
|
||||||
|
"apiVersion": "1.1",
|
||||||
|
"context": CONTEXT,
|
||||||
|
"method": "getLightInformation",
|
||||||
|
},
|
||||||
|
).respond(
|
||||||
json=data,
|
json=data,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -90,24 +99,56 @@ async def test_no_light_entity_without_light_control_representation(
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_LIGHT_CONTROL])
|
@pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_LIGHT_CONTROL])
|
||||||
async def test_lights(hass: HomeAssistant, setup_config_entry, mock_rtsp_event) -> None:
|
async def test_lights(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
respx_mock,
|
||||||
|
setup_config_entry,
|
||||||
|
mock_rtsp_event,
|
||||||
|
api_discovery_items,
|
||||||
|
) -> None:
|
||||||
"""Test that lights are loaded properly."""
|
"""Test that lights are loaded properly."""
|
||||||
# Add light
|
# Add light
|
||||||
with patch(
|
respx.post(
|
||||||
"axis.vapix.interfaces.light_control.LightControl.get_current_intensity",
|
f"http://{DEFAULT_HOST}:80/axis-cgi/lightcontrol.cgi",
|
||||||
return_value={"data": {"intensity": 100}},
|
json={
|
||||||
), patch(
|
"apiVersion": "1.1",
|
||||||
"axis.vapix.interfaces.light_control.LightControl.get_valid_intensity",
|
"context": CONTEXT,
|
||||||
return_value={"data": {"ranges": [{"high": 150}]}},
|
"method": "getCurrentIntensity",
|
||||||
):
|
"params": {"lightID": "led0"},
|
||||||
mock_rtsp_event(
|
},
|
||||||
topic="tns1:Device/tnsaxis:Light/Status",
|
).respond(
|
||||||
data_type="state",
|
json={
|
||||||
data_value="ON",
|
"apiVersion": "1.1",
|
||||||
source_name="id",
|
"context": "Axis library",
|
||||||
source_idx="0",
|
"method": "getCurrentIntensity",
|
||||||
)
|
"data": {"intensity": 100},
|
||||||
await hass.async_block_till_done()
|
},
|
||||||
|
)
|
||||||
|
respx.post(
|
||||||
|
f"http://{DEFAULT_HOST}:80/axis-cgi/lightcontrol.cgi",
|
||||||
|
json={
|
||||||
|
"apiVersion": "1.1",
|
||||||
|
"context": CONTEXT,
|
||||||
|
"method": "getValidIntensity",
|
||||||
|
"params": {"lightID": "led0"},
|
||||||
|
},
|
||||||
|
).respond(
|
||||||
|
json={
|
||||||
|
"apiVersion": "1.1",
|
||||||
|
"context": "Axis library",
|
||||||
|
"method": "getValidIntensity",
|
||||||
|
"data": {"ranges": [{"low": 0, "high": 150}]},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_rtsp_event(
|
||||||
|
topic="tns1:Device/tnsaxis:Light/Status",
|
||||||
|
data_type="state",
|
||||||
|
data_value="ON",
|
||||||
|
source_name="id",
|
||||||
|
source_idx="0",
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(hass.states.async_entity_ids(LIGHT_DOMAIN)) == 1
|
assert len(hass.states.async_entity_ids(LIGHT_DOMAIN)) == 1
|
||||||
|
|
||||||
|
@ -118,14 +159,9 @@ async def test_lights(hass: HomeAssistant, setup_config_entry, mock_rtsp_event)
|
||||||
assert light_0.name == f"{NAME} IR Light 0"
|
assert light_0.name == f"{NAME} IR Light 0"
|
||||||
|
|
||||||
# Turn on, set brightness, light already on
|
# Turn on, set brightness, light already on
|
||||||
with patch(
|
with patch("axis.vapix.vapix.LightHandler.activate_light") as mock_activate, patch(
|
||||||
"axis.vapix.interfaces.light_control.LightControl.activate_light"
|
"axis.vapix.vapix.LightHandler.set_manual_intensity"
|
||||||
) as mock_activate, patch(
|
) as mock_set_intensity:
|
||||||
"axis.vapix.interfaces.light_control.LightControl.set_manual_intensity"
|
|
||||||
) as mock_set_intensity, patch(
|
|
||||||
"axis.vapix.interfaces.light_control.LightControl.get_current_intensity",
|
|
||||||
return_value={"data": {"intensity": 100}},
|
|
||||||
):
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
@ -136,12 +172,7 @@ async def test_lights(hass: HomeAssistant, setup_config_entry, mock_rtsp_event)
|
||||||
mock_set_intensity.assert_called_once_with("led0", 29)
|
mock_set_intensity.assert_called_once_with("led0", 29)
|
||||||
|
|
||||||
# Turn off
|
# Turn off
|
||||||
with patch(
|
with patch("axis.vapix.vapix.LightHandler.deactivate_light") as mock_deactivate:
|
||||||
"axis.vapix.interfaces.light_control.LightControl.deactivate_light"
|
|
||||||
) as mock_deactivate, patch(
|
|
||||||
"axis.vapix.interfaces.light_control.LightControl.get_current_intensity",
|
|
||||||
return_value={"data": {"intensity": 100}},
|
|
||||||
):
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
|
@ -164,14 +195,9 @@ async def test_lights(hass: HomeAssistant, setup_config_entry, mock_rtsp_event)
|
||||||
assert light_0.state == STATE_OFF
|
assert light_0.state == STATE_OFF
|
||||||
|
|
||||||
# Turn on, set brightness
|
# Turn on, set brightness
|
||||||
with patch(
|
with patch("axis.vapix.vapix.LightHandler.activate_light") as mock_activate, patch(
|
||||||
"axis.vapix.interfaces.light_control.LightControl.activate_light"
|
"axis.vapix.vapix.LightHandler.set_manual_intensity"
|
||||||
) as mock_activate, patch(
|
) as mock_set_intensity:
|
||||||
"axis.vapix.interfaces.light_control.LightControl.set_manual_intensity"
|
|
||||||
) as mock_set_intensity, patch(
|
|
||||||
"axis.vapix.interfaces.light_control.LightControl.get_current_intensity",
|
|
||||||
return_value={"data": {"intensity": 100}},
|
|
||||||
):
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
@ -182,12 +208,7 @@ async def test_lights(hass: HomeAssistant, setup_config_entry, mock_rtsp_event)
|
||||||
mock_set_intensity.assert_not_called()
|
mock_set_intensity.assert_not_called()
|
||||||
|
|
||||||
# Turn off, light already off
|
# Turn off, light already off
|
||||||
with patch(
|
with patch("axis.vapix.vapix.LightHandler.deactivate_light") as mock_deactivate:
|
||||||
"axis.vapix.interfaces.light_control.LightControl.deactivate_light"
|
|
||||||
) as mock_deactivate, patch(
|
|
||||||
"axis.vapix.interfaces.light_control.LightControl.get_current_intensity",
|
|
||||||
return_value={"data": {"intensity": 100}},
|
|
||||||
):
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Axis switch platform tests."""
|
"""Axis switch platform tests."""
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from axis.vapix.models.api import CONTEXT
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN
|
from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN
|
||||||
|
@ -32,18 +33,22 @@ async def test_no_switches(hass: HomeAssistant, setup_config_entry) -> None:
|
||||||
assert not hass.states.async_entity_ids(SWITCH_DOMAIN)
|
assert not hass.states.async_entity_ids(SWITCH_DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
PORT_DATA = """root.IOPort.I0.Configurable=yes
|
||||||
|
root.IOPort.I0.Direction=output
|
||||||
|
root.IOPort.I0.Output.Name=Doorbell
|
||||||
|
root.IOPort.I0.Output.Active=closed
|
||||||
|
root.IOPort.I1.Configurable=yes
|
||||||
|
root.IOPort.I1.Direction=output
|
||||||
|
root.IOPort.I1.Output.Name=
|
||||||
|
root.IOPort.I1.Output.Active=open
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("param_ports_payload", [PORT_DATA])
|
||||||
async def test_switches_with_port_cgi(
|
async def test_switches_with_port_cgi(
|
||||||
hass: HomeAssistant, setup_config_entry, mock_rtsp_event
|
hass: HomeAssistant, setup_config_entry, mock_rtsp_event
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that switches are loaded properly using port.cgi."""
|
"""Test that switches are loaded properly using port.cgi."""
|
||||||
device = hass.data[AXIS_DOMAIN][setup_config_entry.entry_id]
|
|
||||||
|
|
||||||
device.api.vapix.ports = {"0": AsyncMock(), "1": AsyncMock()}
|
|
||||||
device.api.vapix.ports["0"].name = "Doorbell"
|
|
||||||
device.api.vapix.ports["0"].open = AsyncMock()
|
|
||||||
device.api.vapix.ports["0"].close = AsyncMock()
|
|
||||||
device.api.vapix.ports["1"].name = ""
|
|
||||||
|
|
||||||
mock_rtsp_event(
|
mock_rtsp_event(
|
||||||
topic="tns1:Device/Trigger/Relay",
|
topic="tns1:Device/Trigger/Relay",
|
||||||
data_type="LogicalState",
|
data_type="LogicalState",
|
||||||
|
@ -72,36 +77,61 @@ async def test_switches_with_port_cgi(
|
||||||
assert relay_0.state == STATE_OFF
|
assert relay_0.state == STATE_OFF
|
||||||
assert relay_0.name == f"{NAME} Doorbell"
|
assert relay_0.name == f"{NAME} Doorbell"
|
||||||
|
|
||||||
await hass.services.async_call(
|
with patch("axis.vapix.vapix.Ports.close") as mock_turn_on:
|
||||||
SWITCH_DOMAIN,
|
await hass.services.async_call(
|
||||||
SERVICE_TURN_ON,
|
SWITCH_DOMAIN,
|
||||||
{ATTR_ENTITY_ID: entity_id},
|
SERVICE_TURN_ON,
|
||||||
blocking=True,
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
)
|
blocking=True,
|
||||||
device.api.vapix.ports["0"].close.assert_called_once()
|
)
|
||||||
|
mock_turn_on.assert_called_once_with("0")
|
||||||
|
|
||||||
await hass.services.async_call(
|
with patch("axis.vapix.vapix.Ports.open") as mock_turn_off:
|
||||||
SWITCH_DOMAIN,
|
await hass.services.async_call(
|
||||||
SERVICE_TURN_OFF,
|
SWITCH_DOMAIN,
|
||||||
{ATTR_ENTITY_ID: entity_id},
|
SERVICE_TURN_OFF,
|
||||||
blocking=True,
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
)
|
blocking=True,
|
||||||
device.api.vapix.ports["0"].open.assert_called_once()
|
)
|
||||||
|
mock_turn_off.assert_called_once_with("0")
|
||||||
|
|
||||||
|
|
||||||
|
PORT_MANAGEMENT_RESPONSE = {
|
||||||
|
"apiVersion": "1.0",
|
||||||
|
"method": "getPorts",
|
||||||
|
"context": CONTEXT,
|
||||||
|
"data": {
|
||||||
|
"numberOfPorts": 2,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"port": "0",
|
||||||
|
"configurable": True,
|
||||||
|
"usage": "",
|
||||||
|
"name": "Doorbell",
|
||||||
|
"direction": "output",
|
||||||
|
"state": "open",
|
||||||
|
"normalState": "open",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": "1",
|
||||||
|
"configurable": True,
|
||||||
|
"usage": "",
|
||||||
|
"name": "",
|
||||||
|
"direction": "output",
|
||||||
|
"state": "open",
|
||||||
|
"normalState": "open",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_PORT_MANAGEMENT])
|
@pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_PORT_MANAGEMENT])
|
||||||
|
@pytest.mark.parametrize("port_management_payload", [PORT_MANAGEMENT_RESPONSE])
|
||||||
async def test_switches_with_port_management(
|
async def test_switches_with_port_management(
|
||||||
hass: HomeAssistant, setup_config_entry, mock_rtsp_event
|
hass: HomeAssistant, setup_config_entry, mock_rtsp_event
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that switches are loaded properly using port management."""
|
"""Test that switches are loaded properly using port management."""
|
||||||
device = hass.data[AXIS_DOMAIN][setup_config_entry.entry_id]
|
|
||||||
|
|
||||||
device.api.vapix.ports = {"0": AsyncMock(), "1": AsyncMock()}
|
|
||||||
device.api.vapix.ports["0"].name = "Doorbell"
|
|
||||||
device.api.vapix.ports["0"].open = AsyncMock()
|
|
||||||
device.api.vapix.ports["0"].close = AsyncMock()
|
|
||||||
device.api.vapix.ports["1"].name = ""
|
|
||||||
|
|
||||||
mock_rtsp_event(
|
mock_rtsp_event(
|
||||||
topic="tns1:Device/Trigger/Relay",
|
topic="tns1:Device/Trigger/Relay",
|
||||||
data_type="LogicalState",
|
data_type="LogicalState",
|
||||||
|
@ -143,18 +173,20 @@ async def test_switches_with_port_management(
|
||||||
|
|
||||||
assert hass.states.get(f"{SWITCH_DOMAIN}.{NAME}_relay_1").state == STATE_ON
|
assert hass.states.get(f"{SWITCH_DOMAIN}.{NAME}_relay_1").state == STATE_ON
|
||||||
|
|
||||||
await hass.services.async_call(
|
with patch("axis.vapix.vapix.IoPortManagement.close") as mock_turn_on:
|
||||||
SWITCH_DOMAIN,
|
await hass.services.async_call(
|
||||||
SERVICE_TURN_ON,
|
SWITCH_DOMAIN,
|
||||||
{ATTR_ENTITY_ID: entity_id},
|
SERVICE_TURN_ON,
|
||||||
blocking=True,
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
)
|
blocking=True,
|
||||||
device.api.vapix.ports["0"].close.assert_called_once()
|
)
|
||||||
|
mock_turn_on.assert_called_once_with("0")
|
||||||
|
|
||||||
await hass.services.async_call(
|
with patch("axis.vapix.vapix.IoPortManagement.open") as mock_turn_off:
|
||||||
SWITCH_DOMAIN,
|
await hass.services.async_call(
|
||||||
SERVICE_TURN_OFF,
|
SWITCH_DOMAIN,
|
||||||
{ATTR_ENTITY_ID: entity_id},
|
SERVICE_TURN_OFF,
|
||||||
blocking=True,
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
)
|
blocking=True,
|
||||||
device.api.vapix.ports["0"].open.assert_called_once()
|
)
|
||||||
|
mock_turn_off.assert_called_once_with("0")
|
||||||
|
|
Loading…
Add table
Reference in a new issue