Improve evohome exception handling and fix bugs (#22140)

* Use latest client library, evohomeclient v0.3.1

* Fix issue #22097: Failed to call service climate/turn_on...

* BUGFIX: handle case where a Zone doesn't have a temperature

* BUGFIX: missing exception handler, and inappropriate delint hints

* Improve exception handling, and also better messages

* improve code (REDACT secrets); remove TODOs

* minor refactor - improve error message

* more refactoring - improve error message

* remove TODOs

* update to latest evohomeclient library

* Use latest client library, evohomeclient v0.3.1

* Fix issue #22097: Failed to call service climate/turn_on...

* BUGFIX: handle case where a Zone doesn't have a temperature

* BUGFIX: missing exception handler, and inappropriate delint hints

* Improve exception handling, and also better messages

* improve code (REDACT secrets); remove TODOs

* minor refactor - improve error message

* more refactoring - improve error message

* remove TODOs

* update to latest evohomeclient library

* fix requests for houndci-bot

* Tidy up requests exception handling

* Correct lint error

* update to latest client library

* minor de-lint

* more cleanup of exceptions, messages

* refactored for new exception

* fix error in requirements*_all.txt

* de-lint

* delint unused import

* import 3rd-party library only inside methods

* change honeywell tests

* delint, fix typo

* we dont log usernames, passwords, etc.

* de-lint
This commit is contained in:
David Bonnes 2019-04-02 14:11:26 +01:00 committed by Martin Hjelmare
parent 16e0953f26
commit 3bd37d6a65
6 changed files with 107 additions and 78 deletions

View file

@ -2,22 +2,22 @@
from datetime import datetime, timedelta
import logging
from requests.exceptions import HTTPError
import requests.exceptions
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_AUTO, STATE_ECO, STATE_MANUAL, SUPPORT_AWAY_MODE, SUPPORT_ON_OFF,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
CONF_SCAN_INTERVAL, HTTP_TOO_MANY_REQUESTS, PRECISION_HALVES, STATE_OFF,
TEMP_CELSIUS)
CONF_SCAN_INTERVAL, HTTP_SERVICE_UNAVAILABLE, HTTP_TOO_MANY_REQUESTS,
PRECISION_HALVES, STATE_OFF, TEMP_CELSIUS)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from . import (
CONF_LOCATION_IDX, DATA_EVOHOME, DISPATCHER_EVOHOME, EVO_CHILD, EVO_PARENT,
GWS, SCAN_INTERVAL_DEFAULT, TCS)
GWS, TCS)
_LOGGER = logging.getLogger(__name__)
@ -81,7 +81,7 @@ async def async_setup_platform(hass, hass_config, async_add_entities,
# evohomeclient has exposed no means of accessing non-default location
# (i.e. loc_idx > 0) other than using a protected member, such as below
tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa E501; pylint: disable=protected-access
tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa: E501; pylint: disable=protected-access
_LOGGER.debug(
"Found Controller, id=%s [%s], name=%s (location_idx=%s)",
@ -128,23 +128,43 @@ class EvoClimateDevice(ClimateDevice):
if packet['to'] & self._type and packet['signal'] == 'refresh':
self.async_schedule_update_ha_state(force_refresh=True)
def _handle_requests_exceptions(self, err):
if err.response.status_code == HTTP_TOO_MANY_REQUESTS:
# execute a backoff: pause, and also reduce rate
old_interval = self._params[CONF_SCAN_INTERVAL]
new_interval = min(old_interval, SCAN_INTERVAL_DEFAULT) * 2
self._params[CONF_SCAN_INTERVAL] = new_interval
def _handle_exception(self, err):
try:
import evohomeclient2
raise err
except evohomeclient2.AuthenticationError:
_LOGGER.error(
"Failed to (re)authenticate with the vendor's server. "
"This may be a temporary error. Message is: %s",
err
)
except requests.exceptions.ConnectionError:
# this appears to be common with Honeywell's servers
_LOGGER.warning(
"API rate limit has been exceeded. Suspending polling for %s "
"seconds, and increasing '%s' from %s to %s seconds",
new_interval * 3, CONF_SCAN_INTERVAL, old_interval,
new_interval)
"Unable to connect with the vendor's server. "
"Check your network and the vendor's status page."
)
self._timers['statusUpdated'] = datetime.now() + new_interval * 3
except requests.exceptions.HTTPError:
if err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
_LOGGER.warning(
"Vendor says their server is currently unavailable. "
"This may be temporary; check the vendor's status page."
)
else:
raise err # we dont handle any other HTTPErrors
elif err.response.status_code == HTTP_TOO_MANY_REQUESTS:
_LOGGER.warning(
"The vendor's API rate limit has been exceeded. "
"So will cease polling, and will resume after %s seconds.",
(self._params[CONF_SCAN_INTERVAL] * 3).total_seconds()
)
self._timers['statusUpdated'] = datetime.now() + \
self._params[CONF_SCAN_INTERVAL] * 3
else:
raise # we don't expect/handle any other HTTPErrors
@property
def name(self) -> str:
@ -239,7 +259,8 @@ class EvoZone(EvoClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature of the evohome Zone."""
return self._status['temperatureStatus']['temperature']
return (self._status['temperatureStatus']['temperature']
if self._status['temperatureStatus']['isAvailable'] else None)
@property
def current_operation(self):
@ -284,9 +305,11 @@ class EvoZone(EvoClimateDevice):
- None for PermanentOverride (i.e. indefinitely)
"""
try:
import evohomeclient2
self._obj.set_temperature(temperature, until)
except HTTPError as err:
self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
self._handle_exception(err)
def set_temperature(self, **kwargs):
"""Set new target temperature, indefinitely."""
@ -334,9 +357,11 @@ class EvoZone(EvoClimateDevice):
def _set_operation_mode(self, operation_mode):
if operation_mode == EVO_FOLLOW:
try:
self._obj.cancel_temp_override(self._obj)
except HTTPError as err:
self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member
import evohomeclient2
self._obj.cancel_temp_override()
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
self._handle_exception(err)
elif operation_mode == EVO_TEMPOVER:
_LOGGER.error(
@ -496,9 +521,11 @@ class EvoController(EvoClimateDevice):
def _set_operation_mode(self, operation_mode):
try:
import evohomeclient2
self._obj._set_status(operation_mode) # noqa: E501; pylint: disable=protected-access
except HTTPError as err:
self._handle_requests_exceptions(err)
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
self._handle_exception(err)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode for the TCS.
@ -532,10 +559,12 @@ class EvoController(EvoClimateDevice):
loc_idx = self._params[CONF_LOCATION_IDX]
try:
import evohomeclient2
self._status.update(
self._client.locations[loc_idx].status()[GWS][0][TCS][0])
except HTTPError as err: # check if we've exceeded the api rate limit
self._handle_requests_exceptions(err)
except (requests.exceptions.RequestException,
evohomeclient2.AuthenticationError) as err:
self._handle_exception(err)
else:
self._timers['statusUpdated'] = datetime.now()
self._available = True