Add services to evohome (#29816)

pull/30941/head
David Bonnes 2020-01-18 12:21:22 +00:00 committed by GitHub
parent bfa8cb760f
commit a037c1d788
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 363 additions and 68 deletions

View File

@ -1,8 +1,8 @@
"""Support for (EMEA/EU-based) Honeywell TCC climate systems.
Such systems include evohome (multi-zone), and Round Thermostat (single zone).
Such systems include evohome, Round Thermostat, and others.
"""
from datetime import datetime, timedelta
from datetime import datetime as dt, timedelta
import logging
import re
from typing import Any, Dict, Optional, Tuple
@ -13,6 +13,7 @@ import evohomeasync2
import voluptuous as vol
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
@ -24,7 +25,12 @@ 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 async_load_platform
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.service import verify_domain_control
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
import homeassistant.util.dt as dt_util
@ -58,16 +64,44 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
ATTR_SYSTEM_MODE = "mode"
ATTR_DURATION_DAYS = "period"
ATTR_DURATION_HOURS = "duration"
def _local_dt_to_aware(dt_naive: datetime) -> datetime:
dt_aware = dt_util.now() + (dt_naive - datetime.now())
ATTR_ZONE_TEMP = "setpoint"
ATTR_DURATION_UNTIL = "duration"
SVC_REFRESH_SYSTEM = "refresh_system"
SVC_SET_SYSTEM_MODE = "set_system_mode"
SVC_RESET_SYSTEM = "reset_system"
SVC_SET_ZONE_OVERRIDE = "set_zone_override"
SVC_RESET_ZONE_OVERRIDE = "clear_zone_override"
RESET_ZONE_OVERRIDE_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.entity_id})
SET_ZONE_OVERRIDE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_ZONE_TEMP): vol.All(
vol.Coerce(float), vol.Range(min=4.0, max=35.0)
),
vol.Optional(ATTR_DURATION_UNTIL): vol.All(
cv.time_period, vol.Range(min=timedelta(days=0), max=timedelta(days=1)),
),
}
)
# system mode schemas are built dynamically, below
def _local_dt_to_aware(dt_naive: dt) -> dt:
dt_aware = dt_util.now() + (dt_naive - dt.now())
if dt_aware.microsecond >= 500000:
dt_aware += timedelta(seconds=1)
return dt_aware.replace(microsecond=0)
def _dt_to_local_naive(dt_aware: datetime) -> datetime:
dt_naive = datetime.now() + (dt_aware - dt_util.now())
def _dt_to_local_naive(dt_aware: dt) -> dt:
dt_naive = dt.now() + (dt_aware - dt_util.now())
if dt_naive.microsecond >= 500000:
dt_naive += timedelta(seconds=1)
return dt_naive.replace(microsecond=0)
@ -114,7 +148,7 @@ def _handle_exception(err) -> bool:
return False
except aiohttp.ClientConnectionError:
# this appears to be common with Honeywell's servers
# this appears to be a common occurance with the vendor's servers
_LOGGER.warning(
"Unable to connect with the vendor's server. "
"Check your network and the vendor's service status page. "
@ -143,7 +177,7 @@ def _handle_exception(err) -> bool:
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
"""Create a (EMEA/EU-based) Honeywell evohome system."""
"""Create a (EMEA/EU-based) Honeywell TCC system."""
async def load_auth_tokens(store) -> Tuple[Dict, Optional[Dict]]:
app_storage = await store.async_load()
@ -209,7 +243,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
)
await broker.save_auth_tokens()
await broker.update() # get initial state
await broker.async_update() # get initial state
hass.async_create_task(async_load_platform(hass, "climate", DOMAIN, {}, config))
if broker.tcs.hotwater:
@ -218,12 +252,133 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
)
hass.helpers.event.async_track_time_interval(
broker.update, config[DOMAIN][CONF_SCAN_INTERVAL]
broker.async_update, config[DOMAIN][CONF_SCAN_INTERVAL]
)
setup_service_functions(hass, broker)
return True
@callback
def setup_service_functions(hass: HomeAssistantType, broker):
"""Set up the service handlers for the system/zone operating modes.
Not all Honeywell TCC-compatible systems support all operating modes. In addition,
each mode will require any of four distinct service schemas. This has to be
enumerated before registering the approperiate handlers.
It appears that all TCC-compatible systems support the same three zones modes.
"""
@verify_domain_control(hass, DOMAIN)
async def force_refresh(call) -> None:
"""Obtain the latest state data via the vendor's RESTful API."""
await broker.async_update()
@verify_domain_control(hass, DOMAIN)
async def set_system_mode(call) -> None:
"""Set the system mode."""
payload = {
"unique_id": broker.tcs.systemId,
"service": call.service,
"data": call.data,
}
async_dispatcher_send(hass, DOMAIN, payload)
@verify_domain_control(hass, DOMAIN)
async def set_zone_override(call) -> None:
"""Set the zone override (setpoint)."""
entity_id = call.data[ATTR_ENTITY_ID]
registry = await hass.helpers.entity_registry.async_get_registry()
registry_entry = registry.async_get(entity_id)
if registry_entry is None or registry_entry.platform != DOMAIN:
raise ValueError(f"'{entity_id}' is not a known {DOMAIN} entity")
if registry_entry.domain != "climate":
raise ValueError(f"'{entity_id}' is not an {DOMAIN} controller/zone")
payload = {
"unique_id": registry_entry.unique_id,
"service": call.service,
"data": call.data,
}
async_dispatcher_send(hass, DOMAIN, payload)
hass.services.async_register(DOMAIN, SVC_REFRESH_SYSTEM, force_refresh)
# Enumerate which operating modes are supported by this system
modes = broker.config["allowedSystemModes"]
# Not all systems support "AutoWithReset": register this handler only if required
if [m["systemMode"] for m in modes if m["systemMode"] == "AutoWithReset"]:
hass.services.async_register(DOMAIN, SVC_RESET_SYSTEM, set_system_mode)
system_mode_schemas = []
modes = [m for m in modes if m["systemMode"] != "AutoWithReset"]
# Permanent-only modes will use this schema
perm_modes = [m["systemMode"] for m in modes if not m["canBeTemporary"]]
if perm_modes: # any of: "Auto", "HeatingOff": permanent only
schema = vol.Schema({vol.Required(ATTR_SYSTEM_MODE): vol.In(perm_modes)})
system_mode_schemas.append(schema)
modes = [m for m in modes if m["canBeTemporary"]]
# These modes are set for a number of hours (or indefinitely): use this schema
temp_modes = [m["systemMode"] for m in modes if m["timingMode"] == "Duration"]
if temp_modes: # any of: "AutoWithEco", permanent or for 0-24 hours
schema = vol.Schema(
{
vol.Required(ATTR_SYSTEM_MODE): vol.In(temp_modes),
vol.Optional(ATTR_DURATION_HOURS): vol.All(
cv.time_period,
vol.Range(min=timedelta(hours=0), max=timedelta(hours=24)),
),
}
)
system_mode_schemas.append(schema)
# These modes are set for a number of days (or indefinitely): use this schema
temp_modes = [m["systemMode"] for m in modes if m["timingMode"] == "Period"]
if temp_modes: # any of: "Away", "Custom", "DayOff", permanent or for 1-99 days
schema = vol.Schema(
{
vol.Required(ATTR_SYSTEM_MODE): vol.In(temp_modes),
vol.Optional(ATTR_DURATION_DAYS): vol.All(
cv.time_period,
vol.Range(min=timedelta(days=1), max=timedelta(days=99)),
),
}
)
system_mode_schemas.append(schema)
if system_mode_schemas:
hass.services.async_register(
DOMAIN,
SVC_SET_SYSTEM_MODE,
set_system_mode,
schema=vol.Any(*system_mode_schemas),
)
# The zone modes are consistent across all systems and use the same schema
hass.services.async_register(
DOMAIN,
SVC_RESET_ZONE_OVERRIDE,
set_zone_override,
schema=RESET_ZONE_OVERRIDE_SCHEMA,
)
hass.services.async_register(
DOMAIN,
SVC_SET_ZONE_OVERRIDE,
set_zone_override,
schema=SET_ZONE_OVERRIDE_SCHEMA,
)
class EvoBroker:
"""Container for evohome client and data."""
@ -238,7 +393,7 @@ class EvoBroker:
loc_idx = params[CONF_LOCATION_IDX]
self.config = client.installation_info[loc_idx][GWS][0][TCS][0]
self.tcs = client.locations[loc_idx]._gateways[0]._control_systems[0]
self.temps = None
self.temps = {}
async def save_auth_tokens(self) -> None:
"""Save access tokens and session IDs to the store for later use."""
@ -260,6 +415,19 @@ class EvoBroker:
await self._store.async_save(app_storage)
async def call_client_api(self, api_function, refresh=True) -> Any:
"""Call a client API."""
try:
result = await api_function
except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err:
if not _handle_exception(err):
return
if refresh:
self.hass.helpers.event.async_call_later(1, self.async_update())
return result
async def _update_v1(self, *args, **kwargs) -> None:
"""Get the latest high-precision temperatures of the default Location."""
@ -311,15 +479,15 @@ class EvoBroker:
except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err:
_handle_exception(err)
else:
self.hass.helpers.dispatcher.async_dispatcher_send(DOMAIN)
async_dispatcher_send(self.hass, DOMAIN)
_LOGGER.debug("Status = %s", status[GWS][0][TCS][0])
if access_token != self.client.access_token:
await self.save_auth_tokens()
async def update(self, *args, **kwargs) -> None:
"""Get the latest state data of an entire evohome Location.
async def async_update(self, *args, **kwargs) -> None:
"""Get the latest state data of an entire Honeywell TCC Location.
This includes state data for a Controller and all its child devices, such as the
operating mode of the Controller and the current temp of its children (e.g.
@ -331,7 +499,7 @@ class EvoBroker:
await self._update_v1()
# inform the evohome devices that state data has been updated
self.hass.helpers.dispatcher.async_dispatcher_send(DOMAIN)
async_dispatcher_send(self.hass, DOMAIN)
class EvoDevice(Entity):
@ -351,9 +519,25 @@ class EvoDevice(Entity):
self._supported_features = None
self._device_state_attrs = {}
@callback
def _refresh(self) -> None:
self.async_schedule_update_ha_state(force_refresh=True)
async def async_refresh(self, payload: Optional[dict] = None) -> None:
"""Process any signals."""
if payload is None:
self.async_schedule_update_ha_state(force_refresh=True)
return
if payload["unique_id"] != self._unique_id:
return
if payload["service"] in [SVC_SET_ZONE_OVERRIDE, SVC_RESET_ZONE_OVERRIDE]:
await self.async_zone_svc_request(payload["service"], payload["data"])
return
await self.async_tcs_svc_request(payload["service"], payload["data"])
async def async_tcs_svc_request(self, service: dict, data: dict) -> None:
"""Process a service request (system mode) for a controller."""
raise NotImplementedError
async def async_zone_svc_request(self, service: dict, data: dict) -> None:
"""Process a service request (setpoint override) for a zone."""
raise NotImplementedError
@property
def should_poll(self) -> bool:
@ -367,12 +551,12 @@ class EvoDevice(Entity):
@property
def name(self) -> str:
"""Return the name of the Evohome entity."""
"""Return the name of the evohome entity."""
return self._name
@property
def device_state_attributes(self) -> Dict[str, Any]:
"""Return the Evohome-specific state attributes."""
"""Return the evohome-specific state attributes."""
status = self._device_state_attrs
if "systemModeStatus" in status:
convert_until(status["systemModeStatus"], "timeUntil")
@ -395,7 +579,7 @@ class EvoDevice(Entity):
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
self.hass.helpers.dispatcher.async_dispatcher_connect(DOMAIN, self._refresh)
async_dispatcher_connect(self.hass, DOMAIN, self.async_refresh)
@property
def precision(self) -> float:
@ -407,18 +591,6 @@ class EvoDevice(Entity):
"""Return the temperature unit to use in the frontend UI."""
return TEMP_CELSIUS
async def _call_client_api(self, api_function, refresh=True) -> Any:
try:
result = await api_function
except (aiohttp.ClientError, evohomeasync2.AuthenticationError) as err:
if not _handle_exception(err):
return
if refresh is True:
self.hass.helpers.event.async_call_later(1, self._evo_broker.update())
return result
class EvoChild(EvoDevice):
"""Base for any evohome child.
@ -497,12 +669,12 @@ class EvoChild(EvoDevice):
return self._setpoints
async def _update_schedule(self) -> None:
"""Get the latest schedule."""
"""Get the latest schedule, if any."""
if "DailySchedules" in self._schedule and not self._schedule["DailySchedules"]:
if not self._evo_device.setpointStatus["setpointMode"] == EVO_FOLLOW:
return # avoid unnecessary I/O - there's nothing to update
self._schedule = await self._call_client_api(
self._schedule = await self._evo_broker.call_client_api(
self._evo_device.schedule(), refresh=False
)

View File

@ -1,4 +1,5 @@
"""Support for Climate devices of (EMEA/EU-based) Honeywell TCC systems."""
from datetime import datetime as dt
import logging
from typing import List, Optional
@ -21,7 +22,18 @@ 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, EvoChild, EvoDevice
from . import (
ATTR_DURATION_DAYS,
ATTR_DURATION_HOURS,
ATTR_DURATION_UNTIL,
ATTR_SYSTEM_MODE,
ATTR_ZONE_TEMP,
CONF_LOCATION_IDX,
SVC_RESET_ZONE_OVERRIDE,
SVC_SET_SYSTEM_MODE,
EvoChild,
EvoDevice,
)
from .const import (
DOMAIN,
EVO_AUTO,
@ -90,8 +102,9 @@ async def async_setup_platform(
zone.zoneId,
zone.name,
)
new_entity = EvoThermostat(broker, zone)
async_add_entities([EvoThermostat(broker, zone)], update_before_add=True)
async_add_entities([new_entity], update_before_add=True)
return
controller = EvoController(broker, broker.tcs)
@ -105,13 +118,15 @@ async def async_setup_platform(
zone.zoneId,
zone.name,
)
zones.append(EvoZone(broker, zone))
new_entity = EvoZone(broker, zone)
zones.append(new_entity)
async_add_entities([controller] + zones, update_before_add=True)
class EvoClimateDevice(EvoDevice, ClimateDevice):
"""Base for a Honeywell evohome Climate device."""
"""Base for an evohome Climate device."""
def __init__(self, evo_broker, evo_device) -> None:
"""Initialize a Climate device."""
@ -119,9 +134,31 @@ class EvoClimateDevice(EvoDevice, ClimateDevice):
self._preset_modes = None
async def _set_tcs_mode(self, op_mode: str) -> None:
async def async_tcs_svc_request(self, service: dict, data: dict) -> None:
"""Process a service request (system mode) for a controller.
Data validation is not required, it will have been done upstream.
"""
if service == SVC_SET_SYSTEM_MODE:
mode = data[ATTR_SYSTEM_MODE]
else: # otherwise it is SVC_RESET_SYSTEM
mode = EVO_RESET
if ATTR_DURATION_DAYS in data:
until = dt.combine(dt.now().date(), dt.min.time())
until += data[ATTR_DURATION_DAYS]
elif ATTR_DURATION_HOURS in data:
until = dt.now() + data[ATTR_DURATION_HOURS]
else:
until = None
await self._set_tcs_mode(mode, until=until)
async def _set_tcs_mode(self, mode: str, until: Optional[dt] = None) -> None:
"""Set a Controller to any of its native EVO_* operating modes."""
await self._call_client_api(self._evo_tcs.set_status(op_mode))
await self._evo_broker.call_client_api(self._evo_tcs.set_status(mode))
@property
def hvac_modes(self) -> List[str]:
@ -135,7 +172,7 @@ class EvoClimateDevice(EvoDevice, ClimateDevice):
class EvoZone(EvoChild, EvoClimateDevice):
"""Base for a Honeywell evohome Zone."""
"""Base for a Honeywell TCC Zone."""
def __init__(self, evo_broker, evo_device) -> None:
"""Initialize a Zone."""
@ -152,6 +189,32 @@ class EvoZone(EvoChild, EvoClimateDevice):
else:
self._precision = self._evo_device.setpointCapabilities["valueResolution"]
async def async_zone_svc_request(self, service: dict, data: dict) -> None:
"""Process a service request (setpoint override) for a zone."""
if service == SVC_RESET_ZONE_OVERRIDE:
await self._evo_broker.call_client_api(
self._evo_device.cancel_temp_override()
)
return
# otherwise it is SVC_SET_ZONE_OVERRIDE
temp = round(data[ATTR_ZONE_TEMP] * self.precision) / self.precision
temp = max(min(temp, self.max_temp), self.min_temp)
if ATTR_DURATION_UNTIL in data:
duration = data[ATTR_DURATION_UNTIL]
if duration == 0:
await self._update_schedule()
until = parse_datetime(str(self.setpoints.get("next_sp_from")))
else:
until = dt.now() + data[ATTR_DURATION_UNTIL]
else:
until = None # indefinitely
await self._evo_broker.call_client_api(
self._evo_device.set_temperature(temperature=temp, until=until)
)
@property
def hvac_mode(self) -> str:
"""Return the current operating mode of a Zone."""
@ -206,16 +269,16 @@ class EvoZone(EvoChild, EvoClimateDevice):
async def async_set_temperature(self, **kwargs) -> None:
"""Set a new target temperature."""
temperature = kwargs["temperature"]
until = kwargs.get("until")
if self._evo_device.setpointStatus["setpointMode"] == EVO_FOLLOW:
await self._update_schedule()
until = parse_datetime(str(self.setpoints.get("next_sp_from")))
elif self._evo_device.setpointStatus["setpointMode"] == EVO_TEMPOVER:
until = parse_datetime(self._evo_device.setpointStatus["until"])
else: # EVO_PERMOVER
until = None
if until is None:
if self._evo_device.setpointStatus["setpointMode"] == EVO_FOLLOW:
await self._update_schedule()
until = parse_datetime(str(self.setpoints.get("next_sp_from")))
elif self._evo_device.setpointStatus["setpointMode"] == EVO_TEMPOVER:
until = parse_datetime(self._evo_device.setpointStatus["until"])
await self._call_client_api(
await self._evo_broker.call_client_api(
self._evo_device.set_temperature(temperature, until)
)
@ -237,18 +300,22 @@ class EvoZone(EvoChild, EvoClimateDevice):
and 'Away', Zones to (by default) 12C.
"""
if hvac_mode == HVAC_MODE_OFF:
await self._call_client_api(
await self._evo_broker.call_client_api(
self._evo_device.set_temperature(self.min_temp, until=None)
)
else: # HVAC_MODE_HEAT
await self._call_client_api(self._evo_device.cancel_temp_override())
await self._evo_broker.call_client_api(
self._evo_device.cancel_temp_override()
)
async def async_set_preset_mode(self, preset_mode: Optional[str]) -> None:
"""Set the preset mode; if None, then revert to following the schedule."""
evo_preset_mode = HA_PRESET_TO_EVO.get(preset_mode, EVO_FOLLOW)
if evo_preset_mode == EVO_FOLLOW:
await self._call_client_api(self._evo_device.cancel_temp_override())
await self._evo_broker.call_client_api(
self._evo_device.cancel_temp_override()
)
return
temperature = self._evo_device.setpointStatus["targetHeatTemperature"]
@ -259,7 +326,7 @@ class EvoZone(EvoChild, EvoClimateDevice):
else: # EVO_PERMOVER
until = None
await self._call_client_api(
await self._evo_broker.call_client_api(
self._evo_device.set_temperature(temperature, until)
)
@ -272,14 +339,14 @@ class EvoZone(EvoChild, EvoClimateDevice):
class EvoController(EvoClimateDevice):
"""Base for a Honeywell evohome Controller (hub).
"""Base for a Honeywell TCC Controller (hub).
The Controller (aka TCS, temperature control system) is the parent of all
the child (CH/DHW) devices. It is also a Climate device.
"""
def __init__(self, evo_broker, evo_device) -> None:
"""Initialize a evohome Controller (hub)."""
"""Initialize an evohome Controller (hub)."""
super().__init__(evo_broker, evo_device)
self._unique_id = evo_device.systemId
@ -349,7 +416,7 @@ class EvoController(EvoClimateDevice):
class EvoThermostat(EvoZone):
"""Base for a Honeywell Round Thermostat.
"""Base for a Honeywell TCC Round Thermostat.
These are implemented as a combined Controller/Zone.
"""

View File

@ -13,7 +13,7 @@ EVO_DAYOFF = "DayOff"
EVO_CUSTOM = "Custom"
EVO_HEATOFF = "HeatingOff"
# The Childs' operating mode is one of:
# The Children's operating mode is one of:
EVO_FOLLOW = "FollowSchedule" # the operating mode is 'inherited' from the TCS
EVO_TEMPOVER = "TemporaryOverride"
EVO_PERMOVER = "PermanentOverride"

View File

@ -0,0 +1,53 @@
# Support for (EMEA/EU-based) Honeywell TCC climate systems.
# Describes the format for available services
set_system_mode:
description: >-
Set the system mode, either indefinitely, or for a specified period of time, after
which it will revert to Auto. Not all systems support all modes.
fields:
mode:
description: 'One of: Auto, AutoWithEco, Away, DayOff, HeatingOff, or Custom.'
example: Away
period:
description: >-
A period of time in days; used only with Away, DayOff, or Custom. The system
will revert to Auto at midnight (up to 99 days, today is day 1).
example: '{"days": 28}'
duration:
description: The duration in hours; used only with AutoWithEco (up to 24 hours).
example: '{"hours": 18}'
reset_system:
description: >-
Set the system to Auto mode and reset all the zones to follow their schedules.
Not all Evohome systems support this feature (i.e. AutoWithReset mode).
refresh_system:
description: >-
Pull the latest data from the vendor's servers now, rather than waiting for the
next scheduled update.
set_zone_override:
description: >-
Override a zone's setpoint, either indefinitely, or for a specified period of
time, after which it will revert to following its schedule.
fields:
entity_id:
description: The entity_id of the Evohome zone.
example: climate.bathroom
setpoint:
description: The temperature to be used instead of the scheduled setpoint.
example: 5.0
duration:
description: >-
The zone will revert to its schedule after this time. If 0 the change is until
the next scheduled setpoint.
example: '{"minutes": 135}'
clear_zone_override:
description: Set a zone to follow its schedule.
fields:
entity_id:
description: The entity_id of the zone.
example: climate.bathroom

View File

@ -38,17 +38,16 @@ async def async_setup_platform(
broker.tcs.hotwater.zone_type,
broker.tcs.hotwater.zoneId,
)
new_entity = EvoDHW(broker, broker.tcs.hotwater)
evo_dhw = EvoDHW(broker, broker.tcs.hotwater)
async_add_entities([evo_dhw], update_before_add=True)
async_add_entities([new_entity], update_before_add=True)
class EvoDHW(EvoChild, WaterHeaterDevice):
"""Base for a Honeywell evohome DHW controller (aka boiler)."""
"""Base for a Honeywell TCC DHW controller (aka boiler)."""
def __init__(self, evo_broker, evo_device) -> None:
"""Initialize a evohome DHW controller."""
"""Initialize an evohome DHW controller."""
super().__init__(evo_broker, evo_device)
self._unique_id = evo_device.dhwId
@ -88,23 +87,27 @@ class EvoDHW(EvoChild, WaterHeaterDevice):
Except for Auto, the mode is only until the next SetPoint.
"""
if operation_mode == STATE_AUTO:
await self._call_client_api(self._evo_device.set_dhw_auto())
await self._evo_broker.call_client_api(self._evo_device.set_dhw_auto())
else:
await self._update_schedule()
until = parse_datetime(str(self.setpoints.get("next_sp_from")))
if operation_mode == STATE_ON:
await self._call_client_api(self._evo_device.set_dhw_on(until))
await self._evo_broker.call_client_api(
self._evo_device.set_dhw_on(until)
)
else: # STATE_OFF
await self._call_client_api(self._evo_device.set_dhw_off(until))
await self._evo_broker.call_client_api(
self._evo_device.set_dhw_off(until)
)
async def async_turn_away_mode_on(self):
"""Turn away mode on."""
await self._call_client_api(self._evo_device.set_dhw_off())
await self._evo_broker.call_client_api(self._evo_device.set_dhw_off())
async def async_turn_away_mode_off(self):
"""Turn away mode off."""
await self._call_client_api(self._evo_device.set_dhw_auto())
await self._evo_broker.call_client_api(self._evo_device.set_dhw_auto())
async def async_update(self) -> None:
"""Get the latest state data for a DHW controller."""