1 """Base for evohome entity."""
3 from datetime
import datetime, timedelta, timezone
7 import evohomeasync2
as evo
8 from evohomeasync2.schema.const
import (
12 SZ_SYSTEM_MODE_STATUS,
21 from .
import EvoBroker, EvoService
22 from .const
import DOMAIN
23 from .helpers
import convert_dict, convert_until
25 _LOGGER = logging.getLogger(__name__)
29 """Base for any evohome-compatible entity (controller, DHW, zone).
31 This includes the controller, (1 to 12) heating zones and (optionally) a
35 _attr_should_poll =
False
39 evo_broker: EvoBroker,
40 evo_device: evo.ControlSystem | evo.HotWater | evo.Zone,
42 """Initialize an evohome-compatible entity (TCS, DHW, zone)."""
46 self._device_state_attrs: dict[str, Any] = {}
49 """Process any signals."""
53 if payload[
"unique_id"] != self._attr_unique_id:
55 if payload[
"service"]
in (
56 EvoService.SET_ZONE_OVERRIDE,
57 EvoService.RESET_ZONE_OVERRIDE,
64 """Process a service request (system mode) for a controller."""
65 raise NotImplementedError
68 """Process a service request (setpoint override) for a zone."""
69 raise NotImplementedError
73 """Return the evohome-specific state attributes."""
74 status = self._device_state_attrs
75 if SZ_SYSTEM_MODE_STATUS
in status:
77 if SZ_SETPOINT_STATUS
in status:
79 if SZ_STATE_STATUS
in status:
85 """Run when entity about to be added to hass."""
90 """Base for any evohome-compatible child entity (DHW, zone).
92 This includes (1 to 12) heating zones and (optionally) a DHW controller.
98 self, evo_broker: EvoBroker, evo_device: evo.HotWater | evo.Zone
100 """Initialize an evohome-compatible child entity (DHW, zone)."""
101 super().
__init__(evo_broker, evo_device)
105 self.
_schedule_schedule: dict[str, Any] = {}
106 self.
_setpoints_setpoints: dict[str, Any] = {}
110 """Return the current temperature of a Zone."""
112 assert isinstance(self.
_evo_device_evo_device, evo.HotWater | evo.Zone)
114 if (temp := self.
_evo_broker_evo_broker.temps.get(self._evo_id))
is not None:
121 """Return the current/next setpoints from the schedule.
123 Only Zones & DHW controllers (but not the TCS) can have schedules.
126 def _dt_evo_to_aware(dt_naive: datetime, utc_offset: timedelta) -> datetime:
127 dt_aware = dt_naive.replace(tzinfo=dt_util.UTC) - utc_offset
128 return dt_util.as_local(dt_aware)
130 if not (schedule := self.
_schedule_schedule.
get(
"DailySchedules")):
134 day_time = dt_util.now().astimezone(timezone(self.
_evo_broker_evo_broker.loc_utc_offset))
135 day_of_week = day_time.weekday()
136 time_of_day = day_time.strftime(
"%H:%M:%S")
140 day = schedule[day_of_week]
142 for i, tmp
in enumerate(day[
"Switchpoints"]):
143 if time_of_day > tmp[
"TimeOfDay"]:
149 this_sp_day = -1
if sp_idx == -1
else 0
150 next_sp_day = 1
if sp_idx + 1 == len(day[
"Switchpoints"])
else 0
152 for key, offset, idx
in (
153 (
"this", this_sp_day, sp_idx),
154 (
"next", next_sp_day, (sp_idx + 1) * (1 - next_sp_day)),
156 sp_date = (day_time +
timedelta(days=offset)).strftime(
"%Y-%m-%d")
157 day = schedule[(day_of_week + offset) % 7]
158 switchpoint = day[
"Switchpoints"][idx]
160 switchpoint_time_of_day = dt_util.parse_datetime(
161 f
"{sp_date}T{switchpoint['TimeOfDay']}"
163 assert switchpoint_time_of_day
is not None
164 dt_aware = _dt_evo_to_aware(
165 switchpoint_time_of_day, self.
_evo_broker_evo_broker.loc_utc_offset
168 self.
_setpoints_setpoints[f
"{key}_sp_from"] = dt_aware.isoformat()
170 self.
_setpoints_setpoints[f
"{key}_sp_temp"] = switchpoint[SZ_HEAT_SETPOINT]
172 self.
_setpoints_setpoints[f
"{key}_sp_state"] = switchpoint[
"DhwState"]
177 "Failed to get setpoints, report as an issue if this error persists",
184 """Get the latest schedule, if any."""
186 assert isinstance(self.
_evo_device_evo_device, evo.HotWater | evo.Zone)
189 schedule = await self.
_evo_broker_evo_broker.call_client_api(
190 self.
_evo_device_evo_device.get_schedule(), update_state=
False
192 except evo.InvalidSchedule
as err:
194 "%s: Unable to retrieve a valid schedule: %s",
202 _LOGGER.debug(
"Schedule['%s'] = %s", self.
namename, self.
_schedule_schedule)
205 """Get the latest state data."""
206 next_sp_from = self.
_setpoints_setpoints.
get(
"next_sp_from",
"2000-01-01T00:00:00+00:00")
207 next_sp_from_dt = dt_util.parse_datetime(next_sp_from)
208 if next_sp_from_dt
is None or dt_util.now() >= next_sp_from_dt:
None _update_schedule(self)
float|None current_temperature(self)
None __init__(self, EvoBroker evo_broker, evo.HotWater|evo.Zone evo_device)
dict[str, Any] setpoints(self)
None async_tcs_svc_request(self, str service, dict[str, Any] data)
None async_added_to_hass(self)
None async_zone_svc_request(self, str service, dict[str, Any] data)
dict[str, Any] extra_state_attributes(self)
None async_refresh(self, dict|None payload=None)
None __init__(self, EvoBroker evo_broker, evo.ControlSystem|evo.HotWater|evo.Zone evo_device)
None async_schedule_update_ha_state(self, bool force_refresh=False)
str|UndefinedType|None name(self)
web.Response get(self, web.Request request, str config_key)
dict[str, Any] convert_dict(dict[str, Any] dictionary)
None convert_until(dict status_dict, str until_key)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)