Skip to content

Commit 3b93002

Browse files
authored
Merge pull request #216 from plugwise/Fix_schedule_old_states
Working fix for #213
2 parents 8037fbf + cffa850 commit 3b93002

File tree

6 files changed

+75
-34
lines changed

6 files changed

+75
-34
lines changed

.github/workflows/verify.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
name: Latest commit
55

66
env:
7-
CACHE_VERSION: 3
7+
CACHE_VERSION: 4
88
DEFAULT_PYTHON: "3.9"
99
PRE_COMMIT_HOME: ~/.cache/pre-commit
1010

CHANGELOG.md

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

3+
# v0.22.1: Improve solution for issue #213
4+
35
# v0.22.0: Smile P1 - add a P1 smartmeter device
46
- Change all gateway model names to Gateway
57
- Change Anna Smile name to Smile Anna, Anna model name to ThermoTouch

plugwise/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Plugwise module."""
22

3-
__version__ = "0.22.0"
3+
__version__ = "0.22.1"
44

55
from plugwise.smile import Smile
66
from plugwise.stick import Stick

plugwise/helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ def __init__(self) -> None:
329329
self._on_off_device = False
330330
self._opentherm_device = False
331331
self._outdoor_temp: float
332-
self._schedule_present_state: str
332+
self._schedule_old_states: dict[str, dict[str, str]] = {}
333333
self._sched_setpoints: list[float] | None = None
334334
self._smile_legacy = False
335335
self._stretch_v2 = False

plugwise/smile.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,14 @@ def _device_data_climate(
216216
if self._adam_cooling_enabled or self._lortherm_cooling_enabled:
217217
device_data["mode"] = "cool"
218218

219-
self._schedule_present_state = "off"
220-
if device_data["mode"] == "auto":
221-
self._schedule_present_state = "on"
219+
if "None" not in avail_schedules:
220+
loc_schedule_states = {}
221+
for schedule in avail_schedules:
222+
loc_schedule_states[schedule] = "off"
223+
if device_data["mode"] == "auto":
224+
loc_schedule_states[sel_schedule] = "on"
225+
226+
self._schedule_old_states[loc_id] = loc_schedule_states
222227

223228
return device_data
224229

@@ -508,7 +513,9 @@ async def async_update(self) -> list[GatewayData | dict[str, DeviceData]]:
508513

509514
return [self.gw_data, self.gw_devices]
510515

511-
async def _set_schedule_state_legacy(self, name: str, status: str) -> None:
516+
async def _set_schedule_state_legacy(
517+
self, loc_id: str, name: str, status: str
518+
) -> None:
512519
"""Helper-function for set_schedule_state()."""
513520
schedule_rule_id: str | None = None
514521
for rule in self._domain_objects.findall("rule"):
@@ -518,9 +525,13 @@ async def _set_schedule_state_legacy(self, name: str, status: str) -> None:
518525
if schedule_rule_id is None:
519526
raise PlugwiseError("Plugwise: no schedule with this name available.")
520527

521-
state = "false"
528+
new_state = "false"
522529
if status == "on":
523-
state = "true"
530+
new_state = "true"
531+
# If no state change is requested, do nothing
532+
if new_state == self._schedule_old_states[loc_id][name]:
533+
return
534+
524535
locator = f'.//*[@id="{schedule_rule_id}"]/template'
525536
for rule in self._domain_objects.findall(locator):
526537
template_id = rule.attrib["id"]
@@ -529,42 +540,38 @@ async def _set_schedule_state_legacy(self, name: str, status: str) -> None:
529540
data = (
530541
"<rules><rule"
531542
f' id="{schedule_rule_id}"><name><![CDATA[{name}]]></name><template'
532-
f' id="{template_id}" /><active>{state}</active></rule></rules>'
543+
f' id="{template_id}" /><active>{new_state}</active></rule></rules>'
533544
)
534545

535546
await self._request(uri, method="put", data=data)
547+
self._schedule_old_states[loc_id][name] = new_state
536548

537549
async def set_schedule_state(
538-
self, loc_id: str, name: str | None, state: str
550+
self, loc_id: str, name: str | None, new_state: str
539551
) -> None:
540552
"""Activate/deactivate the Schedule, with the given name, on the relevant Thermostat.
541553
Determined from - DOMAIN_OBJECTS.
542554
In HA Core used to set the hvac_mode: in practice switch between schedule on - off.
543555
"""
544-
if state not in ["on", "off"]:
556+
# Input checking
557+
if new_state not in ["on", "off"]:
545558
raise PlugwiseError("Plugwise: invalid schedule state.")
546-
547-
# Do nothing when name == None and the state does not change. No need to show
548-
# an error, as doing nothing is the correct action in this scenario.
549559
if name is None:
550-
if state == self._schedule_present_state:
551-
return
552-
# else, raise an error:
553560
raise PlugwiseError(
554561
"Plugwise: cannot change schedule-state: no schedule name provided"
555562
)
556563

557564
if self._smile_legacy:
558-
await self._set_schedule_state_legacy(name, state)
565+
await self._set_schedule_state_legacy(loc_id, name, new_state)
559566
return
560567

561568
schedule_rule = self._rule_ids_by_name(name, loc_id)
562569
# Raise an error when the schedule name does not exist
563570
if not schedule_rule or schedule_rule is None:
564571
raise PlugwiseError("Plugwise: no schedule with this name available.")
565572

566-
# If schedule name is valid but no state change is requested, do nothing
567-
if state == self._schedule_present_state:
573+
# If no state change is requested, do nothing
574+
if new_state == self._schedule_old_states[loc_id][name]:
568575
return
569576

570577
schedule_rule_id: str = next(iter(schedule_rule))
@@ -584,10 +591,10 @@ async def set_schedule_state(
584591
subject = f'<context><zone><location id="{loc_id}" /></zone></context>'
585592
subject = etree.fromstring(subject)
586593

587-
if state == "off":
594+
if new_state == "off":
588595
self._last_active[loc_id] = name
589596
contexts.remove(subject)
590-
if state == "on":
597+
if new_state == "on":
591598
contexts.append(subject)
592599

593600
contexts = etree.tostring(contexts, encoding="unicode").rstrip()
@@ -598,8 +605,7 @@ async def set_schedule_state(
598605
f"{template}{contexts}</rule></rules>"
599606
)
600607
await self._request(uri, method="put", data=data)
601-
602-
self._schedule_present_state = state
608+
self._schedule_old_states[loc_id][name] = new_state
603609

604610
async def _set_preset_legacy(self, preset: str) -> None:
605611
"""Set the given Preset on the relevant Thermostat - from DOMAIN_OBJECTS."""

tests/test_smile.py

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,9 @@ async def tinker_thermostat(
584584

585585
result_1 = await self.tinker_thermostat_temp(smile, loc_id, unhappy)
586586
result_2 = await self.tinker_thermostat_preset(smile, loc_id, unhappy)
587-
smile._schedule_present_state = "off"
587+
if smile._schedule_old_states != {}:
588+
for item in smile._schedule_old_states[loc_id]:
589+
smile._schedule_old_states[loc_id][item] = "off"
588590
result_3 = await self.tinker_thermostat_schedule(
589591
smile, loc_id, "on", good_schedules, single, unhappy
590592
)
@@ -702,7 +704,7 @@ async def test_connect_legacy_anna(self):
702704

703705
result = await self.tinker_thermostat(
704706
smile,
705-
"c34c6864216446528e95d88985e714cc",
707+
"0000aaaa0000aaaa0000aaaa0000aa00",
706708
good_schedules=[
707709
"Thermostat schedule",
708710
],
@@ -712,9 +714,10 @@ async def test_connect_legacy_anna(self):
712714
await self.disconnect(server, client)
713715

714716
server, smile, client = await self.connect_wrapper(raise_timeout=True)
717+
await self.device_test(smile, testdata)
715718
result = await self.tinker_thermostat(
716719
smile,
717-
"c34c6864216446528e95d88985e714cc",
720+
"0000aaaa0000aaaa0000aaaa0000aa00",
718721
good_schedules=[
719722
"Thermostat schedule",
720723
],
@@ -797,24 +800,45 @@ async def test_connect_legacy_anna_2(self):
797800
await self.device_test(smile, testdata)
798801

799802
assert smile.gateway_id == "be81e3f8275b4129852c4d8d550ae2eb"
800-
assert self.device_items == 43
803+
# assert self.device_items = 47
801804
assert not self.notifications
802805

803806
result = await self.tinker_thermostat(
804807
smile,
805-
"c34c6864216446528e95d88985e714cc",
808+
"be81e3f8275b4129852c4d8d550ae2eb",
806809
good_schedules=[
807810
"Thermostat schedule",
808811
],
809812
)
810813
assert result
814+
815+
smile._schedule_old_states["be81e3f8275b4129852c4d8d550ae2eb"][
816+
"Thermostat schedule"
817+
] = "off"
818+
result_1 = await self.tinker_thermostat_schedule(
819+
smile,
820+
"be81e3f8275b4129852c4d8d550ae2eb",
821+
"on",
822+
good_schedules=["Thermostat schedule"],
823+
single=True,
824+
)
825+
result_2 = await self.tinker_thermostat_schedule(
826+
smile,
827+
"be81e3f8275b4129852c4d8d550ae2eb",
828+
"on",
829+
good_schedules=["Thermostat schedule"],
830+
single=True,
831+
)
832+
assert result_1 and result_2
833+
811834
await smile.close_connection()
812835
await self.disconnect(server, client)
813836

814837
server, smile, client = await self.connect_wrapper(raise_timeout=True)
838+
await self.device_test(smile, testdata)
815839
result = await self.tinker_thermostat(
816840
smile,
817-
"c34c6864216446528e95d88985e714cc",
841+
"be81e3f8275b4129852c4d8d550ae2eb",
818842
good_schedules=[
819843
"Thermostat schedule",
820844
],
@@ -882,8 +906,6 @@ async def test_connect_smile_p1_v2(self):
882906
await smile.close_connection()
883907
await self.disconnect(server, client)
884908

885-
server, smile, client = await self.connect_wrapper(raise_timeout=True)
886-
887909
@pytest.mark.asyncio
888910
async def test_connect_smile_p1_v2_2(self):
889911
"""Test another legacy P1 device."""
@@ -1045,6 +1067,7 @@ async def test_connect_anna_v4(self):
10451067
await self.disconnect(server, client)
10461068

10471069
server, smile, client = await self.connect_wrapper(raise_timeout=True)
1070+
await self.device_test(smile, testdata)
10481071
result = await self.tinker_thermostat(
10491072
smile,
10501073
"eb5309212bf5407bb143e5bfa3b18aee",
@@ -1155,6 +1178,7 @@ async def test_connect_anna_v4_dhw(self):
11551178
await self.disconnect(server, client)
11561179

11571180
server, smile, client = await self.connect_wrapper(raise_timeout=True)
1181+
await self.device_test(smile, testdata)
11581182
result = await self.tinker_thermostat(
11591183
smile,
11601184
"eb5309212bf5407bb143e5bfa3b18aee",
@@ -1201,6 +1225,7 @@ async def test_connect_anna_v4_no_tag(self):
12011225
await self.disconnect(server, client)
12021226

12031227
server, smile, client = await self.connect_wrapper(raise_timeout=True)
1228+
await self.device_test(smile, testdata)
12041229
result = await self.tinker_thermostat(
12051230
smile,
12061231
"eb5309212bf5407bb143e5bfa3b18aee",
@@ -1284,6 +1309,7 @@ async def test_connect_anna_without_boiler_fw3(self):
12841309
await self.disconnect(server, client)
12851310

12861311
server, smile, client = await self.connect_wrapper(raise_timeout=True)
1312+
await self.device_test(smile, testdata)
12871313
result = await self.tinker_thermostat(
12881314
smile,
12891315
"c34c6864216446528e95d88985e714cc",
@@ -1365,6 +1391,7 @@ async def test_connect_anna_without_boiler_fw4(self):
13651391
await self.disconnect(server, client)
13661392

13671393
server, smile, client = await self.connect_wrapper(raise_timeout=True)
1394+
await self.device_test(smile, testdata)
13681395
result = await self.tinker_thermostat(
13691396
smile,
13701397
"c34c6864216446528e95d88985e714cc",
@@ -1446,6 +1473,7 @@ async def test_connect_anna_without_boiler_fw42(self):
14461473
await self.disconnect(server, client)
14471474

14481475
server, smile, client = await self.connect_wrapper(raise_timeout=True)
1476+
await self.device_test(smile, testdata)
14491477
result = await self.tinker_thermostat(
14501478
smile,
14511479
"c34c6864216446528e95d88985e714cc",
@@ -1580,6 +1608,7 @@ async def test_connect_adam_plus_anna(self):
15801608
await self.disconnect(server, client)
15811609

15821610
server, smile, client = await self.connect_wrapper(raise_timeout=True)
1611+
await self.device_test(smile, testdata)
15831612
result = await self.tinker_thermostat(
15841613
smile,
15851614
"009490cc2f674ce6b576863fbb64f867",
@@ -1827,7 +1856,9 @@ async def test_connect_adam_plus_anna_new(self):
18271856
)
18281857
assert result
18291858

1830-
smile._schedule_present_state = "off"
1859+
smile._schedule_old_states["f2bf9048bef64cc5b6d5110154e33c81"][
1860+
"Badkamer"
1861+
] = "off"
18311862
result_1 = await self.tinker_thermostat_schedule(
18321863
smile,
18331864
"f2bf9048bef64cc5b6d5110154e33c81",
@@ -2264,6 +2295,7 @@ async def test_connect_adam_zone_per_device(self):
22642295
await self.disconnect(server, client)
22652296

22662297
server, smile, client = await self.connect_wrapper(raise_timeout=True)
2298+
await self.device_test(smile, testdata)
22672299

22682300
result = await self.tinker_thermostat(
22692301
smile,
@@ -2681,6 +2713,7 @@ async def test_connect_adam_multiple_devices_per_zone(self):
26812713
await self.disconnect(server, client)
26822714

26832715
server, smile, client = await self.connect_wrapper(raise_timeout=True)
2716+
await self.device_test(smile, testdata)
26842717
result = await self.tinker_thermostat(
26852718
smile,
26862719
"c50f167537524366a5af7aa3942feb1e",

0 commit comments

Comments
 (0)