Skip to content

Commit 02802b3

Browse files
authored
Merge pull request #558 from plugwise/fix-legacy-schedule
Schedule-related improvements
2 parents f72847c + b31d57b commit 02802b3

File tree

15 files changed

+92
-93
lines changed

15 files changed

+92
-93
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Changelog
22

3-
## Ongoing
3+
## v0.37.6
44

5+
- Schedule-related improvements.
56
- Revert removal of set_temperature_offset() function.
67

78
## v0.37.5

fixtures/legacy_anna/all_data.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@
3636
},
3737
"0d266432d64443e283b5d708ae98b455": {
3838
"active_preset": "home",
39-
"available_schedules": ["Thermostat schedule"],
39+
"available_schedules": ["None"],
4040
"dev_class": "thermostat",
4141
"firmware": "2017-03-13T11:54:58+01:00",
4242
"hardware": "6539-1301-500",
4343
"location": "0000aaaa0000aaaa0000aaaa0000aa00",
44-
"mode": "auto",
44+
"mode": "heat",
4545
"model": "ThermoTouch",
4646
"name": "Anna",
4747
"preset_modes": ["away", "vacation", "asleep", "home", "no_frost"],
48-
"select_schedule": "Thermostat schedule",
48+
"select_schedule": "None",
4949
"sensors": {
5050
"illuminance": 151,
5151
"setpoint": 20.5,

fixtures/legacy_anna_2/all_data.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"devices": {
33
"9e7377867dc24e51b8098a5ba02bd89d": {
44
"active_preset": null,
5-
"available_schedules": ["Thermostat schedule"],
5+
"available_schedules": ["Thermostat schedule", "off"],
66
"dev_class": "thermostat",
77
"firmware": "2017-03-13T11:54:58+01:00",
88
"hardware": "6539-1301-5002",
@@ -11,7 +11,7 @@
1111
"model": "ThermoTouch",
1212
"name": "Anna",
1313
"preset_modes": ["vacation", "away", "no_frost", "home", "asleep"],
14-
"select_schedule": "None",
14+
"select_schedule": "off",
1515
"sensors": {
1616
"illuminance": 19.5,
1717
"setpoint": 15.0,

plugwise/constants.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,13 @@
207207
"stretch_v2": SMILE(STRETCH, "Stretch"),
208208
"stretch_v3": SMILE(STRETCH, "Stretch"),
209209
}
210-
REQUIRE_APPLIANCES: Final[list[str]] = [
210+
REQUIRE_APPLIANCES: Final[tuple[str, ...]] = (
211211
"smile_thermo_v1",
212212
"smile_thermo_v3",
213213
"smile_thermo_v4",
214214
"stretch_v2",
215215
"stretch_v3",
216-
]
216+
)
217217

218218
# Class, Literal and related tuple-definitions
219219

plugwise/data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ def _device_data_climate(self, device: DeviceData, data: DeviceData) -> None:
204204
# Operation modes: auto, heat, heat_cool, cool and off
205205
data["mode"] = "auto"
206206
self._count += 1
207-
if sel_schedule == NONE:
207+
if sel_schedule in (NONE, OFF):
208208
data["mode"] = "heat"
209209
if self._cooling_present:
210210
data["mode"] = "cool" if self.check_reg_mode("cooling") else "heat_cool"

plugwise/helper.py

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
ADAM,
1616
ANNA,
1717
ATTR_NAME,
18-
ATTR_UNIT_OF_MEASUREMENT,
19-
BINARY_SENSORS,
2018
DATA,
2119
DEVICE_MEASUREMENTS,
2220
DHW_SETPOINT,
@@ -29,22 +27,16 @@
2927
NONE,
3028
OFF,
3129
P1_MEASUREMENTS,
32-
SENSORS,
33-
SPECIALS,
34-
SWITCHES,
3530
TEMP_CELSIUS,
3631
THERMOSTAT_CLASSES,
3732
TOGGLES,
3833
UOM,
3934
ActuatorData,
4035
ActuatorDataType,
4136
ActuatorType,
42-
BinarySensorType,
4337
DeviceData,
4438
GatewayData,
4539
SensorType,
46-
SpecialType,
47-
SwitchType,
4840
ThermoLoc,
4941
ToggleNameType,
5042
)
@@ -56,6 +48,7 @@
5648
)
5749
from plugwise.util import (
5850
check_model,
51+
common_match_cases,
5952
escape_illegal_xml_characters,
6053
format_measure,
6154
skip_obsolete_measurements,
@@ -214,7 +207,7 @@ def __init__(self) -> None:
214207
self._thermo_locs: dict[str, ThermoLoc] = {}
215208
###################################################################
216209
# '_cooling_enabled' can refer to the state of the Elga heatpump
217-
# connected to an Anna. For Elga, 'elga_status_code' in [8, 9]
210+
# connected to an Anna. For Elga, 'elga_status_code' in (8, 9)
218211
# means cooling mode is available, next to heating mode.
219212
# 'elga_status_code' = 8 means cooling is active, 9 means idle.
220213
#
@@ -523,7 +516,7 @@ def _get_measurement_data(self, dev_id: str) -> DeviceData:
523516
# Techneco Elga has cooling-capability
524517
self._cooling_present = True
525518
data["model"] = "Generic heater/cooler"
526-
self._cooling_enabled = data["elga_status_code"] in [8, 9]
519+
self._cooling_enabled = data["elga_status_code"] in (8, 9)
527520
data["binary_sensors"]["cooling_state"] = self._cooling_active = (
528521
data["elga_status_code"] == 8
529522
)
@@ -585,29 +578,12 @@ def _appliance_measurements(
585578
measurement = new_name
586579

587580
match measurement:
588-
# measurements with states "on" or "off" that need to be passed directly
589-
case "select_dhw_mode":
590-
data["select_dhw_mode"] = appl_p_loc.text
591-
case _ as measurement if measurement in BINARY_SENSORS:
592-
bs_key = cast(BinarySensorType, measurement)
593-
bs_value = appl_p_loc.text in ["on", "true"]
594-
data["binary_sensors"][bs_key] = bs_value
595-
case _ as measurement if measurement in SENSORS:
596-
s_key = cast(SensorType, measurement)
597-
s_value = format_measure(
598-
appl_p_loc.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT)
599-
)
600-
data["sensors"][s_key] = s_value
601-
case _ as measurement if measurement in SWITCHES:
602-
sw_key = cast(SwitchType, measurement)
603-
sw_value = appl_p_loc.text in ["on", "true"]
604-
data["switches"][sw_key] = sw_value
605-
case _ as measurement if measurement in SPECIALS:
606-
sp_key = cast(SpecialType, measurement)
607-
sp_value = appl_p_loc.text in ["on", "true"]
608-
data[sp_key] = sp_value
609581
case "elga_status_code":
610582
data["elga_status_code"] = int(appl_p_loc.text)
583+
case "select_dhw_mode":
584+
data["select_dhw_mode"] = appl_p_loc.text
585+
586+
common_match_cases(measurement, attrs, appl_p_loc, data)
611587

612588
i_locator = f'.//logs/interval_log[type="{measurement}"]/period/measurement'
613589
if (appl_i_loc := appliance.find(i_locator)) is not None:
@@ -1014,6 +990,8 @@ def _schedules(self, location: str) -> tuple[list[str], str]:
1014990
if schedules:
1015991
available.remove(NONE)
1016992
available.append(OFF)
993+
if selected == NONE:
994+
selected = OFF
1017995
if self._last_active.get(location) is None:
1018996
self._last_active[location] = self._last_used_schedule(schedules)
1019997

plugwise/legacy/data.py

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

77
# Dict as class
88
# Version detection
9-
from plugwise.constants import NONE, DeviceData
9+
from plugwise.constants import NONE, OFF, DeviceData
1010
from plugwise.legacy.helper import SmileLegacyHelper
1111
from plugwise.util import remove_empty_platform_dicts
1212

@@ -88,5 +88,5 @@ def _device_data_climate(self, device: DeviceData, data: DeviceData) -> None:
8888
# Operation modes: auto, heat
8989
data["mode"] = "auto"
9090
self._count += 1
91-
if sel_schedule == NONE:
91+
if sel_schedule in (NONE, OFF):
9292
data["mode"] = "heat"

plugwise/legacy/helper.py

Lines changed: 18 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
ACTUATOR_CLASSES,
1313
APPLIANCES,
1414
ATTR_NAME,
15-
ATTR_UNIT_OF_MEASUREMENT,
16-
BINARY_SENSORS,
1715
DATA,
1816
DEVICE_MEASUREMENTS,
1917
ENERGY_WATT_HOUR,
@@ -22,26 +20,26 @@
2220
HEATER_CENTRAL_MEASUREMENTS,
2321
LIMITS,
2422
NONE,
23+
OFF,
2524
P1_LEGACY_MEASUREMENTS,
26-
SENSORS,
27-
SPECIALS,
28-
SWITCHES,
2925
TEMP_CELSIUS,
3026
THERMOSTAT_CLASSES,
3127
UOM,
3228
ActuatorData,
3329
ActuatorDataType,
3430
ActuatorType,
3531
ApplianceType,
36-
BinarySensorType,
3732
DeviceData,
3833
GatewayData,
3934
SensorType,
40-
SpecialType,
41-
SwitchType,
4235
ThermoLoc,
4336
)
44-
from plugwise.util import format_measure, skip_obsolete_measurements, version_to_model
37+
from plugwise.util import (
38+
common_match_cases,
39+
format_measure,
40+
skip_obsolete_measurements,
41+
version_to_model,
42+
)
4543

4644
# This way of importing aiohttp is because of patch/mocking in testing (aiohttp timeouts)
4745
from defusedxml import ElementTree as etree
@@ -340,25 +338,7 @@ def _appliance_measurements(
340338
if new_name := getattr(attrs, ATTR_NAME, None):
341339
measurement = new_name
342340

343-
match measurement:
344-
case _ as measurement if measurement in BINARY_SENSORS:
345-
bs_key = cast(BinarySensorType, measurement)
346-
bs_value = appl_p_loc.text in ["on", "true"]
347-
data["binary_sensors"][bs_key] = bs_value
348-
case _ as measurement if measurement in SENSORS:
349-
s_key = cast(SensorType, measurement)
350-
s_value = format_measure(
351-
appl_p_loc.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT)
352-
)
353-
data["sensors"][s_key] = s_value
354-
case _ as measurement if measurement in SWITCHES:
355-
sw_key = cast(SwitchType, measurement)
356-
sw_value = appl_p_loc.text in ["on", "true"]
357-
data["switches"][sw_key] = sw_value
358-
case _ as measurement if measurement in SPECIALS:
359-
sp_key = cast(SpecialType, measurement)
360-
sp_value = appl_p_loc.text in ["on", "true"]
361-
data[sp_key] = sp_value
341+
common_match_cases(measurement, attrs, appl_p_loc, data)
362342

363343
i_locator = f'.//logs/interval_log[type="{measurement}"]/period/measurement'
364344
if (appl_i_loc := appliance.find(i_locator)) is not None:
@@ -450,33 +430,32 @@ def _presets(self) -> dict[str, list[float]]:
450430
return presets
451431

452432
def _schedules(self) -> tuple[list[str], str]:
453-
"""Collect available schedules/schedules for the legacy thermostat."""
433+
"""Collect the schedule for the legacy thermostat."""
454434
available: list[str] = [NONE]
455-
selected = NONE
435+
rule_id = selected = NONE
456436
name: str | None = None
457437

458438
search = self._domain_objects
459-
for schedule in search.findall("./rule"):
460-
if rule_name := schedule.find("name").text:
461-
if "preset" not in rule_name:
462-
name = rule_name
439+
if (result := search.find("./rule[name='Thermostat schedule']")) is not None:
440+
name = "Thermostat schedule"
441+
rule_id = result.attrib["id"]
463442

464443
log_type = "schedule_state"
465444
locator = f"./appliance[type='thermostat']/logs/point_log[type='{log_type}']/period/measurement"
466445
active = False
467446
if (result := search.find(locator)) is not None:
468447
active = result.text == "on"
469448

470-
if name is not None:
471-
available = [name]
472-
if active:
473-
selected = name
449+
# Show an empty schedule as no schedule found
450+
directives = search.find(f'./rule[@id="{rule_id}"]/directives/when/then') is not None
451+
if directives and name is not None:
452+
available = [name, OFF]
453+
selected = name if active else OFF
474454

475455
return available, selected
476456

477457
def _thermostat_uri(self) -> str:
478458
"""Determine the location-set_temperature uri - from APPLIANCES."""
479459
locator = "./appliance[type='thermostat']"
480460
appliance_id = self._appliances.find(locator).attrib["id"]
481-
482461
return f"{APPLIANCES};id={appliance_id}/thermostat"

plugwise/legacy/smile.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
LOCATIONS,
1616
LOGGER,
1717
MODULES,
18+
OFF,
1819
REQUIRE_APPLIANCES,
1920
RULES,
2021
DeviceData,
@@ -181,16 +182,19 @@ async def set_preset(self, _: str, preset: str) -> None:
181182
async def set_regulation_mode(self, mode: str) -> None:
182183
"""Set-function placeholder for legacy devices."""
183184

184-
async def set_schedule_state(self, _: str, state: str, __: str | None) -> None:
185+
async def set_schedule_state(self, _: str, state: str, name: str | None) -> None:
185186
"""Activate/deactivate the Schedule.
186187
187188
Determined from - DOMAIN_OBJECTS.
188189
Used in HA Core to set the hvac_mode: in practice switch between schedule on - off.
189190
"""
190-
if state not in ["on", "off"]:
191+
if state not in ("on", "off"):
191192
raise PlugwiseError("Plugwise: invalid schedule state.")
192193

193-
name = "Thermostat schedule"
194+
# Handle no schedule-name / Off-schedule provided
195+
if name is None or name == OFF:
196+
name = "Thermostat schedule"
197+
194198
schedule_rule_id: str | None = None
195199
for rule in self._domain_objects.findall("rule"):
196200
if rule.find("name").text == name:

plugwise/smile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ async def set_schedule_state(
266266
Used in HA Core to set the hvac_mode: in practice switch between schedule on - off.
267267
"""
268268
# Input checking
269-
if new_state not in ["on", "off"]:
269+
if new_state not in ("on", "off"):
270270
raise PlugwiseError("Plugwise: invalid schedule state.")
271271

272272
# Translate selection of Off-schedule-option to disabling the active schedule

0 commit comments

Comments
 (0)