From 2ec295a6f8bdfa8d01fef2eb9baa7835160204b0 Mon Sep 17 00:00:00 2001 From: Johan Bloemberg <github@ijohan.nl> Date: Sat, 16 Jun 2018 00:26:48 +0200 Subject: [PATCH] Add availability to Rflink entities. (#14977) --- homeassistant/components/rflink.py | 31 ++++++++++++++++++++ tests/components/sensor/test_rflink.py | 40 +++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index 87e2a7a2331..272a5b868ec 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -20,6 +20,8 @@ from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.deprecation import get_deprecated from homeassistant.helpers.entity import Entity +from homeassistant.helpers.dispatcher import ( + async_dispatcher_send, async_dispatcher_connect) REQUIREMENTS = ['rflink==0.0.37'] @@ -65,6 +67,8 @@ DOMAIN = 'rflink' SERVICE_SEND_COMMAND = 'send_command' +SIGNAL_AVAILABILITY = 'rflink_device_available' + DEVICE_DEFAULTS_SCHEMA = vol.Schema({ vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, vol.Optional(CONF_SIGNAL_REPETITIONS, @@ -185,6 +189,8 @@ def async_setup(hass, config): # Reset protocol binding before starting reconnect RflinkCommand.set_rflink_protocol(None) + async_dispatcher_send(hass, SIGNAL_AVAILABILITY, False) + # If HA is not stopping, initiate new connection if hass.state != CoreState.stopping: _LOGGER.warning('disconnected from Rflink, reconnecting') @@ -219,9 +225,16 @@ def async_setup(hass, config): _LOGGER.exception( "Error connecting to Rflink, reconnecting in %s", reconnect_interval) + # Connection to Rflink device is lost, make entities unavailable + async_dispatcher_send(hass, SIGNAL_AVAILABILITY, False) + hass.loop.call_later(reconnect_interval, reconnect, exc) return + # There is a valid connection to a Rflink device now so + # mark entities as available + async_dispatcher_send(hass, SIGNAL_AVAILABILITY, True) + # Bind protocol to command class to allow entities to send commands RflinkCommand.set_rflink_protocol( protocol, config[DOMAIN][CONF_WAIT_FOR_ACK]) @@ -244,6 +257,7 @@ class RflinkDevice(Entity): platform = None _state = STATE_UNKNOWN + _available = True def __init__(self, device_id, hass, name=None, aliases=None, group=True, group_aliases=None, nogroup_aliases=None, fire_event=False, @@ -305,6 +319,23 @@ class RflinkDevice(Entity): """Assume device state until first device event sets state.""" return self._state is STATE_UNKNOWN + @property + def available(self): + """Return True if entity is available.""" + return self._available + + @callback + def set_availability(self, availability): + """Update availability state.""" + self._available = availability + self.async_schedule_update_ha_state() + + @asyncio.coroutine + def async_added_to_hass(self): + """Register update callback.""" + async_dispatcher_connect(self.hass, SIGNAL_AVAILABILITY, + self.set_availability) + class RflinkCommand(RflinkDevice): """Singleton class to make Rflink command interface available to entities. diff --git a/tests/components/sensor/test_rflink.py b/tests/components/sensor/test_rflink.py index a99d14cc735..a250a75ab99 100644 --- a/tests/components/sensor/test_rflink.py +++ b/tests/components/sensor/test_rflink.py @@ -8,6 +8,9 @@ automatic sensor creation. import asyncio from ..test_rflink import mock_rflink +from homeassistant.components.rflink import ( + CONF_RECONNECT_INTERVAL) +from homeassistant.const import STATE_UNKNOWN DOMAIN = 'sensor' @@ -32,7 +35,7 @@ CONFIG = { def test_default_setup(hass, monkeypatch): """Test all basic functionality of the rflink sensor component.""" # setup mocking rflink module - event_callback, create, _, _ = yield from mock_rflink( + event_callback, create, _, disconnect_callback = yield from mock_rflink( hass, CONFIG, DOMAIN, monkeypatch) # make sure arguments are passed @@ -100,3 +103,38 @@ def test_disable_automatic_add(hass, monkeypatch): # make sure new device is not added assert not hass.states.get('sensor.test2') + + +@asyncio.coroutine +def test_entity_availability(hass, monkeypatch): + """If Rflink device is disconnected, entities should become unavailable.""" + # Make sure Rflink mock does not 'recover' to quickly from the + # disconnect or else the unavailability cannot be measured + config = CONFIG + failures = [True, True] + config[CONF_RECONNECT_INTERVAL] = 60 + + # Create platform and entities + event_callback, create, _, disconnect_callback = yield from mock_rflink( + hass, config, DOMAIN, monkeypatch, failures=failures) + + # Entities are available by default + assert hass.states.get('sensor.test').state == STATE_UNKNOWN + + # Mock a disconnect of the Rflink device + disconnect_callback() + + # Wait for dispatch events to propagate + yield from hass.async_block_till_done() + + # Entity should be unavailable + assert hass.states.get('sensor.test').state == 'unavailable' + + # Reconnect the Rflink device + disconnect_callback() + + # Wait for dispatch events to propagate + yield from hass.async_block_till_done() + + # Entities should be available again + assert hass.states.get('sensor.test').state == STATE_UNKNOWN -- GitLab