From 8bee3cda375b9a25d27c360364abadc4e393e9b0 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Wed, 17 Feb 2021 14:36:39 -0800 Subject: [PATCH] Centralize wemo exception handling (#46705) --- .../components/wemo/binary_sensor.py | 11 +----- homeassistant/components/wemo/entity.py | 33 +++++++++++++++- homeassistant/components/wemo/fan.py | 36 +++-------------- homeassistant/components/wemo/light.py | 39 ++++--------------- homeassistant/components/wemo/switch.py | 21 ++-------- 5 files changed, 48 insertions(+), 92 deletions(-) diff --git a/homeassistant/components/wemo/binary_sensor.py b/homeassistant/components/wemo/binary_sensor.py index 0d7f2532057..94d5a587c17 100644 --- a/homeassistant/components/wemo/binary_sensor.py +++ b/homeassistant/components/wemo/binary_sensor.py @@ -2,8 +2,6 @@ import asyncio import logging -from pywemo.ouimeaux_device.api.service import ActionException - from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -35,12 +33,5 @@ class WemoBinarySensor(WemoSubscriptionEntity, BinarySensorEntity): def _update(self, force_update=True): """Update the sensor state.""" - try: + with self._wemo_exception_handler("update status"): self._state = self.wemo.get_state(force_update) - - if not self._available: - _LOGGER.info("Reconnected to %s", self.name) - self._available = True - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index 2278cc854b2..91470d0cd5c 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -1,10 +1,12 @@ """Classes shared among Wemo entities.""" import asyncio +import contextlib import logging -from typing import Any, Dict, Optional +from typing import Any, Dict, Generator, Optional import async_timeout from pywemo import WeMoDevice +from pywemo.ouimeaux_device.api.service import ActionException from homeassistant.helpers.entity import Entity @@ -13,6 +15,18 @@ from .const import DOMAIN as WEMO_DOMAIN _LOGGER = logging.getLogger(__name__) +class ExceptionHandlerStatus: + """Exit status from the _wemo_exception_handler context manager.""" + + # An exception if one was raised in the _wemo_exception_handler. + exception: Optional[Exception] = None + + @property + def success(self) -> bool: + """Return True if the handler completed with no exception.""" + return self.exception is None + + class WemoEntity(Entity): """Common methods for Wemo entities. @@ -36,6 +50,23 @@ class WemoEntity(Entity): """Return true if switch is available.""" return self._available + @contextlib.contextmanager + def _wemo_exception_handler( + self, message: str + ) -> Generator[ExceptionHandlerStatus, None, None]: + """Wrap device calls to set `_available` when wemo exceptions happen.""" + status = ExceptionHandlerStatus() + try: + yield status + except ActionException as err: + status.exception = err + _LOGGER.warning("Could not %s for %s (%s)", message, self.name, err) + self._available = False + else: + if not self._available: + _LOGGER.info("Reconnected to %s", self.name) + self._available = True + def _update(self, force_update: Optional[bool] = True): """Update the device state.""" raise NotImplementedError() diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index 678fd93fe05..94dab468a69 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -4,7 +4,6 @@ from datetime import timedelta import logging import math -from pywemo.ouimeaux_device.api.service import ActionException import voluptuous as vol from homeassistant.components.fan import SUPPORT_SET_SPEED, FanEntity @@ -138,7 +137,7 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity): def _update(self, force_update=True): """Update the device state.""" - try: + with self._wemo_exception_handler("update status"): self._state = self.wemo.get_state(force_update) self._fan_mode = self.wemo.fan_mode @@ -152,13 +151,6 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity): if self.wemo.fan_mode != WEMO_FAN_OFF: self._last_fan_on_mode = self.wemo.fan_mode - if not self._available: - _LOGGER.info("Reconnected to %s", self.name) - self._available = True - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False - def turn_on( self, speed: str = None, @@ -171,11 +163,8 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity): def turn_off(self, **kwargs) -> None: """Turn the switch off.""" - try: + with self._wemo_exception_handler("turn off"): self.wemo.set_state(WEMO_FAN_OFF) - except ActionException as err: - _LOGGER.warning("Error while turning off device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() @@ -188,13 +177,8 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity): else: named_speed = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) - try: + with self._wemo_exception_handler("set speed"): self.wemo.set_state(named_speed) - except ActionException as err: - _LOGGER.warning( - "Error while setting speed of device %s (%s)", self.name, err - ) - self._available = False self.schedule_update_ha_state() @@ -211,24 +195,14 @@ class WemoHumidifier(WemoSubscriptionEntity, FanEntity): elif target_humidity >= 100: pywemo_humidity = WEMO_HUMIDITY_100 - try: + with self._wemo_exception_handler("set humidity"): self.wemo.set_humidity(pywemo_humidity) - except ActionException as err: - _LOGGER.warning( - "Error while setting humidity of device: %s (%s)", self.name, err - ) - self._available = False self.schedule_update_ha_state() def reset_filter_life(self) -> None: """Reset the filter life to 100%.""" - try: + with self._wemo_exception_handler("reset filter life"): self.wemo.reset_filter_life() - except ActionException as err: - _LOGGER.warning( - "Error while resetting filter life on device: %s (%s)", self.name, err - ) - self._available = False self.schedule_update_ha_state() diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index 169534bf0c5..bbcdafaf351 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -3,8 +3,6 @@ import asyncio from datetime import timedelta import logging -from pywemo.ouimeaux_device.api.service import ActionException - from homeassistant import util from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -158,7 +156,7 @@ class WemoLight(WemoEntity, LightEntity): "force_update": False, } - try: + with self._wemo_exception_handler("turn on"): if xy_color is not None: self.wemo.set_color(xy_color, transition=transition_time) @@ -167,9 +165,6 @@ class WemoLight(WemoEntity, LightEntity): if self.wemo.turn_on(**turn_on_kwargs): self._state["onoff"] = WEMO_ON - except ActionException as err: - _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() @@ -177,28 +172,21 @@ class WemoLight(WemoEntity, LightEntity): """Turn the light off.""" transition_time = int(kwargs.get(ATTR_TRANSITION, 0)) - try: + with self._wemo_exception_handler("turn off"): if self.wemo.turn_off(transition=transition_time): self._state["onoff"] = WEMO_OFF - except ActionException as err: - _LOGGER.warning("Error while turning off device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() def _update(self, force_update=True): """Synchronize state with bridge.""" - try: + with self._wemo_exception_handler("update status") as handler: self._update_lights(no_throttle=force_update) self._state = self.wemo.state - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False - else: + if handler.success: self._is_on = self._state.get("onoff") != WEMO_OFF self._brightness = self._state.get("level", 255) self._color_temp = self._state.get("temperature_mireds") - self._available = True xy_color = self._state.get("color_xy") @@ -228,19 +216,12 @@ class WemoDimmer(WemoSubscriptionEntity, LightEntity): def _update(self, force_update=True): """Update the device state.""" - try: + with self._wemo_exception_handler("update status"): self._state = self.wemo.get_state(force_update) wemobrightness = int(self.wemo.get_brightness(force_update)) self._brightness = int((wemobrightness * 255) / 100) - if not self._available: - _LOGGER.info("Reconnected to %s", self.name) - self._available = True - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False - def turn_on(self, **kwargs): """Turn the dimmer on.""" # Wemo dimmer switches use a range of [0, 100] to control @@ -251,24 +232,18 @@ class WemoDimmer(WemoSubscriptionEntity, LightEntity): else: brightness = 255 - try: + with self._wemo_exception_handler("turn on"): if self.wemo.on(): self._state = WEMO_ON self.wemo.set_brightness(brightness) - except ActionException as err: - _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the dimmer off.""" - try: + with self._wemo_exception_handler("turn off"): if self.wemo.off(): self._state = WEMO_OFF - except ActionException as err: - _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index f925aad3f72..15b38550b93 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -3,8 +3,6 @@ import asyncio from datetime import datetime, timedelta import logging -from pywemo.ouimeaux_device.api.service import ActionException - from homeassistant.components.switch import SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -140,29 +138,23 @@ class WemoSwitch(WemoSubscriptionEntity, SwitchEntity): def turn_on(self, **kwargs): """Turn the switch on.""" - try: + with self._wemo_exception_handler("turn on"): if self.wemo.on(): self._state = WEMO_ON - except ActionException as err: - _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the switch off.""" - try: + with self._wemo_exception_handler("turn off"): if self.wemo.off(): self._state = WEMO_OFF - except ActionException as err: - _LOGGER.warning("Error while turning off device %s (%s)", self.name, err) - self._available = False self.schedule_update_ha_state() def _update(self, force_update=True): """Update the device state.""" - try: + with self._wemo_exception_handler("update status"): self._state = self.wemo.get_state(force_update) if self.wemo.model_name == "Insight": @@ -173,10 +165,3 @@ class WemoSwitch(WemoSubscriptionEntity, SwitchEntity): elif self.wemo.model_name == "CoffeeMaker": self.coffeemaker_mode = self.wemo.mode self._mode_string = self.wemo.mode_string - - if not self._available: - _LOGGER.info("Reconnected to %s", self.name) - self._available = True - except ActionException as err: - _LOGGER.warning("Could not update status for %s (%s)", self.name, err) - self._available = False