Change evohome to asyncio client (#26042)

* fully async now
* add convergence (call update() 2 seconds after client API call) (issue#25400)
* handle dead TRVs (e.g. flat battery)
This commit is contained in:
David Bonnes 2019-09-01 11:45:41 +01:00 committed by GitHub
parent 298aafc79d
commit f91dd4f5f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 135 additions and 143 deletions

View File

@ -2,14 +2,13 @@
Such systems include evohome (multi-zone), and Round Thermostat (single zone). Such systems include evohome (multi-zone), and Round Thermostat (single zone).
""" """
import asyncio
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
from typing import Any, Dict, Optional, Tuple from typing import Any, Dict, Optional, Tuple
import requests.exceptions import aiohttp.client_exceptions
import voluptuous as vol import voluptuous as vol
import evohomeclient2 import evohomeasync2
from homeassistant.const import ( from homeassistant.const import (
CONF_ACCESS_TOKEN, CONF_ACCESS_TOKEN,
@ -21,17 +20,10 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
async_track_point_in_utc_time,
track_time_interval,
)
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.util.dt import parse_datetime, utcnow from homeassistant.util.dt import parse_datetime, utcnow
@ -81,55 +73,60 @@ def _handle_exception(err) -> bool:
try: try:
raise err raise err
except evohomeclient2.AuthenticationError: except evohomeasync2.AuthenticationError:
_LOGGER.error( _LOGGER.error(
"Failed to (re)authenticate with the vendor's server. " "Failed to (re)authenticate with the vendor's server. "
"Check your network and the vendor's service status page. "
"Check that your username and password are correct. " "Check that your username and password are correct. "
"Message is: %s", "Message is: %s",
err, err,
) )
return False return False
except requests.exceptions.ConnectionError: except aiohttp.ClientConnectionError:
# this appears to be common with Honeywell's servers # this appears to be common with Honeywell's servers
_LOGGER.warning( _LOGGER.warning(
"Unable to connect with the vendor's server. " "Unable to connect with the vendor's server. "
"Check your network and the vendor's status page." "Check your network and the vendor's service status page. "
"Message is: %s", "Message is: %s",
err, err,
) )
return False return False
except requests.exceptions.HTTPError: except aiohttp.ClientResponseError:
if err.response.status_code == HTTP_SERVICE_UNAVAILABLE: if err.status == HTTP_SERVICE_UNAVAILABLE:
_LOGGER.warning( _LOGGER.warning(
"Vendor says their server is currently unavailable. " "The vendor says their server is currently unavailable. "
"Check the vendor's status page." "Check the vendor's service status page."
) )
return False return False
if err.response.status_code == HTTP_TOO_MANY_REQUESTS: if err.status == HTTP_TOO_MANY_REQUESTS:
_LOGGER.warning( _LOGGER.warning(
"The vendor's API rate limit has been exceeded. " "The vendor's API rate limit has been exceeded. "
"Consider increasing the %s.", "If this message persists, consider increasing the %s.",
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
) )
return False return False
raise # we don't expect/handle any other HTTPErrors raise # we don't expect/handle any other ClientResponseError
def setup(hass: HomeAssistantType, hass_config: ConfigType) -> bool: async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
"""Create a (EMEA/EU-based) Honeywell evohome system.""" """Create a (EMEA/EU-based) Honeywell evohome system."""
broker = EvoBroker(hass, hass_config[DOMAIN]) broker = EvoBroker(hass, config[DOMAIN])
if not broker.init_client(): if not await broker.init_client():
return False return False
load_platform(hass, "climate", DOMAIN, {}, hass_config) hass.async_create_task(async_load_platform(hass, "climate", DOMAIN, {}, config))
if broker.tcs.hotwater: if broker.tcs.hotwater:
load_platform(hass, "water_heater", DOMAIN, {}, hass_config) hass.async_create_task(
async_load_platform(hass, "water_heater", DOMAIN, {}, config)
)
track_time_interval(hass, broker.update, hass_config[DOMAIN][CONF_SCAN_INTERVAL]) hass.helpers.event.async_track_time_interval(
broker.update, config[DOMAIN][CONF_SCAN_INTERVAL]
)
return True return True
@ -141,8 +138,7 @@ class EvoBroker:
"""Initialize the evohome client and data structure.""" """Initialize the evohome client and data structure."""
self.hass = hass self.hass = hass
self.params = params self.params = params
self.config = {}
self.config = self.status = self.timers = {}
self.client = self.tcs = None self.client = self.tcs = None
self._app_storage = {} self._app_storage = {}
@ -150,32 +146,31 @@ class EvoBroker:
hass.data[DOMAIN] = {} hass.data[DOMAIN] = {}
hass.data[DOMAIN]["broker"] = self hass.data[DOMAIN]["broker"] = self
def init_client(self) -> bool: async def init_client(self) -> bool:
"""Initialse the evohome data broker. """Initialse the evohome data broker.
Return True if this is successful, otherwise return False. Return True if this is successful, otherwise return False.
""" """
refresh_token, access_token, access_token_expires = asyncio.run_coroutine_threadsafe( refresh_token, access_token, access_token_expires = (
self._load_auth_tokens(), self.hass.loop await self._load_auth_tokens()
).result() )
# evohomeclient2 uses naive/local datetimes # evohomeasync2 uses naive/local datetimes
if access_token_expires is not None: if access_token_expires is not None:
access_token_expires = _utc_to_local_dt(access_token_expires) access_token_expires = _utc_to_local_dt(access_token_expires)
try: client = self.client = evohomeasync2.EvohomeClient(
client = self.client = evohomeclient2.EvohomeClient( self.params[CONF_USERNAME],
self.params[CONF_USERNAME], self.params[CONF_PASSWORD],
self.params[CONF_PASSWORD], refresh_token=refresh_token,
refresh_token=refresh_token, access_token=access_token,
access_token=access_token, access_token_expires=access_token_expires,
access_token_expires=access_token_expires, session=async_get_clientsession(self.hass),
) )
except ( try:
requests.exceptions.RequestException, await client.login()
evohomeclient2.AuthenticationError, except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err:
) as err:
if not _handle_exception(err): if not _handle_exception(err):
return False return False
@ -200,17 +195,14 @@ class EvoBroker:
return False return False
self.tcs = ( self.tcs = (
client.locations[loc_idx] # noqa: E501; pylint: disable=protected-access client.locations[loc_idx] # pylint: disable=protected-access
._gateways[0] ._gateways[0]
._control_systems[0] ._control_systems[0]
) )
_LOGGER.debug("Config = %s", self.config) _LOGGER.debug("Config = %s", self.config)
if _LOGGER.isEnabledFor(logging.DEBUG): if _LOGGER.isEnabledFor(logging.DEBUG): # don't do an I/O unless required
# don't do an I/O unless required await self.update() # includes: _LOGGER.debug("Status = %s"...
_LOGGER.debug(
"Status = %s", client.locations[loc_idx].status()[GWS][0][TCS][0]
)
return True return True
@ -237,7 +229,7 @@ class EvoBroker:
return (None, None, None) # account switched: so tokens wont be valid return (None, None, None) # account switched: so tokens wont be valid
async def _save_auth_tokens(self, *args) -> None: async def _save_auth_tokens(self, *args) -> None:
# evohomeclient2 uses naive/local datetimes # evohomeasync2 uses naive/local datetimes
access_token_expires = _local_dt_to_utc(self.client.access_token_expires) access_token_expires = _local_dt_to_utc(self.client.access_token_expires)
self._app_storage[CONF_USERNAME] = self.params[CONF_USERNAME] self._app_storage[CONF_USERNAME] = self.params[CONF_USERNAME]
@ -248,13 +240,12 @@ class EvoBroker:
store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) store = self.hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
await store.async_save(self._app_storage) await store.async_save(self._app_storage)
async_track_point_in_utc_time( self.hass.helpers.event.async_track_point_in_utc_time(
self.hass,
self._save_auth_tokens, self._save_auth_tokens,
access_token_expires + self.params[CONF_SCAN_INTERVAL], access_token_expires + self.params[CONF_SCAN_INTERVAL],
) )
def update(self, *args, **kwargs) -> None: async def update(self, *args, **kwargs) -> None:
"""Get the latest state data of the entire evohome Location. """Get the latest state data of the entire evohome Location.
This includes state data for the Controller and all its child devices, This includes state data for the Controller and all its child devices,
@ -264,19 +255,16 @@ class EvoBroker:
loc_idx = self.params[CONF_LOCATION_IDX] loc_idx = self.params[CONF_LOCATION_IDX]
try: try:
status = self.client.locations[loc_idx].status()[GWS][0][TCS][0] status = await self.client.locations[loc_idx].status()
except ( except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err:
requests.exceptions.RequestException,
evohomeclient2.AuthenticationError,
) as err:
_handle_exception(err) _handle_exception(err)
else: else:
self.timers["statusUpdated"] = utcnow()
_LOGGER.debug("Status = %s", status)
# inform the evohome devices that state data has been updated # inform the evohome devices that state data has been updated
async_dispatcher_send(self.hass, DOMAIN, {"signal": "refresh"}) self.hass.helpers.dispatcher.async_dispatcher_send(
DOMAIN, {"signal": "refresh"}
)
_LOGGER.debug("Status = %s", status[GWS][0][TCS][0])
class EvoDevice(Entity): class EvoDevice(Entity):
@ -289,6 +277,7 @@ class EvoDevice(Entity):
def __init__(self, evo_broker, evo_device) -> None: def __init__(self, evo_broker, evo_device) -> None:
"""Initialize the evohome entity.""" """Initialize the evohome entity."""
self._evo_device = evo_device self._evo_device = evo_device
self._evo_broker = evo_broker
self._evo_tcs = evo_broker.tcs self._evo_tcs = evo_broker.tcs
self._name = self._icon = self._precision = None self._name = self._icon = self._precision = None
@ -387,7 +376,7 @@ class EvoDevice(Entity):
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass.""" """Run when entity about to be added to hass."""
async_dispatcher_connect(self.hass, DOMAIN, self._refresh) self.hass.helpers.dispatcher.async_dispatcher_connect(DOMAIN, self._refresh)
@property @property
def precision(self) -> float: def precision(self) -> float:
@ -399,14 +388,27 @@ class EvoDevice(Entity):
"""Return the temperature unit to use in the frontend UI.""" """Return the temperature unit to use in the frontend UI."""
return TEMP_CELSIUS return TEMP_CELSIUS
def _update_schedule(self) -> None: async def _call_client_api(self, api_function) -> None:
try:
await api_function
except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err:
_handle_exception(err)
self.hass.helpers.event.async_call_later(
2, self._evo_broker.update()
) # call update() in 2 seconds
async def _update_schedule(self) -> None:
"""Get the latest state data.""" """Get the latest state data."""
if ( if (
not self._schedule.get("DailySchedules") not self._schedule.get("DailySchedules")
or parse_datetime(self.setpoints["next"]["from"]) < utcnow() or parse_datetime(self.setpoints["next"]["from"]) < utcnow()
): ):
self._schedule = self._evo_device.schedule() try:
self._schedule = await self._evo_device.schedule()
except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err:
_handle_exception(err)
def update(self) -> None: async def async_update(self) -> None:
"""Get the latest state data.""" """Get the latest state data."""
self._update_schedule() await self._update_schedule()

View File

@ -3,9 +3,6 @@ from datetime import datetime
import logging import logging
from typing import Any, Dict, Optional, List from typing import Any, Dict, Optional, List
import requests.exceptions
import evohomeclient2
from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import ( from homeassistant.components.climate.const import (
CURRENT_HVAC_HEAT, CURRENT_HVAC_HEAT,
@ -25,7 +22,7 @@ from homeassistant.const import PRECISION_TENTHS
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.util.dt import parse_datetime from homeassistant.util.dt import parse_datetime
from . import CONF_LOCATION_IDX, _handle_exception, EvoDevice from . import CONF_LOCATION_IDX, EvoDevice
from .const import ( from .const import (
DOMAIN, DOMAIN,
EVO_RESET, EVO_RESET,
@ -65,10 +62,13 @@ EVO_PRESET_TO_HA = {
HA_PRESET_TO_EVO = {v: k for k, v in EVO_PRESET_TO_HA.items()} HA_PRESET_TO_EVO = {v: k for k, v in EVO_PRESET_TO_HA.items()}
def setup_platform( async def async_setup_platform(
hass: HomeAssistantType, hass_config: ConfigType, add_entities, discovery_info=None hass: HomeAssistantType, config: ConfigType, async_add_entities, discovery_info=None
) -> None: ) -> None:
"""Create the evohome Controller, and its Zones, if any.""" """Create the evohome Controller, and its Zones, if any."""
if discovery_info is None:
return
broker = hass.data[DOMAIN]["broker"] broker = hass.data[DOMAIN]["broker"]
loc_idx = broker.params[CONF_LOCATION_IDX] loc_idx = broker.params[CONF_LOCATION_IDX]
@ -91,7 +91,7 @@ def setup_platform(
zone.name, zone.name,
) )
add_entities([EvoThermostat(broker, zone)], update_before_add=True) async_add_entities([EvoThermostat(broker, zone)], update_before_add=True)
return return
controller = EvoController(broker, broker.tcs) controller = EvoController(broker, broker.tcs)
@ -107,7 +107,7 @@ def setup_platform(
) )
zones.append(EvoZone(broker, zone)) zones.append(EvoZone(broker, zone))
add_entities([controller] + zones, update_before_add=True) async_add_entities([controller] + zones, update_before_add=True)
class EvoClimateDevice(EvoDevice, ClimateDevice): class EvoClimateDevice(EvoDevice, ClimateDevice):
@ -119,22 +119,18 @@ class EvoClimateDevice(EvoDevice, ClimateDevice):
self._preset_modes = None self._preset_modes = None
def _set_temperature( async def _set_temperature(
self, temperature: float, until: Optional[datetime] = None self, temperature: float, until: Optional[datetime] = None
) -> None: ) -> None:
"""Set a new target temperature for the Zone. """Set a new target temperature for the Zone.
until == None means indefinitely (i.e. PermanentOverride) until == None means indefinitely (i.e. PermanentOverride)
""" """
try: await self._call_client_api(
self._evo_device.set_temperature(temperature, until) self._evo_device.set_temperature(temperature, until)
except ( )
requests.exceptions.RequestException,
evohomeclient2.AuthenticationError,
) as err:
_handle_exception(err)
def _set_zone_mode(self, op_mode: str) -> None: async def _set_zone_mode(self, op_mode: str) -> None:
"""Set a Zone to one of its native EVO_* operating modes. """Set a Zone to one of its native EVO_* operating modes.
Zones inherit their _effective_ operating mode from the Controller. Zones inherit their _effective_ operating mode from the Controller.
@ -153,35 +149,24 @@ class EvoClimateDevice(EvoDevice, ClimateDevice):
(by default) 5C, and 'Away', Zones to (by default) 12C. (by default) 5C, and 'Away', Zones to (by default) 12C.
""" """
if op_mode == EVO_FOLLOW: if op_mode == EVO_FOLLOW:
try: await self._call_client_api(self._evo_device.cancel_temp_override())
self._evo_device.cancel_temp_override()
except (
requests.exceptions.RequestException,
evohomeclient2.AuthenticationError,
) as err:
_handle_exception(err)
return return
temperature = self._evo_device.setpointStatus["targetHeatTemperature"] temperature = self._evo_device.setpointStatus["targetHeatTemperature"]
until = None # EVO_PERMOVER until = None # EVO_PERMOVER
if op_mode == EVO_TEMPOVER and self._schedule["DailySchedules"]: if op_mode == EVO_TEMPOVER and self._schedule["DailySchedules"]:
self._update_schedule() await self._update_schedule()
if self._schedule["DailySchedules"]: if self._schedule["DailySchedules"]:
until = parse_datetime(self.setpoints["next"]["from"]) until = parse_datetime(self.setpoints["next"]["from"])
self._set_temperature(temperature, until=until) await self._set_temperature(temperature, until=until)
def _set_tcs_mode(self, op_mode: str) -> None: async def _set_tcs_mode(self, op_mode: str) -> None:
"""Set the Controller to any of its native EVO_* operating modes.""" """Set the Controller to any of its native EVO_* operating modes."""
try: await self._call_client_api(
# noqa: E501; pylint: disable=protected-access self._evo_tcs._set_status(op_mode) # pylint: disable=protected-access
self._evo_tcs._set_status(op_mode) )
except (
requests.exceptions.RequestException,
evohomeclient2.AuthenticationError,
) as err:
_handle_exception(err)
@property @property
def hvac_modes(self) -> List[str]: def hvac_modes(self) -> List[str]:
@ -216,6 +201,11 @@ class EvoZone(EvoClimateDevice):
self._supported_features = SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE self._supported_features = SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE
self._preset_modes = list(HA_PRESET_TO_EVO) self._preset_modes = list(HA_PRESET_TO_EVO)
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._evo_device.temperatureStatus["isAvailable"]
@property @property
def hvac_mode(self) -> str: def hvac_mode(self) -> str:
"""Return the current operating mode of the evohome Zone.""" """Return the current operating mode of the evohome Zone."""
@ -276,28 +266,28 @@ class EvoZone(EvoClimateDevice):
""" """
return self._evo_device.setpointCapabilities["maxHeatSetpoint"] return self._evo_device.setpointCapabilities["maxHeatSetpoint"]
def set_temperature(self, **kwargs) -> None: async def async_set_temperature(self, **kwargs) -> None:
"""Set a new target temperature.""" """Set a new target temperature."""
until = kwargs.get("until") until = kwargs.get("until")
if until: if until:
until = parse_datetime(until) until = parse_datetime(until)
self._set_temperature(kwargs["temperature"], until) await self._set_temperature(kwargs["temperature"], until)
def set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set an operating mode for the Zone.""" """Set an operating mode for the Zone."""
if hvac_mode == HVAC_MODE_OFF: if hvac_mode == HVAC_MODE_OFF:
self._set_temperature(self.min_temp, until=None) await self._set_temperature(self.min_temp, until=None)
else: # HVAC_MODE_HEAT else: # HVAC_MODE_HEAT
self._set_zone_mode(EVO_FOLLOW) await self._set_zone_mode(EVO_FOLLOW)
def set_preset_mode(self, preset_mode: Optional[str]) -> None: async def async_set_preset_mode(self, preset_mode: Optional[str]) -> None:
"""Set a new preset mode. """Set a new preset mode.
If preset_mode is None, then revert to following the schedule. If preset_mode is None, then revert to following the schedule.
""" """
self._set_zone_mode(HA_PRESET_TO_EVO.get(preset_mode, EVO_FOLLOW)) await self._set_zone_mode(HA_PRESET_TO_EVO.get(preset_mode, EVO_FOLLOW))
class EvoController(EvoClimateDevice): class EvoController(EvoClimateDevice):
@ -344,25 +334,25 @@ class EvoController(EvoClimateDevice):
"""Return the current preset mode, e.g., home, away, temp.""" """Return the current preset mode, e.g., home, away, temp."""
return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"]) return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"])
def set_temperature(self, **kwargs) -> None: async def async_set_temperature(self, **kwargs) -> None:
"""Do nothing. """Do nothing.
The evohome Controller doesn't have a target temperature. The evohome Controller doesn't have a target temperature.
""" """
return return
def set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set an operating mode for the Controller.""" """Set an operating mode for the Controller."""
self._set_tcs_mode(HA_HVAC_TO_TCS.get(hvac_mode)) await self._set_tcs_mode(HA_HVAC_TO_TCS.get(hvac_mode))
def set_preset_mode(self, preset_mode: Optional[str]) -> None: async def async_set_preset_mode(self, preset_mode: Optional[str]) -> None:
"""Set a new preset mode. """Set a new preset mode.
If preset_mode is None, then revert to 'Auto' mode. If preset_mode is None, then revert to 'Auto' mode.
""" """
self._set_tcs_mode(HA_PRESET_TO_TCS.get(preset_mode, EVO_AUTO)) await self._set_tcs_mode(HA_PRESET_TO_TCS.get(preset_mode, EVO_AUTO))
def update(self) -> None: async def async_update(self) -> None:
"""Get the latest state data.""" """Get the latest state data."""
return return
@ -409,16 +399,16 @@ class EvoThermostat(EvoZone):
return super().preset_mode return super().preset_mode
def set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set an operating mode.""" """Set an operating mode."""
self._set_tcs_mode(HA_HVAC_TO_TCS.get(hvac_mode)) await self._set_tcs_mode(HA_HVAC_TO_TCS.get(hvac_mode))
def set_preset_mode(self, preset_mode: Optional[str]) -> None: async def async_set_preset_mode(self, preset_mode: Optional[str]) -> None:
"""Set a new preset mode. """Set a new preset mode.
If preset_mode is None, then revert to following the schedule. If preset_mode is None, then revert to following the schedule.
""" """
if preset_mode in list(HA_PRESET_TO_TCS): if preset_mode in list(HA_PRESET_TO_TCS):
self._set_tcs_mode(HA_PRESET_TO_TCS.get(preset_mode)) await self._set_tcs_mode(HA_PRESET_TO_TCS.get(preset_mode))
else: else:
self._set_zone_mode(HA_PRESET_TO_EVO.get(preset_mode, EVO_FOLLOW)) await self._set_zone_mode(HA_PRESET_TO_EVO.get(preset_mode, EVO_FOLLOW))

