Axis component reflect device availability (#22401)

This commit is contained in:
Robert Svensson 2019-03-29 15:20:12 +01:00 committed by GitHub
parent f4625fd561
commit 5f6037d563
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 69 additions and 28 deletions

View File

@ -12,7 +12,7 @@ from .config_flow import configured_devices, DEVICE_SCHEMA
from .const import CONF_CAMERA, CONF_EVENTS, DEFAULT_TRIGGER_TIME, DOMAIN
from .device import AxisNetworkDevice, get_device
REQUIREMENTS = ['axis==17']
REQUIREMENTS = ['axis==19']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA),

View File

@ -35,23 +35,29 @@ class AxisBinarySensor(BinarySensorDevice):
"""Initialize the Axis binary sensor."""
self.event = event
self.device = device
self.delay = device.config_entry.options[CONF_TRIGGER_TIME]
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)
def update_callback(self):
"""Update the sensor's state, if needed."""
@callback
def update_callback(self, no_delay=False):
"""Update the sensor's state, if needed.
Parameter no_delay is True when device_event_reachable is sent.
"""
delay = self.device.config_entry.options[CONF_TRIGGER_TIME]
if self.remove_timer is not None:
self.remove_timer()
self.remove_timer = None
if delay == 0 or self.is_on:
self.schedule_update_ha_state()
if self.is_on or delay == 0 or no_delay:
self.async_schedule_update_ha_state()
return
@callback
@ -87,6 +93,10 @@ class AxisBinarySensor(BinarySensorDevice):
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."""

View File

@ -5,6 +5,7 @@ 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 .const import DOMAIN as AXIS_DOMAIN
@ -46,12 +47,25 @@ class AxisCamera(MjpegCamera):
self.device_config = config
self.device = device
self.port = device.config_entry.data[CONF_DEVICE][CONF_PORT]
self.unsub_dispatcher = None
self.unsub_dispatcher = []
async def async_added_to_hass(self):
"""Subscribe camera events."""
self.unsub_dispatcher = async_dispatcher_connect(
self.hass, 'axis_{}_new_ip'.format(self.device.name), self._new_ip)
self.unsub_dispatcher.append(async_dispatcher_connect(
self.hass, 'axis_{}_new_ip'.format(self.device.name),
self._new_ip))
self.unsub_dispatcher.append(async_dispatcher_connect(
self.hass, self.device.event_reachable, self.update_callback))
@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_ip(self, host):
"""Set new IP for video stream."""

View File

@ -12,6 +12,7 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import CONF_CAMERA, CONF_EVENTS, CONF_MODEL, DOMAIN, LOGGER
from .errors import AuthenticationRequired, CannotConnect
@ -72,8 +73,7 @@ class AxisNetworkDevice:
try:
self.api = await get_device(
hass, self.config_entry.data[CONF_DEVICE],
event_types='on', signal_callback=self.async_signal_callback)
hass, self.config_entry.data[CONF_DEVICE])
except CannotConnect:
raise ConfigEntryNotReady
@ -95,17 +95,38 @@ class AxisNetworkDevice:
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)
self.api.start()
return True
@property
def event_reachable(self):
"""Device specific event to signal a change in connection status."""
return 'axis_reachable_{}'.format(self.serial)
@callback
def async_connection_status_callback(self, status):
"""Handle signals of gateway connection status.
This is called on every RTSP keep-alive message.
Only signal state change if state change is true.
"""
from axis.streammanager import SIGNAL_PLAYING
if self.available != (status == SIGNAL_PLAYING):
self.available = not self.available
async_dispatcher_send(self.hass, self.event_reachable, True)
@property
def event_new_sensor(self):
"""Device specific event to signal new sensor available."""
return 'axis_add_sensor_{}'.format(self.serial)
@callback
def async_signal_callback(self, action, event):
def async_event_callback(self, action, event):
"""Call to configure events when initialized on event stream."""
if action == 'add':
async_dispatcher_send(self.hass, self.event_new_sensor, event)
@ -116,7 +137,7 @@ class AxisNetworkDevice:
self.api.stop()
async def get_device(hass, config, event_types=None, signal_callback=None):
async def get_device(hass, config):
"""Create a Axis device."""
import axis
@ -124,8 +145,7 @@ async def get_device(hass, config, event_types=None, signal_callback=None):
loop=hass.loop, host=config[CONF_HOST],
username=config[CONF_USERNAME],
password=config[CONF_PASSWORD],
port=config[CONF_PORT], web_proto='http',
event_types=event_types, signal=signal_callback)
port=config[CONF_PORT], web_proto='http')
try:
with async_timeout.timeout(15):

View File

@ -192,7 +192,7 @@ av==6.1.2
# avion==0.10
# homeassistant.components.axis
axis==17
axis==19
# homeassistant.components.modem_callerid.sensor
basicmodem==0.7

View File

@ -60,7 +60,7 @@ apns2==0.3.0
av==6.1.2
# homeassistant.components.axis
axis==17
axis==19
# homeassistant.components.zha
bellows-homeassistant==0.7.1

View File

@ -53,9 +53,9 @@ async def setup_device(hass):
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],
signal=device.async_signal_callback)
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, 'binary_sensor')

View File

@ -37,9 +37,9 @@ async def setup_device(hass):
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],
signal=device.async_signal_callback)
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, 'camera')

View File

@ -31,8 +31,7 @@ async def test_flow_works(hass):
with patch('axis.AxisDevice') as mock_device:
def mock_constructor(
loop, host, username, password, port, web_proto, event_types,
signal):
loop, host, username, password, port, web_proto):
"""Fake the controller constructor."""
mock_device.loop = loop
mock_device.host = host
@ -189,8 +188,7 @@ async def test_discovery_flow_known_device(hass):
config_flow.CONF_PORT: 80}}), \
patch('axis.AxisDevice') as mock_device:
def mock_constructor(
loop, host, username, password, port, web_proto, event_types,
signal):
loop, host, username, password, port, web_proto):
"""Fake the controller constructor."""
mock_device.loop = loop
mock_device.host = host
@ -277,8 +275,7 @@ async def test_import_flow_works(hass):
with patch('axis.AxisDevice') as mock_device:
def mock_constructor(
loop, host, username, password, port, web_proto, event_types,
signal):
loop, host, username, password, port, web_proto):
"""Fake the controller constructor."""
mock_device.loop = loop
mock_device.host = host

View File

@ -94,7 +94,7 @@ async def test_new_event_sends_signal(hass):
axis_device = device.AxisNetworkDevice(hass, entry)
with patch.object(device, 'async_dispatcher_send') as mock_dispatch_send:
axis_device.async_signal_callback(action='add', event='event')
axis_device.async_event_callback(action='add', event='event')
await hass.async_block_till_done()
assert len(mock_dispatch_send.mock_calls) == 1