22
33from __future__ import annotations
44
5+ from dataclasses import dataclass
56from typing import Any
67
78from homeassistant .components .climate import (
1314 HVACAction ,
1415 HVACMode ,
1516)
16- from homeassistant .const import ATTR_TEMPERATURE , UnitOfTemperature
17+ from homeassistant .const import ATTR_TEMPERATURE , STATE_OFF , STATE_ON , UnitOfTemperature
1718from homeassistant .core import HomeAssistant , callback
19+ from homeassistant .exceptions import HomeAssistantError
1820from homeassistant .helpers .entity_platform import AddConfigEntryEntitiesCallback
21+ from homeassistant .helpers .restore_state import ExtraStoredData , RestoreEntity
1922
2023from .const import DOMAIN , MASTER_THERMOSTATS
2124from .coordinator import PlugwiseConfigEntry , PlugwiseDataUpdateCoordinator
2225from .entity import PlugwiseEntity
2326from .util import plugwise_command
2427
28+ ERROR_NO_SCHEDULE = "set_schedule_first"
2529PARALLEL_UPDATES = 0
2630
2731
32+ @dataclass
33+ class PlugwiseClimateExtraStoredData (ExtraStoredData ):
34+ """Object to hold extra stored data."""
35+
36+ last_active_schedule : str | None
37+ previous_action_mode : str | None
38+
39+ def as_dict (self ) -> dict [str , Any ]:
40+ """Return a dict representation of the text data."""
41+ return {
42+ "last_active_schedule" : self .last_active_schedule ,
43+ "previous_action_mode" : self .previous_action_mode ,
44+ }
45+
46+ @classmethod
47+ def from_dict (cls , restored : dict [str , Any ]) -> PlugwiseClimateExtraStoredData :
48+ """Initialize a stored data object from a dict."""
49+ return cls (
50+ last_active_schedule = restored .get ("last_active_schedule" ),
51+ previous_action_mode = restored .get ("previous_action_mode" ),
52+ )
53+
54+
2855async def async_setup_entry (
2956 hass : HomeAssistant ,
3057 entry : PlugwiseConfigEntry ,
@@ -56,14 +83,26 @@ def _add_entities() -> None:
5683 entry .async_on_unload (coordinator .async_add_listener (_add_entities ))
5784
5885
59- class PlugwiseClimateEntity (PlugwiseEntity , ClimateEntity ):
86+ class PlugwiseClimateEntity (PlugwiseEntity , ClimateEntity , RestoreEntity ):
6087 """Representation of a Plugwise thermostat."""
6188
6289 _attr_name = None
6390 _attr_temperature_unit = UnitOfTemperature .CELSIUS
6491 _attr_translation_key = DOMAIN
6592
66- _previous_mode : str = "heating"
93+ _last_active_schedule : str | None = None
94+ _previous_action_mode : str | None = HVACAction .HEATING .value
95+
96+ async def async_added_to_hass (self ) -> None :
97+ """Run when entity about to be added."""
98+ await super ().async_added_to_hass ()
99+
100+ if extra_data := await self .async_get_last_extra_data ():
101+ plugwise_extra_data = PlugwiseClimateExtraStoredData .from_dict (
102+ extra_data .as_dict ()
103+ )
104+ self ._last_active_schedule = plugwise_extra_data .last_active_schedule
105+ self ._previous_action_mode = plugwise_extra_data .previous_action_mode
67106
68107 def __init__ (
69108 self ,
@@ -76,7 +115,6 @@ def __init__(
76115
77116 gateway_id : str = coordinator .api .gateway_id
78117 self ._gateway_data = coordinator .data [gateway_id ]
79-
80118 self ._location = device_id
81119 if (location := self .device .get ("location" )) is not None :
82120 self ._location = location
@@ -105,25 +143,19 @@ def __init__(
105143 self .device ["thermostat" ]["resolution" ], 0.1
106144 )
107145
108- def _previous_action_mode (self , coordinator : PlugwiseDataUpdateCoordinator ) -> None :
109- """Return the previous action-mode when the regulation-mode is not heating or cooling.
110-
111- Helper for set_hvac_mode().
112- """
113- # When no cooling available, _previous_mode is always heating
114- if (
115- "regulation_modes" in self ._gateway_data
116- and "cooling" in self ._gateway_data ["regulation_modes" ]
117- ):
118- mode = self ._gateway_data ["select_regulation_mode" ]
119- if mode in ("cooling" , "heating" ):
120- self ._previous_mode = mode
121-
122146 @property
123147 def current_temperature (self ) -> float :
124148 """Return the current temperature."""
125149 return self .device ["sensors" ]["temperature" ]
126150
151+ @property
152+ def extra_restore_state_data (self ) -> PlugwiseClimateExtraStoredData :
153+ """Return text specific state data to be restored."""
154+ return PlugwiseClimateExtraStoredData (
155+ last_active_schedule = self ._last_active_schedule ,
156+ previous_action_mode = self ._previous_action_mode ,
157+ )
158+
127159 @property
128160 def target_temperature (self ) -> float :
129161 """Return the temperature we try to reach.
@@ -170,9 +202,10 @@ def hvac_modes(self) -> list[HVACMode]:
170202
171203 if self .coordinator .api .cooling_present :
172204 if "regulation_modes" in self ._gateway_data :
173- if self ._gateway_data ["select_regulation_mode" ] == "cooling" :
205+ selected = self ._gateway_data .get ("select_regulation_mode" )
206+ if selected == HVACAction .COOLING .value :
174207 hvac_modes .append (HVACMode .COOL )
175- if self . _gateway_data [ "select_regulation_mode" ] == "heating" :
208+ if selected == HVACAction . HEATING . value :
176209 hvac_modes .append (HVACMode .HEAT )
177210 else :
178211 hvac_modes .append (HVACMode .HEAT_COOL )
@@ -184,8 +217,16 @@ def hvac_modes(self) -> list[HVACMode]:
184217 @property
185218 def hvac_action (self ) -> HVACAction :
186219 """Return the current running hvac operation if supported."""
187- # Keep track of the previous action-mode
188- self ._previous_action_mode (self .coordinator )
220+ # Keep track of the previous hvac_action mode.
221+ # When no cooling available, _previous_action_mode is always heating
222+ if (
223+ "regulation_modes" in self ._gateway_data
224+ and HVACAction .COOLING .value in self ._gateway_data ["regulation_modes" ]
225+ ):
226+ mode = self ._gateway_data ["select_regulation_mode" ]
227+ if mode in (HVACAction .COOLING .value , HVACAction .HEATING .value ):
228+ self ._previous_action_mode = mode
229+
189230 if (action := self .device .get ("control_state" )) is not None :
190231 return HVACAction (action )
191232
@@ -219,14 +260,33 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
219260 return
220261
221262 if hvac_mode == HVACMode .OFF :
222- await self .coordinator .api .set_regulation_mode (hvac_mode )
263+ await self .coordinator .api .set_regulation_mode (hvac_mode . value )
223264 else :
265+ current = self .device .get ("select_schedule" )
266+ desired = current
267+
268+ # Capture the last valid schedule
269+ if desired and desired != "off" :
270+ self ._last_active_schedule = desired
271+ elif desired == "off" :
272+ desired = self ._last_active_schedule
273+
274+ # Enabling HVACMode.AUTO requires a previously set schedule for saving and restoring
275+ if hvac_mode == HVACMode .AUTO and not desired :
276+ raise HomeAssistantError (
277+ translation_domain = DOMAIN ,
278+ translation_key = ERROR_NO_SCHEDULE ,
279+ )
280+
224281 await self .coordinator .api .set_schedule_state (
225282 self ._location ,
226- "on" if hvac_mode == HVACMode .AUTO else "off" ,
283+ STATE_ON if hvac_mode == HVACMode .AUTO else STATE_OFF ,
284+ desired ,
227285 )
228- if self .hvac_mode == HVACMode .OFF :
229- await self .coordinator .api .set_regulation_mode (self ._previous_mode )
286+ if self .hvac_mode == HVACMode .OFF and self ._previous_action_mode :
287+ await self .coordinator .api .set_regulation_mode (
288+ self ._previous_action_mode
289+ )
230290
231291 @plugwise_command
232292 async def async_set_preset_mode (self , preset_mode : str ) -> None :
0 commit comments