diff --git a/homeassistant/components/alarm_control_panel/egardia.py b/homeassistant/components/alarm_control_panel/egardia.py index 64e165f6b16..845eb81bbe0 100644 --- a/homeassistant/components/alarm_control_panel/egardia.py +++ b/homeassistant/components/alarm_control_panel/egardia.py @@ -33,6 +33,8 @@ STATES = { def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Egardia platform.""" + if discovery_info is None: + return device = EgardiaAlarm( discovery_info['name'], hass.data[EGARDIA_DEVICE], diff --git a/homeassistant/components/arlo.py b/homeassistant/components/arlo.py index 7e51ec8c045..77201e5ead9 100644 --- a/homeassistant/components/arlo.py +++ b/homeassistant/components/arlo.py @@ -5,11 +5,13 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/arlo/ """ import logging +from datetime import timedelta import voluptuous as vol from requests.exceptions import HTTPError, ConnectTimeout from homeassistant.helpers import config_validation as cv +from homeassistant.util import Throttle from homeassistant.const import CONF_USERNAME, CONF_PASSWORD REQUIREMENTS = ['pyarlo==0.1.2'] @@ -45,6 +47,7 @@ def setup(hass, config): arlo = PyArlo(username, password, preload=False) if not arlo.is_connected: return False + arlo.update = Throttle(timedelta(seconds=10))(arlo.update) hass.data[DATA_ARLO] = arlo except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex)) diff --git a/homeassistant/components/hue.py b/homeassistant/components/hue.py index d3870f0a3a1..f6e654ab44b 100644 --- a/homeassistant/components/hue.py +++ b/homeassistant/components/hue.py @@ -181,6 +181,7 @@ class HueBridge(object): self.allow_in_emulated_hue = allow_in_emulated_hue self.allow_hue_groups = allow_hue_groups + self.available = True self.bridge = None self.lights = {} self.lightgroups = {} diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index 75825683928..661b7c2b3a1 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -123,15 +123,20 @@ def unthrottled_update_lights(hass, bridge, add_devices): api = bridge.get_api() except phue.PhueRequestTimeout: _LOGGER.warning("Timeout trying to reach the bridge") + bridge.available = False return except ConnectionRefusedError: _LOGGER.error("The bridge refused the connection") + bridge.available = False return except socket.error: # socket.error when we cannot reach Hue _LOGGER.exception("Cannot reach the bridge") + bridge.available = False return + bridge.available = True + new_lights = process_lights( hass, api, bridge, lambda **kw: update_lights(hass, bridge, add_devices, **kw)) @@ -266,8 +271,9 @@ class HueLight(Light): @property def available(self): """Return if light is available.""" - return (self.is_group or self.allow_unreachable or - self.info['state']['reachable']) + return self.bridge.available and (self.is_group or + self.allow_unreachable or + self.info['state']['reachable']) @property def supported_features(self): diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 4339c92bb60..4f06f941558 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -4,16 +4,19 @@ Support for WeMo switches. For more details about this component, please refer to the documentation at https://home-assistant.io/components/switch.wemo/ """ +import asyncio import logging from datetime import datetime, timedelta +import async_timeout + from homeassistant.components.switch import SwitchDevice from homeassistant.util import convert from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN) -from homeassistant.loader import get_component DEPENDENCIES = ['wemo'] +SCAN_INTERVAL = timedelta(seconds=10) _LOGGER = logging.getLogger(__name__) @@ -54,29 +57,35 @@ class WemoSwitch(SwitchDevice): self.maker_params = None self.coffeemaker_mode = None self._state = None + self._available = True + self._update_lock = None # look up model name once as it incurs network traffic self._model_name = self.wemo.model_name - wemo = get_component('wemo') - wemo.SUBSCRIPTION_REGISTRY.register(self.wemo) - wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback) - - def _update_callback(self, _device, _type, _params): + def _subscription_callback(self, _device, _type, _params): """Update the state by the Wemo device.""" - _LOGGER.info("Subscription update for %s", _device) + _LOGGER.info("Subscription update for %s", self.name) updated = self.wemo.subscription_update(_type, _params) - self._update(force_update=(not updated)) + self.hass.add_job( + self._async_locked_subscription_callback(not updated)) - if not hasattr(self, 'hass'): + async def _async_locked_subscription_callback(self, force_update): + """Helper to handle an update from a subscription.""" + # If an update is in progress, we don't do anything + if self._update_lock.locked(): return - self.schedule_update_ha_state() + + await self._async_locked_update(force_update) + self.async_schedule_update_ha_state() @property def should_poll(self): - """No polling needed with subscriptions.""" - if self._model_name == 'Insight': - return True - return False + """Device should poll. + + Subscriptions push the state, however it won't detect if a device + is no longer available. Use polling to detect if a device is available. + """ + return True @property def unique_id(self): @@ -172,13 +181,7 @@ class WemoSwitch(SwitchDevice): @property def available(self): """Return true if switch is available.""" - if self._model_name == 'Insight' and self.insight_params is None: - return False - if self._model_name == 'Maker' and self.maker_params is None: - return False - if self._model_name == 'CoffeeMaker' and self.coffeemaker_mode is None: - return False - return True + return self._available @property def icon(self): @@ -189,21 +192,46 @@ class WemoSwitch(SwitchDevice): def turn_on(self, **kwargs): """Turn the switch on.""" - self._state = WEMO_ON self.wemo.on() - self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the switch off.""" - self._state = WEMO_OFF self.wemo.off() - self.schedule_update_ha_state() - def update(self): - """Update WeMo state.""" - self._update(force_update=True) + async def async_added_to_hass(self): + """Wemo switch added to HASS.""" + # Define inside async context so we know our event loop + self._update_lock = asyncio.Lock() - def _update(self, force_update=True): + registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY + await self.hass.async_add_job(registry.register, self.wemo) + registry.on(self.wemo, None, self._subscription_callback) + + async def async_update(self): + """Update WeMo state. + + Wemo has an aggressive retry logic that sometimes can take over a + minute to return. If we don't get a state after 5 seconds, assume the + Wemo switch is unreachable. If update goes through, it will be made + available again. + """ + # If an update is in progress, we don't do anything + if self._update_lock.locked(): + return + + try: + with async_timeout.timeout(5): + await asyncio.shield(self._async_locked_update(True)) + except asyncio.TimeoutError: + _LOGGER.warning('Lost connection to %s', self.name) + self._available = False + + async def _async_locked_update(self, force_update): + """Try updating within an async lock.""" + async with self._update_lock: + await self.hass.async_add_job(self._update, force_update) + + def _update(self, force_update): """Update the device state.""" try: self._state = self.wemo.get_state(force_update) @@ -215,6 +243,11 @@ class WemoSwitch(SwitchDevice): self.maker_params = self.wemo.maker_params elif self._model_name == 'CoffeeMaker': self.coffeemaker_mode = self.wemo.mode + + if not self._available: + _LOGGER.info('Reconnected to %s', self.name) + self._available = True except AttributeError as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) + self._available = False diff --git a/homeassistant/const.py b/homeassistant/const.py index 203a9c63d95..12d988c552e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 65 -PATCH_VERSION = '3' +PATCH_VERSION = '4' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3)