mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Axis component reflect device availability (#22401)
This commit is contained in:
parent
f4625fd561
commit
5f6037d563
@ -12,7 +12,7 @@ from .config_flow import configured_devices, DEVICE_SCHEMA
|
|||||||
from .const import CONF_CAMERA, CONF_EVENTS, DEFAULT_TRIGGER_TIME, DOMAIN
|
from .const import CONF_CAMERA, CONF_EVENTS, DEFAULT_TRIGGER_TIME, DOMAIN
|
||||||
from .device import AxisNetworkDevice, get_device
|
from .device import AxisNetworkDevice, get_device
|
||||||
|
|
||||||
REQUIREMENTS = ['axis==17']
|
REQUIREMENTS = ['axis==19']
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA),
|
DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA),
|
||||||
|
@ -35,23 +35,29 @@ class AxisBinarySensor(BinarySensorDevice):
|
|||||||
"""Initialize the Axis binary sensor."""
|
"""Initialize the Axis binary sensor."""
|
||||||
self.event = event
|
self.event = event
|
||||||
self.device = device
|
self.device = device
|
||||||
self.delay = device.config_entry.options[CONF_TRIGGER_TIME]
|
|
||||||
self.remove_timer = None
|
self.remove_timer = None
|
||||||
|
self.unsub_dispatcher = None
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Subscribe sensors events."""
|
"""Subscribe sensors events."""
|
||||||
self.event.register_callback(self.update_callback)
|
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):
|
@callback
|
||||||
"""Update the sensor's state, if needed."""
|
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]
|
delay = self.device.config_entry.options[CONF_TRIGGER_TIME]
|
||||||
|
|
||||||
if self.remove_timer is not None:
|
if self.remove_timer is not None:
|
||||||
self.remove_timer()
|
self.remove_timer()
|
||||||
self.remove_timer = None
|
self.remove_timer = None
|
||||||
|
|
||||||
if delay == 0 or self.is_on:
|
if self.is_on or delay == 0 or no_delay:
|
||||||
self.schedule_update_ha_state()
|
self.async_schedule_update_ha_state()
|
||||||
return
|
return
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -87,6 +93,10 @@ class AxisBinarySensor(BinarySensorDevice):
|
|||||||
return '{}-{}-{}'.format(
|
return '{}-{}-{}'.format(
|
||||||
self.device.serial, self.event.topic, self.event.id)
|
self.device.serial, self.event.topic, self.event.id)
|
||||||
|
|
||||||
|
def available(self):
|
||||||
|
"""Return True if device is available."""
|
||||||
|
return self.device.available
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""No polling needed."""
|
"""No polling needed."""
|
||||||
|
@ -5,6 +5,7 @@ from homeassistant.components.mjpeg.camera import (
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_AUTHENTICATION, CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME,
|
CONF_AUTHENTICATION, CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_NAME,
|
||||||
CONF_PASSWORD, CONF_PORT, CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION)
|
CONF_PASSWORD, CONF_PORT, CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION)
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
from .const import DOMAIN as AXIS_DOMAIN
|
from .const import DOMAIN as AXIS_DOMAIN
|
||||||
@ -46,12 +47,25 @@ class AxisCamera(MjpegCamera):
|
|||||||
self.device_config = config
|
self.device_config = config
|
||||||
self.device = device
|
self.device = device
|
||||||
self.port = device.config_entry.data[CONF_DEVICE][CONF_PORT]
|
self.port = device.config_entry.data[CONF_DEVICE][CONF_PORT]
|
||||||
self.unsub_dispatcher = None
|
self.unsub_dispatcher = []
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Subscribe camera events."""
|
"""Subscribe camera events."""
|
||||||
self.unsub_dispatcher = async_dispatcher_connect(
|
self.unsub_dispatcher.append(async_dispatcher_connect(
|
||||||
self.hass, 'axis_{}_new_ip'.format(self.device.name), self._new_ip)
|
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):
|
def _new_ip(self, host):
|
||||||
"""Set new IP for video stream."""
|
"""Set new IP for video stream."""
|
||||||
|
@ -12,6 +12,7 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
|||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
|
||||||
from .const import CONF_CAMERA, CONF_EVENTS, CONF_MODEL, DOMAIN, LOGGER
|
from .const import CONF_CAMERA, CONF_EVENTS, CONF_MODEL, DOMAIN, LOGGER
|
||||||
|
|
||||||
from .errors import AuthenticationRequired, CannotConnect
|
from .errors import AuthenticationRequired, CannotConnect
|
||||||
|
|
||||||
|
|
||||||
@ -72,8 +73,7 @@ class AxisNetworkDevice:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.api = await get_device(
|
self.api = await get_device(
|
||||||
hass, self.config_entry.data[CONF_DEVICE],
|
hass, self.config_entry.data[CONF_DEVICE])
|
||||||
event_types='on', signal_callback=self.async_signal_callback)
|
|
||||||
|
|
||||||
except CannotConnect:
|
except CannotConnect:
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
@ -95,17 +95,38 @@ class AxisNetworkDevice:
|
|||||||
self.hass.async_create_task(
|
self.hass.async_create_task(
|
||||||
self.hass.config_entries.async_forward_entry_setup(
|
self.hass.config_entries.async_forward_entry_setup(
|
||||||
self.config_entry, 'binary_sensor'))
|
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()
|
self.api.start()
|
||||||
|
|
||||||
return True
|
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
|
@property
|
||||||
def event_new_sensor(self):
|
def event_new_sensor(self):
|
||||||
"""Device specific event to signal new sensor available."""
|
"""Device specific event to signal new sensor available."""
|
||||||
return 'axis_add_sensor_{}'.format(self.serial)
|
return 'axis_add_sensor_{}'.format(self.serial)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_signal_callback(self, action, event):
|
def async_event_callback(self, action, event):
|
||||||
"""Call to configure events when initialized on event stream."""
|
"""Call to configure events when initialized on event stream."""
|
||||||
if action == 'add':
|
if action == 'add':
|
||||||
async_dispatcher_send(self.hass, self.event_new_sensor, event)
|
async_dispatcher_send(self.hass, self.event_new_sensor, event)
|
||||||
@ -116,7 +137,7 @@ class AxisNetworkDevice:
|
|||||||
self.api.stop()
|
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."""
|
"""Create a Axis device."""
|
||||||
import axis
|
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],
|
loop=hass.loop, host=config[CONF_HOST],
|
||||||
username=config[CONF_USERNAME],
|
username=config[CONF_USERNAME],
|
||||||
password=config[CONF_PASSWORD],
|
password=config[CONF_PASSWORD],
|
||||||
port=config[CONF_PORT], web_proto='http',
|
port=config[CONF_PORT], web_proto='http')
|
||||||
event_types=event_types, signal=signal_callback)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with async_timeout.timeout(15):
|
with async_timeout.timeout(15):
|
||||||
|
@ -192,7 +192,7 @@ av==6.1.2
|
|||||||
# avion==0.10
|
# avion==0.10
|
||||||
|
|
||||||
# homeassistant.components.axis
|
# homeassistant.components.axis
|
||||||
axis==17
|
axis==19
|
||||||
|
|
||||||
# homeassistant.components.modem_callerid.sensor
|
# homeassistant.components.modem_callerid.sensor
|
||||||
basicmodem==0.7
|
basicmodem==0.7
|
||||||
|
@ -60,7 +60,7 @@ apns2==0.3.0
|
|||||||
av==6.1.2
|
av==6.1.2
|
||||||
|
|
||||||
# homeassistant.components.axis
|
# homeassistant.components.axis
|
||||||
axis==17
|
axis==19
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
bellows-homeassistant==0.7.1
|
bellows-homeassistant==0.7.1
|
||||||
|
@ -53,9 +53,9 @@ async def setup_device(hass):
|
|||||||
1, axis.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
|
1, axis.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
|
||||||
config_entries.CONN_CLASS_LOCAL_PUSH, options=ENTRY_OPTIONS)
|
config_entries.CONN_CLASS_LOCAL_PUSH, options=ENTRY_OPTIONS)
|
||||||
device = axis.AxisNetworkDevice(hass, config_entry)
|
device = axis.AxisNetworkDevice(hass, config_entry)
|
||||||
device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE],
|
device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE])
|
||||||
signal=device.async_signal_callback)
|
|
||||||
hass.data[axis.DOMAIN] = {device.serial: 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(
|
await hass.config_entries.async_forward_entry_setup(
|
||||||
config_entry, 'binary_sensor')
|
config_entry, 'binary_sensor')
|
||||||
|
@ -37,9 +37,9 @@ async def setup_device(hass):
|
|||||||
1, axis.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
|
1, axis.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
|
||||||
config_entries.CONN_CLASS_LOCAL_PUSH, options=ENTRY_OPTIONS)
|
config_entries.CONN_CLASS_LOCAL_PUSH, options=ENTRY_OPTIONS)
|
||||||
device = axis.AxisNetworkDevice(hass, config_entry)
|
device = axis.AxisNetworkDevice(hass, config_entry)
|
||||||
device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE],
|
device.api = AxisDevice(loop=loop, **config_entry.data[axis.CONF_DEVICE])
|
||||||
signal=device.async_signal_callback)
|
|
||||||
hass.data[axis.DOMAIN] = {device.serial: 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(
|
await hass.config_entries.async_forward_entry_setup(
|
||||||
config_entry, 'camera')
|
config_entry, 'camera')
|
||||||
|
@ -31,8 +31,7 @@ async def test_flow_works(hass):
|
|||||||
|
|
||||||
with patch('axis.AxisDevice') as mock_device:
|
with patch('axis.AxisDevice') as mock_device:
|
||||||
def mock_constructor(
|
def mock_constructor(
|
||||||
loop, host, username, password, port, web_proto, event_types,
|
loop, host, username, password, port, web_proto):
|
||||||
signal):
|
|
||||||
"""Fake the controller constructor."""
|
"""Fake the controller constructor."""
|
||||||
mock_device.loop = loop
|
mock_device.loop = loop
|
||||||
mock_device.host = host
|
mock_device.host = host
|
||||||
@ -189,8 +188,7 @@ async def test_discovery_flow_known_device(hass):
|
|||||||
config_flow.CONF_PORT: 80}}), \
|
config_flow.CONF_PORT: 80}}), \
|
||||||
patch('axis.AxisDevice') as mock_device:
|
patch('axis.AxisDevice') as mock_device:
|
||||||
def mock_constructor(
|
def mock_constructor(
|
||||||
loop, host, username, password, port, web_proto, event_types,
|
loop, host, username, password, port, web_proto):
|
||||||
signal):
|
|
||||||
"""Fake the controller constructor."""
|
"""Fake the controller constructor."""
|
||||||
mock_device.loop = loop
|
mock_device.loop = loop
|
||||||
mock_device.host = host
|
mock_device.host = host
|
||||||
@ -277,8 +275,7 @@ async def test_import_flow_works(hass):
|
|||||||
|
|
||||||
with patch('axis.AxisDevice') as mock_device:
|
with patch('axis.AxisDevice') as mock_device:
|
||||||
def mock_constructor(
|
def mock_constructor(
|
||||||
loop, host, username, password, port, web_proto, event_types,
|
loop, host, username, password, port, web_proto):
|
||||||
signal):
|
|
||||||
"""Fake the controller constructor."""
|
"""Fake the controller constructor."""
|
||||||
mock_device.loop = loop
|
mock_device.loop = loop
|
||||||
mock_device.host = host
|
mock_device.host = host
|
||||||
|
@ -94,7 +94,7 @@ async def test_new_event_sends_signal(hass):
|
|||||||
axis_device = device.AxisNetworkDevice(hass, entry)
|
axis_device = device.AxisNetworkDevice(hass, entry)
|
||||||
|
|
||||||
with patch.object(device, 'async_dispatcher_send') as mock_dispatch_send:
|
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()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(mock_dispatch_send.mock_calls) == 1
|
assert len(mock_dispatch_send.mock_calls) == 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user