Skip to content

Commit 004f191

Browse files
authored
Merge pull request #945 from plugwise/save_last_option
Store the last_used_schedule for reuse
2 parents 3012703 + 25347ea commit 004f191

File tree

6 files changed

+199
-41
lines changed

6 files changed

+199
-41
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
Versions from 0.40 and up
44

5-
## Ongoing
5+
## v0.59.0
66

7+
- New Feature: use RestoreState in climate to save schedule en regulation status, also via plugwise [v1.8.3](https://github.com/plugwise/python-plugwise/releases/tag/v1.8.3)
78
- More Emma-related updates via plugwise [v1.8.2](https://github.com/plugwise/python-plugwise/releases/tag/v1.8.2)
89

910
## v0.58.1

custom_components/plugwise/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -
146146
"""Migrate back to v1.1 config entry."""
147147
if entry.version > 1:
148148
# This means the user has downgraded from a future version
149-
return False
149+
return False #pragma: no cover
150150

151151
if entry.version == 1 and entry.minor_version == 2:
152152
new_data = {**entry.data}

custom_components/plugwise/climate.py

Lines changed: 88 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from dataclasses import dataclass
56
from typing import Any
67

78
from homeassistant.components.climate import (
@@ -23,7 +24,9 @@
2324
UnitOfTemperature,
2425
)
2526
from homeassistant.core import HomeAssistant, callback
27+
from homeassistant.exceptions import HomeAssistantError
2628
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
29+
from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity
2730

2831
from .const import (
2932
ACTIVE_PRESET,
@@ -51,6 +54,7 @@
5154
from .entity import PlugwiseEntity
5255
from .util import plugwise_command
5356

57+
ERROR_NO_SCHEDULE = "Failed setting HVACMode, set a schedule first"
5458
PARALLEL_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 (

custom_components/plugwise/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"integration_type": "hub",
88
"iot_class": "local_polling",
99
"loggers": ["plugwise"],
10-
"requirements": ["plugwise==1.8.2"],
11-
"version": "0.58.1",
10+
"requirements": ["plugwise==1.8.3"],
11+
"version": "0.59.0",
1212
"zeroconf": ["_plugwise._tcp.local."]
1313
}

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "plugwise-beta"
3-
version = "0.58.2"
3+
version = "0.59.0"
44
description = "Plugwise beta custom-component"
55
readme = "README.md"
66
requires-python = ">=3.13"

0 commit comments

Comments
 (0)