Follow Axis library changes and improve tests (#44126)
This commit is contained in:
parent
ff3a1f2050
commit
6325bc8bfe
10 changed files with 149 additions and 78 deletions
|
@ -7,10 +7,12 @@ from axis.event_stream import (
|
|||
CLASS_LIGHT,
|
||||
CLASS_MOTION,
|
||||
CLASS_OUTPUT,
|
||||
CLASS_PTZ,
|
||||
CLASS_SOUND,
|
||||
FenceGuard,
|
||||
LoiteringGuard,
|
||||
MotionGuard,
|
||||
ObjectAnalytics,
|
||||
Vmd4,
|
||||
)
|
||||
|
||||
|
@ -46,7 +48,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
"""Add binary sensor from Axis device."""
|
||||
event = device.api.event[event_id]
|
||||
|
||||
if event.CLASS != CLASS_OUTPUT and not (
|
||||
if event.CLASS not in (CLASS_OUTPUT, CLASS_PTZ) and not (
|
||||
event.CLASS == CLASS_LIGHT and event.TYPE == "Light"
|
||||
):
|
||||
async_add_entities([AxisBinarySensor(event, device)])
|
||||
|
@ -101,7 +103,7 @@ class AxisBinarySensor(AxisEventBase, BinarySensorEntity):
|
|||
"""Return the name of the event."""
|
||||
if (
|
||||
self.event.CLASS == CLASS_INPUT
|
||||
and self.event.id
|
||||
and self.event.id in self.device.api.vapix.ports
|
||||
and self.device.api.vapix.ports[self.event.id].name
|
||||
):
|
||||
return (
|
||||
|
@ -114,6 +116,7 @@ class AxisBinarySensor(AxisEventBase, BinarySensorEntity):
|
|||
(FenceGuard, self.device.api.vapix.fence_guard),
|
||||
(LoiteringGuard, self.device.api.vapix.loitering_guard),
|
||||
(MotionGuard, self.device.api.vapix.motion_guard),
|
||||
(ObjectAnalytics, self.device.api.vapix.object_analytics),
|
||||
(Vmd4, self.device.api.vapix.vmd4),
|
||||
):
|
||||
if (
|
||||
|
|
|
@ -25,6 +25,7 @@ from homeassistant.core import HomeAssistant, callback
|
|||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.setup import async_when_setup
|
||||
|
||||
from .const import (
|
||||
|
@ -177,7 +178,7 @@ class AxisNetworkDevice:
|
|||
self.disconnect_from_stream()
|
||||
|
||||
event = mqtt_json_to_event(message.payload)
|
||||
self.api.event.process_event(event)
|
||||
self.api.event.update([event])
|
||||
|
||||
# Setup and teardown methods
|
||||
|
||||
|
@ -195,8 +196,10 @@ class AxisNetworkDevice:
|
|||
except CannotConnect as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
LOGGER.error("Unknown error connecting with Axis device on %s", self.host)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
LOGGER.error(
|
||||
"Unknown error connecting with Axis device (%s): %s", self.host, err
|
||||
)
|
||||
return False
|
||||
|
||||
self.fw_version = self.api.vapix.firmware_version
|
||||
|
@ -239,12 +242,10 @@ class AxisNetworkDevice:
|
|||
async def shutdown(self, event):
|
||||
"""Stop the event stream."""
|
||||
self.disconnect_from_stream()
|
||||
await self.api.vapix.close()
|
||||
|
||||
async def async_reset(self):
|
||||
"""Reset this device to default state."""
|
||||
self.disconnect_from_stream()
|
||||
await self.api.vapix.close()
|
||||
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
|
@ -267,9 +268,10 @@ class AxisNetworkDevice:
|
|||
|
||||
async def get_device(hass, host, port, username, password):
|
||||
"""Create a Axis device."""
|
||||
session = get_async_client(hass, verify_ssl=False)
|
||||
|
||||
device = axis.AxisDevice(
|
||||
Configuration(host, port=port, username=username, password=password)
|
||||
Configuration(session, host, port=port, username=username, password=password)
|
||||
)
|
||||
|
||||
try:
|
||||
|
@ -280,15 +282,12 @@ async def get_device(hass, host, port, username, password):
|
|||
|
||||
except axis.Unauthorized as err:
|
||||
LOGGER.warning("Connected to device at %s but not registered.", host)
|
||||
await device.vapix.close()
|
||||
raise AuthenticationRequired from err
|
||||
|
||||
except (asyncio.TimeoutError, axis.RequestError) as err:
|
||||
LOGGER.error("Error connecting to the Axis device at %s", host)
|
||||
await device.vapix.close()
|
||||
raise CannotConnect from err
|
||||
|
||||
except axis.AxisException as err:
|
||||
LOGGER.exception("Unknown Axis communication error occurred")
|
||||
await device.vapix.close()
|
||||
raise AuthenticationRequired from err
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Axis",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/axis",
|
||||
"requirements": ["axis==41"],
|
||||
"requirements": ["axis==42"],
|
||||
"zeroconf": [
|
||||
{ "type": "_axis-video._tcp.local.", "macaddress": "00408C*" },
|
||||
{ "type": "_axis-video._tcp.local.", "macaddress": "ACCC8E*" },
|
||||
|
|
|
@ -306,7 +306,7 @@ av==8.0.2
|
|||
# avion==0.10
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==41
|
||||
axis==42
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.1.0
|
||||
|
|
|
@ -177,7 +177,7 @@ auroranoaa==0.0.2
|
|||
av==8.0.2
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==41
|
||||
axis==42
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.1.0
|
||||
|
|
|
@ -19,6 +19,14 @@ EVENTS = [
|
|||
"type": "state",
|
||||
"value": "0",
|
||||
},
|
||||
{
|
||||
"operation": "Initialized",
|
||||
"topic": "tns1:PTZController/tnsaxis:PTZPresets/Channel_1",
|
||||
"source": "PresetToken",
|
||||
"source_idx": "0",
|
||||
"type": "on_preset",
|
||||
"value": "1",
|
||||
},
|
||||
{
|
||||
"operation": "Initialized",
|
||||
"topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1Profile1",
|
||||
|
@ -54,8 +62,7 @@ async def test_binary_sensors(hass):
|
|||
config_entry = await setup_axis_integration(hass)
|
||||
device = hass.data[AXIS_DOMAIN][config_entry.unique_id]
|
||||
|
||||
for event in EVENTS:
|
||||
device.api.event.process_event(event)
|
||||
device.api.event.update(EVENTS)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 2
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Test Axis config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import respx
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.axis import config_flow
|
||||
from homeassistant.components.axis.const import (
|
||||
|
@ -25,7 +27,13 @@ from homeassistant.data_entry_flow import (
|
|||
RESULT_TYPE_FORM,
|
||||
)
|
||||
|
||||
from .test_device import MAC, MODEL, NAME, setup_axis_integration, vapix_request
|
||||
from .test_device import (
|
||||
MAC,
|
||||
MODEL,
|
||||
NAME,
|
||||
mock_default_vapix_requests,
|
||||
setup_axis_integration,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
@ -41,7 +49,8 @@ async def test_flow_manual_configuration(hass):
|
|||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == SOURCE_USER
|
||||
|
||||
with patch("axis.vapix.Vapix.request", new=vapix_request):
|
||||
with respx.mock:
|
||||
mock_default_vapix_requests(respx)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
|
@ -80,7 +89,8 @@ async def test_manual_configuration_update_configuration(hass):
|
|||
with patch(
|
||||
"homeassistant.components.axis.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry, patch("axis.vapix.Vapix.request", new=vapix_request):
|
||||
) as mock_setup_entry, respx.mock:
|
||||
mock_default_vapix_requests(respx, "2.3.4.5")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
|
@ -109,7 +119,8 @@ async def test_flow_fails_already_configured(hass):
|
|||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == SOURCE_USER
|
||||
|
||||
with patch("axis.vapix.Vapix.request", new=vapix_request):
|
||||
with respx.mock:
|
||||
mock_default_vapix_requests(respx)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
|
@ -196,7 +207,8 @@ async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass):
|
|||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == SOURCE_USER
|
||||
|
||||
with patch("axis.vapix.Vapix.request", new=vapix_request):
|
||||
with respx.mock:
|
||||
mock_default_vapix_requests(respx)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
|
@ -238,7 +250,8 @@ async def test_zeroconf_flow(hass):
|
|||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == SOURCE_USER
|
||||
|
||||
with patch("axis.vapix.Vapix.request", new=vapix_request):
|
||||
with respx.mock:
|
||||
mock_default_vapix_requests(respx)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
|
@ -304,7 +317,8 @@ async def test_zeroconf_flow_updated_configuration(hass):
|
|||
with patch(
|
||||
"homeassistant.components.axis.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry, patch("axis.vapix.Vapix.request", new=vapix_request):
|
||||
) as mock_setup_entry, respx.mock:
|
||||
mock_default_vapix_requests(respx, "2.3.4.5")
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
AXIS_DOMAIN,
|
||||
data={
|
||||
|
|
|
@ -1,26 +1,12 @@
|
|||
"""Test Axis device."""
|
||||
from copy import deepcopy
|
||||
from unittest import mock
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import axis as axislib
|
||||
from axis.api_discovery import URL as API_DISCOVERY_URL
|
||||
from axis.applications import URL_LIST as APPLICATIONS_URL
|
||||
from axis.applications.vmd4 import URL as VMD4_URL
|
||||
from axis.basic_device_info import URL as BASIC_DEVICE_INFO_URL
|
||||
from axis.event_stream import OPERATION_INITIALIZED
|
||||
from axis.light_control import URL as LIGHT_CONTROL_URL
|
||||
from axis.mqtt import URL_CLIENT as MQTT_CLIENT_URL
|
||||
from axis.param_cgi import (
|
||||
BRAND as BRAND_URL,
|
||||
INPUT as INPUT_URL,
|
||||
IOPORT as IOPORT_URL,
|
||||
OUTPUT as OUTPUT_URL,
|
||||
PROPERTIES as PROPERTIES_URL,
|
||||
STREAM_PROFILES as STREAM_PROFILES_URL,
|
||||
)
|
||||
from axis.port_management import URL as PORT_MANAGEMENT_URL
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import axis
|
||||
|
@ -47,10 +33,12 @@ MAC = "00408C12345"
|
|||
MODEL = "model"
|
||||
NAME = "name"
|
||||
|
||||
DEFAULT_HOST = "1.2.3.4"
|
||||
|
||||
ENTRY_OPTIONS = {CONF_EVENTS: True}
|
||||
|
||||
ENTRY_CONFIG = {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
CONF_HOST: DEFAULT_HOST,
|
||||
CONF_USERNAME: "root",
|
||||
CONF_PASSWORD: "pass",
|
||||
CONF_PORT: 80,
|
||||
|
@ -166,6 +154,14 @@ root.Brand.ProdVariant=
|
|||
root.Brand.WebURL=http://www.axis.com
|
||||
"""
|
||||
|
||||
IMAGE_RESPONSE = """root.Image.I0.Enabled=yes
|
||||
root.Image.I0.Name=View Area 1
|
||||
root.Image.I0.Source=0
|
||||
root.Image.I1.Enabled=no
|
||||
root.Image.I1.Name=View Area 2
|
||||
root.Image.I1.Source=0
|
||||
"""
|
||||
|
||||
PORTS_RESPONSE = """root.Input.NbrOfInputs=1
|
||||
root.IOPort.I0.Configurable=no
|
||||
root.IOPort.I0.Direction=input
|
||||
|
@ -188,6 +184,9 @@ root.Properties.Image.Rotation=0,180
|
|||
root.Properties.System.SerialNumber=00408C12345
|
||||
"""
|
||||
|
||||
PTZ_RESPONSE = ""
|
||||
|
||||
|
||||
STREAM_PROFILES_RESPONSE = """root.StreamProfile.MaxGroups=26
|
||||
root.StreamProfile.S0.Description=profile_1_description
|
||||
root.StreamProfile.S0.Name=profile_1
|
||||
|
@ -197,31 +196,85 @@ root.StreamProfile.S1.Name=profile_2
|
|||
root.StreamProfile.S1.Parameters=videocodec=h265
|
||||
"""
|
||||
|
||||
VIEW_AREAS_RESPONSE = {"apiVersion": "1.0", "method": "list", "data": {"viewAreas": []}}
|
||||
|
||||
async def vapix_request(self, session, url, **kwargs):
|
||||
"""Return data based on url."""
|
||||
if API_DISCOVERY_URL in url:
|
||||
return API_DISCOVERY_RESPONSE
|
||||
if APPLICATIONS_URL in url:
|
||||
return APPLICATIONS_LIST_RESPONSE
|
||||
if BASIC_DEVICE_INFO_URL in url:
|
||||
return BASIC_DEVICE_INFO_RESPONSE
|
||||
if LIGHT_CONTROL_URL in url:
|
||||
return LIGHT_CONTROL_RESPONSE
|
||||
if MQTT_CLIENT_URL in url:
|
||||
return MQTT_CLIENT_RESPONSE
|
||||
if PORT_MANAGEMENT_URL in url:
|
||||
return PORT_MANAGEMENT_RESPONSE
|
||||
if VMD4_URL in url:
|
||||
return VMD4_RESPONSE
|
||||
if BRAND_URL in url:
|
||||
return BRAND_RESPONSE
|
||||
if IOPORT_URL in url or INPUT_URL in url or OUTPUT_URL in url:
|
||||
return PORTS_RESPONSE
|
||||
if PROPERTIES_URL in url:
|
||||
return PROPERTIES_RESPONSE
|
||||
if STREAM_PROFILES_URL in url:
|
||||
return STREAM_PROFILES_RESPONSE
|
||||
|
||||
def mock_default_vapix_requests(respx: respx, host: str = DEFAULT_HOST) -> None:
|
||||
"""Mock default Vapix requests responses."""
|
||||
respx.post(f"http://{host}:80/axis-cgi/apidiscovery.cgi").respond(
|
||||
json=API_DISCOVERY_RESPONSE,
|
||||
)
|
||||
respx.post(f"http://{host}:80/axis-cgi/basicdeviceinfo.cgi").respond(
|
||||
json=BASIC_DEVICE_INFO_RESPONSE,
|
||||
)
|
||||
respx.post(f"http://{host}:80/axis-cgi/io/portmanagement.cgi").respond(
|
||||
json=PORT_MANAGEMENT_RESPONSE,
|
||||
)
|
||||
respx.post(f"http://{host}:80/axis-cgi/lightcontrol.cgi").respond(
|
||||
json=LIGHT_CONTROL_RESPONSE,
|
||||
)
|
||||
respx.post(f"http://{host}:80/axis-cgi/mqtt/client.cgi").respond(
|
||||
json=MQTT_CLIENT_RESPONSE,
|
||||
)
|
||||
respx.post(f"http://{host}:80/axis-cgi/streamprofile.cgi").respond(
|
||||
json=STREAM_PROFILES_RESPONSE,
|
||||
)
|
||||
respx.post(f"http://{host}:80/axis-cgi/viewarea/info.cgi").respond(
|
||||
json=VIEW_AREAS_RESPONSE
|
||||
)
|
||||
respx.get(
|
||||
f"http://{host}:80/axis-cgi/param.cgi?action=list&group=root.Brand"
|
||||
).respond(
|
||||
text=BRAND_RESPONSE,
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
respx.get(
|
||||
f"http://{host}:80/axis-cgi/param.cgi?action=list&group=root.Image"
|
||||
).respond(
|
||||
text=IMAGE_RESPONSE,
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
respx.get(
|
||||
f"http://{host}:80/axis-cgi/param.cgi?action=list&group=root.Input"
|
||||
).respond(
|
||||
text=PORTS_RESPONSE,
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
respx.get(
|
||||
f"http://{host}:80/axis-cgi/param.cgi?action=list&group=root.IOPort"
|
||||
).respond(
|
||||
text=PORTS_RESPONSE,
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
respx.get(
|
||||
f"http://{host}:80/axis-cgi/param.cgi?action=list&group=root.Output"
|
||||
).respond(
|
||||
text=PORTS_RESPONSE,
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
respx.get(
|
||||
f"http://{host}:80/axis-cgi/param.cgi?action=list&group=root.Properties"
|
||||
).respond(
|
||||
text=PROPERTIES_RESPONSE,
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
respx.get(
|
||||
f"http://{host}:80/axis-cgi/param.cgi?action=list&group=root.PTZ"
|
||||
).respond(
|
||||
text=PTZ_RESPONSE,
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
respx.get(
|
||||
f"http://{host}:80/axis-cgi/param.cgi?action=list&group=root.StreamProfile"
|
||||
).respond(
|
||||
text=STREAM_PROFILES_RESPONSE,
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
respx.post(f"http://{host}:80/axis-cgi/applications/list.cgi").respond(
|
||||
text=APPLICATIONS_LIST_RESPONSE,
|
||||
headers={"Content-Type": "text/xml"},
|
||||
)
|
||||
respx.post(f"http://{host}:80/local/vmd/control.cgi").respond(json=VMD4_RESPONSE)
|
||||
|
||||
|
||||
async def setup_axis_integration(hass, config=ENTRY_CONFIG, options=ENTRY_OPTIONS):
|
||||
|
@ -235,10 +288,8 @@ async def setup_axis_integration(hass, config=ENTRY_CONFIG, options=ENTRY_OPTION
|
|||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
with patch("axis.vapix.Vapix.request", new=vapix_request), patch(
|
||||
"axis.rtsp.RTSPClient.start",
|
||||
return_value=True,
|
||||
):
|
||||
with patch("axis.rtsp.RTSPClient.start", return_value=True), respx.mock:
|
||||
mock_default_vapix_requests(respx)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -317,10 +368,11 @@ async def test_update_address(hass):
|
|||
device = hass.data[AXIS_DOMAIN][config_entry.unique_id]
|
||||
assert device.api.config.host == "1.2.3.4"
|
||||
|
||||
with patch("axis.vapix.Vapix.request", new=vapix_request), patch(
|
||||
with patch(
|
||||
"homeassistant.components.axis.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
) as mock_setup_entry, respx.mock:
|
||||
mock_default_vapix_requests(respx, "2.3.4.5")
|
||||
await hass.config_entries.flow.async_init(
|
||||
AXIS_DOMAIN,
|
||||
data={
|
||||
|
@ -390,12 +442,10 @@ async def test_shutdown():
|
|||
|
||||
axis_device = axis.device.AxisNetworkDevice(hass, entry)
|
||||
axis_device.api = Mock()
|
||||
axis_device.api.vapix.close = AsyncMock()
|
||||
|
||||
await axis_device.shutdown(None)
|
||||
|
||||
assert len(axis_device.api.stream.stop.mock_calls) == 1
|
||||
assert len(axis_device.api.vapix.close.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_get_device_fails(hass):
|
||||
|
|
|
@ -74,7 +74,7 @@ async def test_lights(hass):
|
|||
"axis.light_control.LightControl.get_valid_intensity",
|
||||
return_value={"data": {"ranges": [{"high": 150}]}},
|
||||
):
|
||||
device.api.event.process_event(EVENT_ON)
|
||||
device.api.event.update([EVENT_ON])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_entity_ids(LIGHT_DOMAIN)) == 1
|
||||
|
@ -119,7 +119,7 @@ async def test_lights(hass):
|
|||
mock_deactivate.assert_called_once()
|
||||
|
||||
# Event turn off light
|
||||
device.api.event.process_event(EVENT_OFF)
|
||||
device.api.event.update([EVENT_OFF])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
light_0 = hass.states.get(entity_id)
|
||||
|
|
|
@ -68,8 +68,7 @@ async def test_switches_with_port_cgi(hass):
|
|||
device.api.vapix.ports["0"].close = AsyncMock()
|
||||
device.api.vapix.ports["1"].name = ""
|
||||
|
||||
for event in EVENTS:
|
||||
device.api.event.process_event(event)
|
||||
device.api.event.update(EVENTS)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2
|
||||
|
@ -116,8 +115,7 @@ async def test_switches_with_port_management(hass):
|
|||
device.api.vapix.ports["0"].close = AsyncMock()
|
||||
device.api.vapix.ports["1"].name = ""
|
||||
|
||||
for event in EVENTS:
|
||||
device.api.event.process_event(event)
|
||||
device.api.event.update(EVENTS)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue