Axis IO-port support (#23312)
Support digital inputs and supervised inputs, digital outputs and relayspull/24015/head
parent
5c346e8fb6
commit
eb912be47a
|
@ -0,0 +1,86 @@
|
|||
"""Base classes for Axis entities."""
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN as AXIS_DOMAIN
|
||||
|
||||
|
||||
class AxisEntityBase(Entity):
|
||||
"""Base common to all Axis entities."""
|
||||
|
||||
def __init__(self, device):
|
||||
"""Initialize the Axis event."""
|
||||
self.device = device
|
||||
self.unsub_dispatcher = []
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe device events."""
|
||||
self.unsub_dispatcher.append(async_dispatcher_connect(
|
||||
self.hass, self.device.event_reachable, self.update_callback))
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Unsubscribe device events when removed."""
|
||||
for unsub_dispatcher in self.unsub_dispatcher:
|
||||
unsub_dispatcher()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if device is available."""
|
||||
return self.device.available
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
return {
|
||||
'identifiers': {(AXIS_DOMAIN, self.device.serial)}
|
||||
}
|
||||
|
||||
@callback
|
||||
def update_callback(self, no_delay=None):
|
||||
"""Update the entities state."""
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
|
||||
class AxisEventBase(AxisEntityBase):
|
||||
"""Base common to all Axis entities from event stream."""
|
||||
|
||||
def __init__(self, event, device):
|
||||
"""Initialize the Axis event."""
|
||||
super().__init__(device)
|
||||
self.event = event
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe sensors events."""
|
||||
self.event.register_callback(self.update_callback)
|
||||
|
||||
await super().async_added_to_hass()
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect device object when removed."""
|
||||
self.event.remove_callback(self.update_callback)
|
||||
|
||||
await super().async_will_remove_from_hass()
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the event."""
|
||||
return self.event.CLASS
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the event."""
|
||||
return '{} {} {}'.format(
|
||||
self.device.name, self.event.TYPE, self.event.id)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this device."""
|
||||
return '{}-{}-{}'.format(
|
||||
self.device.serial, self.event.topic, self.event.id)
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
from datetime import timedelta
|
||||
|
||||
from axis.event_stream import CLASS_INPUT, CLASS_OUTPUT
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import CONF_MAC, CONF_TRIGGER_TIME
|
||||
from homeassistant.core import callback
|
||||
|
@ -9,7 +11,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .const import DOMAIN as AXIS_DOMAIN, LOGGER
|
||||
from .axis_base import AxisEventBase
|
||||
from .const import DOMAIN as AXIS_DOMAIN
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
@ -21,32 +24,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
def async_add_sensor(event_id):
|
||||
"""Add binary sensor from Axis device."""
|
||||
event = device.api.event.events[event_id]
|
||||
|
||||
if event.CLASS != CLASS_OUTPUT:
|
||||
async_add_entities([AxisBinarySensor(event, device)], True)
|
||||
|
||||
device.listeners.append(async_dispatcher_connect(
|
||||
hass, device.event_new_sensor, async_add_sensor))
|
||||
|
||||
|
||||
class AxisBinarySensor(BinarySensorDevice):
|
||||
class AxisBinarySensor(AxisEventBase, BinarySensorDevice):
|
||||
"""Representation of a binary Axis event."""
|
||||
|
||||
def __init__(self, event, device):
|
||||
"""Initialize the Axis binary sensor."""
|
||||
self.event = event
|
||||
self.device = device
|
||||
super().__init__(event, device)
|
||||
self.remove_timer = None
|
||||
self.unsub_dispatcher = None
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe sensors events."""
|
||||
self.event.register_callback(self.update_callback)
|
||||
self.unsub_dispatcher = async_dispatcher_connect(
|
||||
self.hass, self.device.event_reachable, self.update_callback)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect device object when removed."""
|
||||
self.event.remove_callback(self.update_callback)
|
||||
self.unsub_dispatcher()
|
||||
|
||||
@callback
|
||||
def update_callback(self, no_delay=False):
|
||||
|
@ -67,7 +59,6 @@ class AxisBinarySensor(BinarySensorDevice):
|
|||
@callback
|
||||
def _delay_update(now):
|
||||
"""Timer callback for sensor update."""
|
||||
LOGGER.debug("%s called delayed (%s sec) update", self.name, delay)
|
||||
self.async_schedule_update_ha_state()
|
||||
self.remove_timer = None
|
||||
|
||||
|
@ -83,32 +74,10 @@ class AxisBinarySensor(BinarySensorDevice):
|
|||
@property
|
||||
def name(self):
|
||||
"""Return the name of the event."""
|
||||
return '{} {} {}'.format(
|
||||
self.device.name, self.event.TYPE, self.event.id)
|
||||
if self.event.CLASS == CLASS_INPUT and self.event.id and \
|
||||
self.device.api.vapix.ports[self.event.id].name:
|
||||
return '{} {}'.format(
|
||||
self.device.name,
|
||||
self.device.api.vapix.ports[self.event.id].name)
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the event."""
|
||||
return self.event.CLASS
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this device."""
|
||||
return '{}-{}-{}'.format(
|
||||
self.device.serial, self.event.topic, self.event.id)
|
||||
|
||||
def available(self):
|
||||
"""Return True if device is available."""
|
||||
return self.device.available
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
return {
|
||||
'identifiers': {(AXIS_DOMAIN, self.device.serial)}
|
||||
}
|
||||
return super().name
|
||||
|
|
|
@ -6,9 +6,9 @@ from homeassistant.components.mjpeg.camera import (
|
|||
from homeassistant.const import (
|
||||
CONF_AUTHENTICATION, CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME,
|
||||
CONF_PASSWORD, CONF_PORT, CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .axis_base import AxisEntityBase
|
||||
from .const import DOMAIN as AXIS_DOMAIN
|
||||
|
||||
AXIS_IMAGE = 'http://{}:{}/axis-cgi/jpg/image.cgi'
|
||||
|
@ -38,28 +38,20 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
async_add_entities([AxisCamera(config, device)])
|
||||
|
||||
|
||||
class AxisCamera(MjpegCamera):
|
||||
class AxisCamera(AxisEntityBase, MjpegCamera):
|
||||
"""Representation of a Axis camera."""
|
||||
|
||||
def __init__(self, config, device):
|
||||
"""Initialize Axis Communications camera component."""
|
||||
super().__init__(config)
|
||||
self.device_config = config
|
||||
self.device = device
|
||||
self.port = device.config_entry.data[CONF_DEVICE][CONF_PORT]
|
||||
self.unsub_dispatcher = []
|
||||
AxisEntityBase.__init__(self, device)
|
||||
MjpegCamera.__init__(self, config)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe camera events."""
|
||||
self.unsub_dispatcher.append(async_dispatcher_connect(
|
||||
self.hass, self.device.event_new_address, self._new_address))
|
||||
self.unsub_dispatcher.append(async_dispatcher_connect(
|
||||
self.hass, self.device.event_reachable, self.update_callback))
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect device object when removed."""
|
||||
for unsub_dispatcher in self.unsub_dispatcher:
|
||||
unsub_dispatcher()
|
||||
await super().async_added_to_hass()
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
|
@ -74,29 +66,13 @@ class AxisCamera(MjpegCamera):
|
|||
self.device.config_entry.data[CONF_DEVICE][CONF_PASSWORD],
|
||||
self.device.host)
|
||||
|
||||
@callback
|
||||
def update_callback(self, no_delay=None):
|
||||
"""Update the cameras state."""
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if device is available."""
|
||||
return self.device.available
|
||||
|
||||
def _new_address(self):
|
||||
"""Set new device address for video stream."""
|
||||
self._mjpeg_url = AXIS_VIDEO.format(self.device.host, self.port)
|
||||
self._still_image_url = AXIS_IMAGE.format(self.device.host, self.port)
|
||||
port = self.device.config_entry.data[CONF_DEVICE][CONF_PORT]
|
||||
self._mjpeg_url = AXIS_VIDEO.format(self.device.host, port)
|
||||
self._still_image_url = AXIS_IMAGE.format(self.device.host, port)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this device."""
|
||||
return '{}-camera'.format(self.device.serial)
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
return {
|
||||
'identifiers': {(AXIS_DOMAIN, self.device.serial)}
|
||||
}
|
||||
|
|
|
@ -83,19 +83,23 @@ class AxisNetworkDevice:
|
|||
self.product_type = self.api.vapix.params.prodtype
|
||||
|
||||
if self.config_entry.options[CONF_CAMERA]:
|
||||
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.async_forward_entry_setup(
|
||||
self.config_entry, 'camera'))
|
||||
|
||||
if self.config_entry.options[CONF_EVENTS]:
|
||||
task = self.hass.async_create_task(
|
||||
self.hass.config_entries.async_forward_entry_setup(
|
||||
self.config_entry, 'binary_sensor'))
|
||||
|
||||
self.api.stream.connection_status_callback = \
|
||||
self.async_connection_status_callback
|
||||
self.api.enable_events(event_callback=self.async_event_callback)
|
||||
task.add_done_callback(self.start)
|
||||
|
||||
platform_tasks = [
|
||||
self.hass.config_entries.async_forward_entry_setup(
|
||||
self.config_entry, platform)
|
||||
for platform in ['binary_sensor', 'switch']
|
||||
]
|
||||
self.hass.async_create_task(self.start(platform_tasks))
|
||||
|
||||
self.config_entry.add_update_listener(self.async_new_address_callback)
|
||||
|
||||
|
@ -145,9 +149,9 @@ class AxisNetworkDevice:
|
|||
if action == 'add':
|
||||
async_dispatcher_send(self.hass, self.event_new_sensor, event_id)
|
||||
|
||||
@callback
|
||||
def start(self, fut):
|
||||
"""Start the event stream."""
|
||||
async def start(self, platform_tasks):
|
||||
"""Start the event stream when all platforms are loaded."""
|
||||
await asyncio.gather(*platform_tasks)
|
||||
self.api.start()
|
||||
|
||||
@callback
|
||||
|
@ -157,15 +161,22 @@ class AxisNetworkDevice:
|
|||
|
||||
async def async_reset(self):
|
||||
"""Reset this device to default state."""
|
||||
self.api.stop()
|
||||
platform_tasks = []
|
||||
|
||||
if self.config_entry.options[CONF_CAMERA]:
|
||||
await self.hass.config_entries.async_forward_entry_unload(
|
||||
self.config_entry, 'camera')
|
||||
platform_tasks.append(
|
||||
self.hass.config_entries.async_forward_entry_unload(
|
||||
self.config_entry, 'camera'))
|
||||
|
||||
if self.config_entry.options[CONF_EVENTS]:
|
||||
await self.hass.config_entries.async_forward_entry_unload(
|
||||
self.config_entry, 'binary_sensor')
|
||||
self.api.stop()
|
||||
platform_tasks += [
|
||||
self.hass.config_entries.async_forward_entry_unload(
|
||||
self.config_entry, platform)
|
||||
for platform in ['binary_sensor', 'switch']
|
||||
]
|
||||
|
||||
await asyncio.gather(*platform_tasks)
|
||||
|
||||
for unsub_dispatcher in self.listeners:
|
||||
unsub_dispatcher()
|
||||
|
@ -185,13 +196,22 @@ async def get_device(hass, config):
|
|||
port=config[CONF_PORT], web_proto='http')
|
||||
|
||||
device.vapix.initialize_params(preload_data=False)
|
||||
device.vapix.initialize_ports()
|
||||
|
||||
try:
|
||||
with async_timeout.timeout(15):
|
||||
await hass.async_add_executor_job(
|
||||
device.vapix.params.update_brand)
|
||||
await hass.async_add_executor_job(
|
||||
device.vapix.params.update_properties)
|
||||
|
||||
await asyncio.gather(
|
||||
hass.async_add_executor_job(
|
||||
device.vapix.params.update_brand),
|
||||
|
||||
hass.async_add_executor_job(
|
||||
device.vapix.params.update_properties),
|
||||
|
||||
hass.async_add_executor_job(
|
||||
device.vapix.ports.update)
|
||||
)
|
||||
|
||||
return device
|
||||
|
||||
except axis.Unauthorized:
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Axis",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/components/axis",
|
||||
"requirements": ["axis==22"],
|
||||
"requirements": ["axis==23"],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@kane610"]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
"""Support for Axis switches."""
|
||||
|
||||
from axis.event_stream import CLASS_OUTPUT
|
||||
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.const import CONF_MAC
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .axis_base import AxisEventBase
|
||||
from .const import DOMAIN as AXIS_DOMAIN
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up a Axis switch."""
|
||||
serial_number = config_entry.data[CONF_MAC]
|
||||
device = hass.data[AXIS_DOMAIN][serial_number]
|
||||
|
||||
@callback
|
||||
def async_add_switch(event_id):
|
||||
"""Add switch from Axis device."""
|
||||
event = device.api.event.events[event_id]
|
||||
|
||||
if event.CLASS == CLASS_OUTPUT:
|
||||
async_add_entities([AxisSwitch(event, device)], True)
|
||||
|
||||
device.listeners.append(async_dispatcher_connect(
|
||||
hass, device.event_new_sensor, async_add_switch))
|
||||
|
||||
|
||||
class AxisSwitch(AxisEventBase, SwitchDevice):
|
||||
"""Representation of a Axis switch."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if event is active."""
|
||||
return self.event.is_tripped
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn on switch."""
|
||||
action = '/'
|
||||
await self.hass.async_add_executor_job(
|
||||
self.device.api.vapix.ports[self.event.id].action, action)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn off switch."""
|
||||
action = '\\'
|
||||
await self.hass.async_add_executor_job(
|
||||
self.device.api.vapix.ports[self.event.id].action, action)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the event."""
|
||||
if self.event.id and self.device.api.vapix.ports[self.event.id].name:
|
||||
return '{} {}'.format(
|
||||
self.device.name,
|
||||
self.device.api.vapix.ports[self.event.id].name)
|
||||
|
||||
return super().name
|
|
@ -207,7 +207,7 @@ av==6.1.2
|
|||
# avion==0.10
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==22
|
||||
axis==23
|
||||
|
||||
# homeassistant.components.baidu
|
||||
baidu-aip==1.6.6
|
||||
|
|
|
@ -67,7 +67,7 @@ apns2==0.3.0
|
|||
av==6.1.2
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==22
|
||||
axis==23
|
||||
|
||||
# homeassistant.components.zha
|
||||
bellows-homeassistant==0.7.3
|
||||
|
|
|
@ -37,6 +37,7 @@ async def test_device_setup():
|
|||
api = Mock()
|
||||
|
||||
axis_device = device.AxisNetworkDevice(hass, entry)
|
||||
axis_device.start = Mock()
|
||||
|
||||
assert axis_device.host == DEVICE_DATA[device.CONF_HOST]
|
||||
assert axis_device.model == ENTRY_CONFIG[device.CONF_MODEL]
|
||||
|
@ -47,11 +48,13 @@ async def test_device_setup():
|
|||
assert await axis_device.async_setup() is True
|
||||
|
||||
assert axis_device.api is api
|
||||
assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 2
|
||||
assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 3
|
||||
assert hass.config_entries.async_forward_entry_setup.mock_calls[0][1] == \
|
||||
(entry, 'camera')
|
||||
assert hass.config_entries.async_forward_entry_setup.mock_calls[1][1] == \
|
||||
(entry, 'binary_sensor')
|
||||
assert hass.config_entries.async_forward_entry_setup.mock_calls[2][1] == \
|
||||
(entry, 'switch')
|
||||
|
||||
|
||||
async def test_device_signal_new_address(hass):
|
||||
|
@ -71,7 +74,7 @@ async def test_device_signal_new_address(hass):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) == 1
|
||||
assert len(axis_device.listeners) == 1
|
||||
assert len(axis_device.listeners) == 2
|
||||
|
||||
entry.data[device.CONF_DEVICE][device.CONF_HOST] = '2.3.4.5'
|
||||
hass.config_entries.async_update_entry(entry, data=entry.data)
|
||||
|
@ -193,6 +196,8 @@ async def test_get_device(hass):
|
|||
with patch('axis.param_cgi.Params.update_brand',
|
||||
return_value=mock_coro()), \
|
||||
patch('axis.param_cgi.Params.update_properties',
|
||||
return_value=mock_coro()), \
|
||||
patch('axis.port_cgi.Ports.update',
|
||||
return_value=mock_coro()):
|
||||
assert await device.get_device(hass, DEVICE_DATA)
|
||||
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
"""Axis switch platform tests."""
|
||||
|
||||
from unittest.mock import call as mock_call, Mock
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import axis
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
import homeassistant.components.switch as switch
|
||||
|
||||
EVENTS = [
|
||||
{
|
||||
'operation': 'Initialized',
|
||||
'topic': 'tns1:Device/Trigger/Relay',
|
||||
'source': 'RelayToken',
|
||||
'source_idx': '0',
|
||||
'type': 'LogicalState',
|
||||
'value': 'inactive'
|
||||
},
|
||||
{
|
||||
'operation': 'Initialized',
|
||||
'topic': 'tns1:Device/Trigger/Relay',
|
||||
'source': 'RelayToken',
|
||||
'source_idx': '1',
|
||||
'type': 'LogicalState',
|
||||
'value': 'active'
|
||||
}
|
||||
]
|
||||
|
||||
ENTRY_CONFIG = {
|
||||
axis.CONF_DEVICE: {
|
||||
axis.config_flow.CONF_HOST: '1.2.3.4',
|
||||
axis.config_flow.CONF_USERNAME: 'user',
|
||||
axis.config_flow.CONF_PASSWORD: 'pass',
|
||||
axis.config_flow.CONF_PORT: 80
|
||||
},
|
||||
axis.config_flow.CONF_MAC: '1234ABCD',
|
||||
axis.config_flow.CONF_MODEL: 'model',
|
||||
axis.config_flow.CONF_NAME: 'model 0'
|
||||
}
|
||||
|
||||
ENTRY_OPTIONS = {
|
||||
axis.CONF_CAMERA: False,
|
||||
axis.CONF_EVENTS: True,
|
||||
axis.CONF_TRIGGER_TIME: 0
|
||||
}
|
||||
|
||||
|
||||
async def setup_device(hass):
|
||||
"""Load the Axis switch platform."""
|
||||
from axis import AxisDevice
|
||||
loop = Mock()
|
||||
|
||||
config_entry = config_entries.ConfigEntry(
|
||||
1, axis.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
|
||||
config_entries.CONN_CLASS_LOCAL_PUSH, options=ENTRY_OPTIONS)
|
||||
device = axis.AxisNetworkDevice(hass, config_entry)
|
||||
device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE])
|
||||
hass.data[axis.DOMAIN] = {device.serial: device}
|
||||
device.api.enable_events(event_callback=device.async_event_callback)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setup(
|
||||
config_entry, 'switch')
|
||||
# To flush out the service call to update the group
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return device
|
||||
|
||||
|
||||
async def test_platform_manually_configured(hass):
|
||||
"""Test that nothing happens when platform is manually configured."""
|
||||
assert await async_setup_component(hass, switch.DOMAIN, {
|
||||
'switch': {
|
||||
'platform': axis.DOMAIN
|
||||
}
|
||||
})
|
||||
|
||||
assert axis.DOMAIN not in hass.data
|
||||
|
||||
|
||||
async def test_no_switches(hass):
|
||||
"""Test that no output events in Axis results in no switch entities."""
|
||||
await setup_device(hass)
|
||||
|
||||
assert not hass.states.async_entity_ids('switch')
|
||||
|
||||
|
||||
async def test_switches(hass):
|
||||
"""Test that switches are loaded properly."""
|
||||
device = await setup_device(hass)
|
||||
device.api.vapix.ports = {'0': Mock(), '1': Mock()}
|
||||
device.api.vapix.ports['0'].name = 'Doorbell'
|
||||
device.api.vapix.ports['1'].name = ''
|
||||
|
||||
for event in EVENTS:
|
||||
device.api.stream.event.manage_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
relay_0 = hass.states.get('switch.model_0_doorbell')
|
||||
assert relay_0.state == 'off'
|
||||
assert relay_0.name == 'model 0 Doorbell'
|
||||
|
||||
relay_1 = hass.states.get('switch.model_0_relay_1')
|
||||
assert relay_1.state == 'on'
|
||||
assert relay_1.name == 'model 0 Relay 1'
|
||||
|
||||
device.api.vapix.ports['0'].action = Mock()
|
||||
|
||||
await hass.services.async_call('switch', 'turn_on', {
|
||||
'entity_id': 'switch.model_0_doorbell'
|
||||
}, blocking=True)
|
||||
|
||||
await hass.services.async_call('switch', 'turn_off', {
|
||||
'entity_id': 'switch.model_0_doorbell'
|
||||
}, blocking=True)
|
||||
|
||||
assert device.api.vapix.ports['0'].action.call_args_list == \
|
||||
[mock_call('/'), mock_call('\\')]
|
Loading…
Reference in New Issue