View File

@ -3,7 +3,7 @@
"name": "Evohome", "name": "Evohome",
"documentation": "https://www.home-assistant.io/components/evohome", "documentation": "https://www.home-assistant.io/components/evohome",
"requirements": [ "requirements": [
"evohomeclient==0.3.3" "evohome-async==0.3.3b4"
], ],
"dependencies": [], "dependencies": [],
"codeowners": ["@zxdavb"] "codeowners": ["@zxdavb"]

View File

@ -2,9 +2,6 @@
import logging import logging
from typing import List from typing import List
import requests.exceptions
import evohomeclient2
from homeassistant.components.water_heater import ( from homeassistant.components.water_heater import (
SUPPORT_OPERATION_MODE, SUPPORT_OPERATION_MODE,
WaterHeaterDevice, WaterHeaterDevice,
@ -12,7 +9,7 @@ from homeassistant.components.water_heater import (
from homeassistant.const import PRECISION_WHOLE, STATE_OFF, STATE_ON from homeassistant.const import PRECISION_WHOLE, STATE_OFF, STATE_ON
from homeassistant.util.dt import parse_datetime from homeassistant.util.dt import parse_datetime
from . import _handle_exception, EvoDevice from . import EvoDevice
from .const import DOMAIN, EVO_STRFTIME, EVO_FOLLOW, EVO_TEMPOVER, EVO_PERMOVER from .const import DOMAIN, EVO_STRFTIME, EVO_FOLLOW, EVO_TEMPOVER, EVO_PERMOVER
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,8 +20,13 @@ EVO_STATE_TO_HA = {v: k for k, v in HA_STATE_TO_EVO.items()}
HA_OPMODE_TO_DHW = {STATE_ON: EVO_FOLLOW, STATE_OFF: EVO_PERMOVER} HA_OPMODE_TO_DHW = {STATE_ON: EVO_FOLLOW, STATE_OFF: EVO_PERMOVER}
def setup_platform(hass, hass_config, add_entities, discovery_info=None) -> None: async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None
) -> None:
"""Create the DHW controller.""" """Create the DHW controller."""
if discovery_info is None:
return
broker = hass.data[DOMAIN]["broker"] broker = hass.data[DOMAIN]["broker"]
_LOGGER.debug( _LOGGER.debug(
@ -33,7 +35,7 @@ def setup_platform(hass, hass_config, add_entities, discovery_info=None) -> None
evo_dhw = EvoDHW(broker, broker.tcs.hotwater) evo_dhw = EvoDHW(broker, broker.tcs.hotwater)
add_entities([evo_dhw], update_before_add=True) async_add_entities([evo_dhw], update_before_add=True)
class EvoDHW(EvoDevice, WaterHeaterDevice): class EvoDHW(EvoDevice, WaterHeaterDevice):
@ -58,6 +60,11 @@ class EvoDHW(EvoDevice, WaterHeaterDevice):
self._supported_features = SUPPORT_OPERATION_MODE self._supported_features = SUPPORT_OPERATION_MODE
self._operation_list = list(HA_OPMODE_TO_DHW) self._operation_list = list(HA_OPMODE_TO_DHW)
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._evo_device.temperatureStatus.get("isAvailable", False)
@property @property
def current_operation(self) -> str: def current_operation(self) -> str:
"""Return the current operating mode (On, or Off).""" """Return the current operating mode (On, or Off)."""
@ -73,7 +80,7 @@ class EvoDHW(EvoDevice, WaterHeaterDevice):
"""Return the current temperature.""" """Return the current temperature."""
return self._evo_device.temperatureStatus["temperature"] return self._evo_device.temperatureStatus["temperature"]
def set_operation_mode(self, operation_mode: str) -> None: async def async_set_operation_mode(self, operation_mode: str) -> None:
"""Set new operation mode for a DHW controller.""" """Set new operation mode for a DHW controller."""
op_mode = HA_OPMODE_TO_DHW[operation_mode] op_mode = HA_OPMODE_TO_DHW[operation_mode]
@ -81,17 +88,13 @@ class EvoDHW(EvoDevice, WaterHeaterDevice):
until = None # EVO_FOLLOW, EVO_PERMOVER until = None # EVO_FOLLOW, EVO_PERMOVER
if op_mode == EVO_TEMPOVER and self._schedule["DailySchedules"]: if op_mode == EVO_TEMPOVER and self._schedule["DailySchedules"]:
self._update_schedule() await self._update_schedule()
if self._schedule["DailySchedules"]: if self._schedule["DailySchedules"]:
until = parse_datetime(self.setpoints["next"]["from"]) until = parse_datetime(self.setpoints["next"]["from"])
until = until.strftime(EVO_STRFTIME) until = until.strftime(EVO_STRFTIME)
data = {"Mode": op_mode, "State": state, "UntilTime": until} data = {"Mode": op_mode, "State": state, "UntilTime": until}
try: await self._call_client_api(
self._evo_device._set_dhw(data) # pylint: disable=protected-access self._evo_device._set_dhw(data) # pylint: disable=protected-access
except ( )
requests.exceptions.RequestException,
evohomeclient2.AuthenticationError,
) as err:
_handle_exception(err)

View File

@ -461,7 +461,7 @@ eternalegypt==0.0.10
# evdev==0.6.1 # evdev==0.6.1
# homeassistant.components.evohome # homeassistant.components.evohome
evohomeclient==0.3.3 evohome-async==0.3.3b4
# homeassistant.components.dlib_face_detect # homeassistant.components.dlib_face_detect
# homeassistant.components.dlib_face_identify # homeassistant.components.dlib_face_identify

View File

@ -123,9 +123,6 @@ enocean==0.50
# homeassistant.components.season # homeassistant.components.season
ephem==3.7.6.0 ephem==3.7.6.0
# homeassistant.components.evohome
evohomeclient==0.3.3
# homeassistant.components.feedreader # homeassistant.components.feedreader
feedparser-homeassistant==5.2.2.dev1 feedparser-homeassistant==5.2.2.dev1