22
33from __future__ import annotations
44
5+ from dataclasses import dataclass
56from typing import Any
67
78from homeassistant .components .climate import (
2324 UnitOfTemperature ,
2425)
2526from homeassistant .core import HomeAssistant , callback
27+ from homeassistant .exceptions import HomeAssistantError
2628from homeassistant .helpers .entity_platform import AddConfigEntryEntitiesCallback
29+ from homeassistant .helpers .restore_state import ExtraStoredData , RestoreEntity
2730
2831from .const import (
2932 ACTIVE_PRESET ,
5154from .entity import PlugwiseEntity
5255from .util import plugwise_command
5356
57+ ERROR_NO_SCHEDULE = "Failed setting HVACMode, set a schedule first"
5458PARALLEL_UPDATES = 0
5559
5660
@@ -97,7 +101,30 @@ def _add_entities() -> None:
97101 entry .async_on_unload (coordinator .async_add_listener (_add_entities ))
98102
99103
100- class PlugwiseClimateEntity (PlugwiseEntity , ClimateEntity ):
104+ @dataclass
105+ class PlugwiseClimateExtraStoredData (ExtraStoredData ):
106+ """Object to hold extra stored data."""
107+
108+ last_active_schedule : str | None
109+ previous_action_mode : str | None
110+
111+ def as_dict (self ) -> dict [str , Any ]:
112+ """Return a dict representation of the text data."""
113+ return {
114+ "last_active_schedule" : self .last_active_schedule ,
115+ "previous_action_mode" : self .previous_action_mode ,
116+ }
117+
118+ @classmethod
119+ def from_dict (cls , restored : dict [str , Any ]) -> PlugwiseClimateExtraStoredData :
120+ """Initialize a stored data object from a dict."""
121+ return cls (
122+ last_active_schedule = restored .get ("last_active_schedule" ),
123+ previous_action_mode = restored .get ("previous_action_mode" ),
124+ )
125+
126+
127+ class PlugwiseClimateEntity (PlugwiseEntity , ClimateEntity , RestoreEntity ):
101128 """Representation of a Plugwise thermostat."""
102129
103130 _attr_has_entity_name = True
@@ -106,9 +133,21 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
106133 _attr_translation_key = DOMAIN
107134 _enable_turn_on_off_backwards_compatibility = False
108135
109- _previous_mode : str = HVACAction .HEATING # Upstream
136+ _last_active_schedule : str | None = None
137+ _previous_action_mode : str | None = HVACAction .HEATING .value # Upstream
110138 _homekit_mode : HVACMode | None = None # pw-beta homekit emulation + intentional unsort
111139
140+ async def async_added_to_hass (self ) -> None :
141+ """Run when entity about to be added."""
142+ await super ().async_added_to_hass ()
143+
144+ if extra_data := await self .async_get_last_extra_data ():
145+ plugwise_extra_data = PlugwiseClimateExtraStoredData .from_dict (
146+ extra_data .as_dict ()
147+ )
148+ self ._last_active_schedule = plugwise_extra_data .last_active_schedule
149+ self ._previous_action_mode = plugwise_extra_data .previous_action_mode
150+
112151 def __init__ (
113152 self ,
114153 coordinator : PlugwiseDataUpdateCoordinator ,
@@ -121,7 +160,6 @@ def __init__(
121160 gateway_id : str = coordinator .api .gateway_id
122161 self ._gateway_data = coordinator .data [gateway_id ]
123162 self ._homekit_enabled = homekit_enabled # pw-beta homekit emulation
124-
125163 self ._location = device_id
126164 if (location := self .device .get (LOCATION )) is not None :
127165 self ._location = location
@@ -151,25 +189,19 @@ def __init__(
151189 self ._attr_supported_features |= ClimateEntityFeature .PRESET_MODE
152190 self ._attr_preset_modes = presets
153191
154- def _previous_action_mode (self , coordinator : PlugwiseDataUpdateCoordinator ) -> None :
155- """Return the previous action-mode when the regulation-mode is not heating or cooling.
156-
157- Helper for set_hvac_mode().
158- """
159- # When no cooling available, _previous_mode is always heating
160- if (
161- REGULATION_MODES in self ._gateway_data
162- and HVACAction .COOLING in self ._gateway_data [REGULATION_MODES ]
163- ):
164- mode = self ._gateway_data [SELECT_REGULATION_MODE ]
165- if mode in (HVACAction .COOLING , HVACAction .HEATING ):
166- self ._previous_mode = mode
167-
168192 @property
169193 def current_temperature (self ) -> float | None :
170194 """Return the current temperature."""
171195 return self .device .get (SENSORS , {}).get (ATTR_TEMPERATURE )
172196
197+ @property
198+ def extra_restore_state_data (self ) -> PlugwiseClimateExtraStoredData :
199+ """Return text specific state data to be restored."""
200+ return PlugwiseClimateExtraStoredData (
201+ last_active_schedule = self ._last_active_schedule ,
202+ previous_action_mode = self ._previous_action_mode ,
203+ )
204+
173205 @property
174206 def target_temperature (self ) -> float | None :
175207 """Return the temperature we try to reach.
@@ -203,13 +235,14 @@ def hvac_mode(self) -> HVACMode:
203235 return HVACMode .HEAT # pragma: no cover
204236 try :
205237 hvac = HVACMode (mode )
206- except ValueError :
238+ except ValueError : # pragma: no cover
207239 return HVACMode .HEAT # pragma: no cover
208240 if hvac not in self .hvac_modes :
209241 return HVACMode .HEAT # pragma: no cover
210242 # pw-beta homekit emulation
211243 if self ._homekit_enabled and self ._homekit_mode == HVACMode .OFF :
212244 return HVACMode .OFF # pragma: no cover
245+
213246 return hvac
214247
215248 @property
@@ -228,9 +261,9 @@ def hvac_modes(self) -> list[HVACMode]:
228261 if self .coordinator .api .cooling_present :
229262 if REGULATION_MODES in self ._gateway_data :
230263 selected = self ._gateway_data .get (SELECT_REGULATION_MODE )
231- if selected == HVACAction .COOLING :
264+ if selected == HVACAction .COOLING . value :
232265 hvac_modes .append (HVACMode .COOL )
233- if selected == HVACAction .HEATING :
266+ if selected == HVACAction .HEATING . value :
234267 hvac_modes .append (HVACMode .HEAT )
235268 else :
236269 hvac_modes .append (HVACMode .HEAT_COOL )
@@ -242,8 +275,15 @@ def hvac_modes(self) -> list[HVACMode]:
242275 @property
243276 def hvac_action (self ) -> HVACAction : # pw-beta add to Core
244277 """Return the current running hvac operation if supported."""
245- # Keep track of the previous action-mode
246- self ._previous_action_mode (self .coordinator )
278+ # Keep track of the previous hvac_action mode.
279+ # When no cooling available, _previous_action_mode is always heating
280+ if (
281+ REGULATION_MODES in self ._gateway_data
282+ and HVACAction .COOLING .value in self ._gateway_data [REGULATION_MODES ]
283+ ):
284+ mode = self ._gateway_data [SELECT_REGULATION_MODE ]
285+ if mode in (HVACAction .COOLING .value , HVACAction .HEATING .value ):
286+ self ._previous_action_mode = mode
247287
248288 if (action := self .device .get (CONTROL_STATE )) is not None :
249289 return HVACAction (action )
@@ -280,20 +320,38 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
280320 return
281321
282322 if hvac_mode != HVACMode .OFF :
323+ current = self .device .get ("select_schedule" )
324+ desired = current
325+
326+ # Capture the last valid schedule
327+ if desired and desired != "off" :
328+ self ._last_active_schedule = desired
329+ elif desired == "off" :
330+ desired = self ._last_active_schedule
331+
332+ # Enabling HVACMode.AUTO requires a previously set schedule for saving and restoring
333+ if hvac_mode == HVACMode .AUTO and not desired :
334+ raise HomeAssistantError (ERROR_NO_SCHEDULE )
335+
283336 await self .coordinator .api .set_schedule_state (
284337 self ._location ,
285338 STATE_ON if hvac_mode == HVACMode .AUTO else STATE_OFF ,
339+ desired ,
286340 )
287341
342+ await self ._homekit_translate_or_not (hvac_mode ) # pw-beta
343+
344+ async def _homekit_translate_or_not (self , mode : HVACMode ) -> None :
345+ """Mimic HomeKit by setting a suitable preset, when homekit mode is enabled."""
288346 if (
289- not self ._homekit_enabled
290- ): # pw-beta: feature request - mimic HomeKit behavior
291- if hvac_mode == HVACMode .OFF :
292- await self .coordinator .api .set_regulation_mode (hvac_mode )
293- elif self .hvac_mode == HVACMode .OFF :
294- await self .coordinator .api .set_regulation_mode (self ._previous_mode )
295- else :
296- self ._homekit_mode = hvac_mode # pragma: no cover
347+ not self ._homekit_enabled # pw-beta
348+ ):
349+ if mode == HVACMode .OFF :
350+ await self .coordinator .api .set_regulation_mode (mode . value )
351+ elif self .hvac_mode == HVACMode .OFF and self . _previous_action_mode :
352+ await self .coordinator .api .set_regulation_mode (self ._previous_action_mode )
353+ else : # pw-beta
354+ self ._homekit_mode = mode # pragma: no cover
297355 if self ._homekit_mode == HVACMode .OFF : # pragma: no cover
298356 await self .async_set_preset_mode (PRESET_AWAY ) # pragma: no cover
299357 if (
0 commit comments