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:
parent
298aafc79d
commit
f91dd4f5f8
6 changed files with 135 additions and 143 deletions
|
@ -2,14 +2,13 @@
|
|||
|
||||
Such systems include evohome (multi-zone), and Round Thermostat (single zone).
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
import requests.exceptions
|
||||
import aiohttp.client_exceptions
|
||||
import voluptuous as vol
|
||||
import evohomeclient2
|
||||
import evohomeasync2
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_ACCESS_TOKEN,
|
||||
|
@ -21,17 +20,10 @@ from homeassistant.const import (
|
|||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.discovery import load_platform
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
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.util.dt import parse_datetime, utcnow
|
||||
|
||||
|
@ -81,55 +73,60 @@ def _handle_exception(err) -> bool:
|
|||
try:
|
||||
raise err
|
||||
|
||||
except evohomeclient2.AuthenticationError:
|
||||
except evohomeasync2.AuthenticationError:
|
||||
_LOGGER.error(
|
||||
"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. "
|
||||
"Message is: %s",
|
||||
err,
|
||||
)
|
||||
return False
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
except aiohttp.ClientConnectionError:
|
||||
# this appears to be common with Honeywell's servers
|
||||
_LOGGER.warning(
|
||||
"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",
|
||||
err,
|
||||
)
|
||||
return False
|
||||
|
||||
except requests.exceptions.HTTPError:
|
||||
if err.response.status_code == HTTP_SERVICE_UNAVAILABLE:
|
||||
except aiohttp.ClientResponseError:
|
||||
if err.status == HTTP_SERVICE_UNAVAILABLE:
|
||||
_LOGGER.warning(
|
||||
"Vendor says their server is currently unavailable. "
|
||||
"Check the vendor's status page."
|
||||
"The vendor says their server is currently unavailable. "
|
||||
"Check the vendor's service status page."
|
||||
)
|
||||
return False
|
||||
|
||||
if err.response.status_code == HTTP_TOO_MANY_REQUESTS:
|
||||
if err.status == HTTP_TOO_MANY_REQUESTS:
|
||||
_LOGGER.warning(
|
||||
"The vendor's API rate limit has been exceeded. "
|
||||
"Consider increasing the %s.",
|
||||
"If this message persists, consider increasing the %s.",
|
||||
CONF_SCAN_INTERVAL,
|
||||
)
|
||||
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."""
|
||||
broker = EvoBroker(hass, hass_config[DOMAIN])
|
||||
if not broker.init_client():
|
||||
broker = EvoBroker(hass, config[DOMAIN])
|
||||
if not await broker.init_client():
|
||||
return False
|
||||
|
||||
load_platform(hass, "climate", DOMAIN, {}, hass_config)
|
||||
hass.async_create_task(async_load_platform(hass, "climate", DOMAIN, {}, config))
|
||||
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
|
||||
|
||||
|
@ -141,8 +138,7 @@ class EvoBroker:
|
|||
"""Initialize the evohome client and data structure."""
|
||||
self.hass = hass
|
||||
self.params = params
|
||||
|
||||
self.config = self.status = self.timers = {}
|
||||
self.config = {}
|
||||
|
||||
self.client = self.tcs = None
|
||||
self._app_storage = {}
|
||||
|
@ -150,32 +146,31 @@ class EvoBroker:
|
|||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN]["broker"] = self
|
||||
|
||||
def init_client(self) -> bool:
|
||||
async def init_client(self) -> bool:
|
||||
"""Initialse the evohome data broker.
|
||||
|
||||
Return True if this is successful, otherwise return False.
|
||||
"""
|
||||
refresh_token, access_token, access_token_expires = asyncio.run_coroutine_threadsafe(
|
||||
self._load_auth_tokens(), self.hass.loop
|
||||
).result()
|
||||
refresh_token, access_token, access_token_expires = (
|
||||
await self._load_auth_tokens()
|
||||
)
|
||||
|
||||
# evohomeclient2 uses naive/local datetimes
|
||||
# evohomeasync2 uses naive/local datetimes
|
||||
if access_token_expires is not None:
|
||||
access_token_expires = _utc_to_local_dt(access_token_expires)
|
||||
|
||||
try:
|
||||
client = self.client = evohomeclient2.EvohomeClient(
|
||||
self.params[CONF_USERNAME],
|
||||
self.params[CONF_PASSWORD],
|
||||
refresh_token=refresh_token,
|
||||
access_token=access_token,
|
||||
access_token_expires=access_token_expires,
|
||||
)
|
||||
client = self.client = evohomeasync2.EvohomeClient(
|
||||
self.params[CONF_USERNAME],
|
||||
self.params[CONF_PASSWORD],
|
||||
refresh_token=refresh_token,
|
||||
access_token=access_token,
|
||||
access_token_expires=access_token_expires,
|
||||
session=async_get_clientsession(self.hass),
|
||||
)
|
||||
|
||||
except (
|
||||
requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError,
|
||||
) as err:
|
||||
try:
|
||||
await client.login()
|
||||
except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err:
|
||||
if not _handle_exception(err):
|
||||
return False
|
||||
|
||||
|
@ -200,17 +195,14 @@ class EvoBroker:
|
|||
return False
|
||||
|
||||
self.tcs = (
|
||||
client.locations[loc_idx] # noqa: E501; pylint: disable=protected-access
|
||||
client.locations[loc_idx] # pylint: disable=protected-access
|
||||
._gateways[0]
|
||||
._control_systems[0]
|
||||
)
|
||||
|
||||
_LOGGER.debug("Config = %s", self.config)
|
||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||
# don't do an I/O unless required
|
||||
_LOGGER.debug(
|
||||
"Status = %s", client.locations[loc_idx].status()[GWS][0][TCS][0]
|
||||
)
|
||||
if _LOGGER.isEnabledFor(logging.DEBUG): # don't do an I/O unless required
|
||||
await self.update() # includes: _LOGGER.debug("Status = %s"...
|
||||
|
||||
return True
|
||||
|
||||
|
@ -237,7 +229,7 @@ class EvoBroker:
|
|||
return (None, None, None) # account switched: so tokens wont be valid
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
await store.async_save(self._app_storage)
|
||||
|
||||
async_track_point_in_utc_time(
|
||||
self.hass,
|
||||
self.hass.helpers.event.async_track_point_in_utc_time(
|
||||
self._save_auth_tokens,
|
||||
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.
|
||||
|
||||
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]
|
||||
|
||||
try:
|
||||
status = self.client.locations[loc_idx].status()[GWS][0][TCS][0]
|
||||
except (
|
||||
requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError,
|
||||
) as err:
|
||||
status = await self.client.locations[loc_idx].status()
|
||||
except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err:
|
||||
_handle_exception(err)
|
||||
else:
|
||||
self.timers["statusUpdated"] = utcnow()
|
||||
|
||||
_LOGGER.debug("Status = %s", status)
|
||||
|
||||
# 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):
|
||||
|
@ -289,6 +277,7 @@ class EvoDevice(Entity):
|
|||
def __init__(self, evo_broker, evo_device) -> None:
|
||||
"""Initialize the evohome entity."""
|
||||
self._evo_device = evo_device
|
||||
self._evo_broker = evo_broker
|
||||
self._evo_tcs = evo_broker.tcs
|
||||
|
||||
self._name = self._icon = self._precision = None
|
||||
|
@ -387,7 +376,7 @@ class EvoDevice(Entity):
|
|||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""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
|
||||
def precision(self) -> float:
|
||||
|
@ -399,14 +388,27 @@ class EvoDevice(Entity):
|
|||
"""Return the temperature unit to use in the frontend UI."""
|
||||
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."""
|
||||
if (
|
||||
not self._schedule.get("DailySchedules")
|
||||
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."""
|
||||
self._update_schedule()
|
||||
await self._update_schedule()
|
||||
|
|
|
@ -3,9 +3,6 @@ from datetime import datetime
|
|||
import logging
|
||||
from typing import Any, Dict, Optional, List
|
||||
|
||||
import requests.exceptions
|
||||
import evohomeclient2
|
||||
|
||||
from homeassistant.components.climate import ClimateDevice
|
||||
from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_HEAT,
|
||||
|
@ -25,7 +22,7 @@ from homeassistant.const import PRECISION_TENTHS
|
|||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||
from homeassistant.util.dt import parse_datetime
|
||||
|
||||
from . import CONF_LOCATION_IDX, _handle_exception, EvoDevice
|
||||
from . import CONF_LOCATION_IDX, EvoDevice
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
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()}
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistantType, hass_config: ConfigType, add_entities, discovery_info=None
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistantType, config: ConfigType, async_add_entities, discovery_info=None
|
||||
) -> None:
|
||||
"""Create the evohome Controller, and its Zones, if any."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
broker = hass.data[DOMAIN]["broker"]
|
||||
loc_idx = broker.params[CONF_LOCATION_IDX]
|
||||
|
||||
|
@ -91,7 +91,7 @@ def setup_platform(
|
|||
zone.name,
|
||||
)
|
||||
|
||||
add_entities([EvoThermostat(broker, zone)], update_before_add=True)
|
||||
async_add_entities([EvoThermostat(broker, zone)], update_before_add=True)
|
||||
return
|
||||
|
||||
controller = EvoController(broker, broker.tcs)
|
||||
|
@ -107,7 +107,7 @@ def setup_platform(
|
|||
)
|
||||
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):
|
||||
|
@ -119,22 +119,18 @@ class EvoClimateDevice(EvoDevice, ClimateDevice):
|
|||
|
||||
self._preset_modes = None
|
||||
|
||||
def _set_temperature(
|
||||
async def _set_temperature(
|
||||
self, temperature: float, until: Optional[datetime] = None
|
||||
) -> None:
|
||||
"""Set a new target temperature for the Zone.
|
||||
|
||||
until == None means indefinitely (i.e. PermanentOverride)
|
||||
"""
|
||||
try:
|
||||
await self._call_client_api(
|
||||
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.
|
||||
|
||||
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.
|
||||
"""
|
||||
if op_mode == EVO_FOLLOW:
|
||||
try:
|
||||
self._evo_device.cancel_temp_override()
|
||||
except (
|
||||
requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError,
|
||||
) as err:
|
||||
_handle_exception(err)
|
||||
await self._call_client_api(self._evo_device.cancel_temp_override())
|
||||
return
|
||||
|
||||
temperature = self._evo_device.setpointStatus["targetHeatTemperature"]
|
||||
until = None # EVO_PERMOVER
|
||||
|
||||
if op_mode == EVO_TEMPOVER and self._schedule["DailySchedules"]:
|
||||
self._update_schedule()
|
||||
await self._update_schedule()
|
||||
if self._schedule["DailySchedules"]:
|
||||
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."""
|
||||
try:
|
||||
# noqa: E501; pylint: disable=protected-access
|
||||
self._evo_tcs._set_status(op_mode)
|
||||
except (
|
||||
requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError,
|
||||
) as err:
|
||||
_handle_exception(err)
|
||||
await self._call_client_api(
|
||||
self._evo_tcs._set_status(op_mode) # pylint: disable=protected-access
|
||||
)
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> List[str]:
|
||||
|
@ -216,6 +201,11 @@ class EvoZone(EvoClimateDevice):
|
|||
self._supported_features = SUPPORT_PRESET_MODE | SUPPORT_TARGET_TEMPERATURE
|
||||
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
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return the current operating mode of the evohome Zone."""
|
||||
|
@ -276,28 +266,28 @@ class EvoZone(EvoClimateDevice):
|
|||
"""
|
||||
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."""
|
||||
until = kwargs.get("until")
|
||||
if 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."""
|
||||
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
|
||||
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.
|
||||
|
||||
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):
|
||||
|
@ -344,25 +334,25 @@ class EvoController(EvoClimateDevice):
|
|||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
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.
|
||||
|
||||
The evohome Controller doesn't have a target temperature.
|
||||
"""
|
||||
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."""
|
||||
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.
|
||||
|
||||
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."""
|
||||
return
|
||||
|
||||
|
@ -409,16 +399,16 @@ class EvoThermostat(EvoZone):
|
|||
|
||||
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."""
|
||||
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.
|
||||
|
||||
If preset_mode is None, then revert to following the schedule.
|
||||
"""
|
||||
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:
|
||||
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))
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Evohome",
|
||||
"documentation": "https://www.home-assistant.io/components/evohome",
|
||||
"requirements": [
|
||||
"evohomeclient==0.3.3"
|
||||
"evohome-async==0.3.3b4"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": ["@zxdavb"]
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
import logging
|
||||
from typing import List
|
||||
|
||||
import requests.exceptions
|
||||
import evohomeclient2
|
||||
|
||||
from homeassistant.components.water_heater import (
|
||||
SUPPORT_OPERATION_MODE,
|
||||
WaterHeaterDevice,
|
||||
|
@ -12,7 +9,7 @@ from homeassistant.components.water_heater import (
|
|||
from homeassistant.const import PRECISION_WHOLE, STATE_OFF, STATE_ON
|
||||
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
|
||||
|
||||
_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}
|
||||
|
||||
|
||||
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."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
broker = hass.data[DOMAIN]["broker"]
|
||||
|
||||
_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)
|
||||
|
||||
add_entities([evo_dhw], update_before_add=True)
|
||||
async_add_entities([evo_dhw], update_before_add=True)
|
||||
|
||||
|
||||
class EvoDHW(EvoDevice, WaterHeaterDevice):
|
||||
|
@ -58,6 +60,11 @@ class EvoDHW(EvoDevice, WaterHeaterDevice):
|
|||
self._supported_features = SUPPORT_OPERATION_MODE
|
||||
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
|
||||
def current_operation(self) -> str:
|
||||
"""Return the current operating mode (On, or Off)."""
|
||||
|
@ -73,7 +80,7 @@ class EvoDHW(EvoDevice, WaterHeaterDevice):
|
|||
"""Return the current 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."""
|
||||
op_mode = HA_OPMODE_TO_DHW[operation_mode]
|
||||
|
||||
|
@ -81,17 +88,13 @@ class EvoDHW(EvoDevice, WaterHeaterDevice):
|
|||
until = None # EVO_FOLLOW, EVO_PERMOVER
|
||||
|
||||
if op_mode == EVO_TEMPOVER and self._schedule["DailySchedules"]:
|
||||
self._update_schedule()
|
||||
await self._update_schedule()
|
||||
if self._schedule["DailySchedules"]:
|
||||
until = parse_datetime(self.setpoints["next"]["from"])
|
||||
until = until.strftime(EVO_STRFTIME)
|
||||
|
||||
data = {"Mode": op_mode, "State": state, "UntilTime": until}
|
||||
|
||||
try:
|
||||
await self._call_client_api(
|
||||
self._evo_device._set_dhw(data) # pylint: disable=protected-access
|
||||
except (
|
||||
requests.exceptions.RequestException,
|
||||
evohomeclient2.AuthenticationError,
|
||||
) as err:
|
||||
_handle_exception(err)
|
||||
)
|
||||
|
|
|
@ -461,7 +461,7 @@ eternalegypt==0.0.10
|
|||
# evdev==0.6.1
|
||||
|
||||
# homeassistant.components.evohome
|
||||
evohomeclient==0.3.3
|
||||
evohome-async==0.3.3b4
|
||||
|
||||
# homeassistant.components.dlib_face_detect
|
||||
# homeassistant.components.dlib_face_identify
|
||||
|
|
|
@ -123,9 +123,6 @@ enocean==0.50
|
|||
# homeassistant.components.season
|
||||
ephem==3.7.6.0
|
||||
|
||||
# homeassistant.components.evohome
|
||||
evohomeclient==0.3.3
|
||||
|
||||
# homeassistant.components.feedreader
|
||||
feedparser-homeassistant==5.2.2.dev1
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue