diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index 53087f2682c..6082c96863f 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -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), diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index 6d373dd638f..30e0e759a2c 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -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.""" diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index 45801257d00..34b6da778a8 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -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.""" diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index ffe48e5f733..746808e0d91 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -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): diff --git a/requirements_all.txt b/requirements_all.txt index 281b0434148..66ad3d04430 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 60d9697ed19..414f8c45919 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -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 diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py index 9ca8b81793b..75dd6462c4e 100644 --- a/tests/components/axis/test_binary_sensor.py +++ b/tests/components/axis/test_binary_sensor.py @@ -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') diff --git a/tests/components/axis/test_camera.py b/tests/components/axis/test_camera.py index c585ada6319..95878697e03 100644 --- a/tests/components/axis/test_camera.py +++ b/tests/components/axis/test_camera.py @@ -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') diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index 7e18b36c6a6..086c2692d44 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -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 diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index 2a0a7d6391c..72d426819c6 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -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