From 16c63741ba5b62cf0aec57ffc705cf29ecdcc145 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 22 Oct 2025 11:23:47 +0200 Subject: [PATCH 01/10] Remove use of last_active This functionality is moved to the HA integration (restore_state) --- plugwise/__init__.py | 2 -- plugwise/helper.py | 28 ++-------------------------- plugwise/smile.py | 15 ++------------- tests/test_adam.py | 29 ----------------------------- tests/test_anna.py | 18 ------------------ 5 files changed, 4 insertions(+), 88 deletions(-) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index 47abbd837..2b89b065f 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -67,7 +67,6 @@ def __init__( self._cooling_present = False self._elga = False self._is_thermostat = False - self._last_active: dict[str, str | None] = {} self._loc_data: dict[str, ThermoLoc] = {} self._on_off_device = False self._opentherm_device = False @@ -156,7 +155,6 @@ async def connect(self) -> Version: self._cooling_present, self._elga, self._is_thermostat, - self._last_active, self._loc_data, self._on_off_device, self._opentherm_device, diff --git a/plugwise/helper.py b/plugwise/helper.py index 4ed5347d6..f761339de 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -78,7 +78,6 @@ def __init__(self) -> None: self._endpoint: str self._elga: bool self._is_thermostat: bool - self._last_active: dict[str, str | None] self._loc_data: dict[str, ThermoLoc] self._schedule_old_states: dict[str, dict[str, str]] self._gateway_id: str = NONE @@ -871,7 +870,7 @@ def _rule_ids_by_name(self, name: str, loc_id: str) -> dict[str, dict[str, str]] return schedule_ids def _rule_ids_by_tag(self, tag: str, loc_id: str) -> dict[str, dict[str, str]]: - """Helper-function for _presets(), _schedules() and _last_active_schedule(). + """Helper-function for _presets() and _schedules(). Obtain the rule_id from the given template_tag and provide the location_id, when present. """ @@ -906,11 +905,6 @@ def _schedules(self, location: str) -> tuple[list[str], str]: available: list[str] = [NONE] rule_ids: dict[str, dict[str, str]] = {} selected = NONE - # Adam schedules, one schedule can be linked to various locations - # self._last_active contains the locations and the active schedule name per location, or None - if location not in self._last_active: - self._last_active[location] = None - tag = "zone_preset_based_on_time_and_presence_with_override" if not (rule_ids := self._rule_ids_by_tag(tag, location)): return available, selected @@ -927,7 +921,6 @@ def _schedules(self, location: str) -> tuple[list[str], str]: available.append(name) if location == data["location"] and active: selected = name - self._last_active[location] = selected schedules.append(name) if schedules: @@ -935,26 +928,9 @@ def _schedules(self, location: str) -> tuple[list[str], str]: available.append(OFF) if selected == NONE: selected = OFF - if self._last_active.get(location) is None: - self._last_active[location] = self._last_used_schedule(schedules) - - return available, selected - def _last_used_schedule(self, schedules: list[str]) -> str: - """Helper-function for _schedules(). - Determine the last-used schedule based on the modified date. - """ - epoch = dt.datetime(1970, 1, 1, tzinfo=tz.tzutc()) - schedules_dates: dict[str, float] = {} - - for name in schedules: - result = self._domain_objects.find(f'./rule[name="{name}"]') - schedule_date = result.find("modified_date").text - schedule_time = parse(schedule_date) - schedules_dates[name] = (schedule_time - epoch).total_seconds() - - return sorted(schedules_dates.items(), key=lambda kv: kv[1])[-1][0] + return available, selected def _thermostat_uri(self, loc_id: str) -> str: """Helper-function for smile.py: set_temperature(). diff --git a/plugwise/smile.py b/plugwise/smile.py index 970181ca1..54bf154b5 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -68,7 +68,6 @@ def __init__( _cooling_present: bool, _elga: bool, _is_thermostat: bool, - _last_active: dict[str, str | None], _loc_data: dict[str, ThermoLoc], _on_off_device: bool, _opentherm_device: bool, @@ -81,7 +80,6 @@ def __init__( self._cooling_present = _cooling_present self._elga = _elga self._is_thermostat = _is_thermostat - self._last_active = _last_active self._loc_data = _loc_data self._on_off_device = _on_off_device self._opentherm_device = _opentherm_device @@ -309,8 +307,8 @@ async def set_regulation_mode(self, mode: str) -> None: async def set_schedule_state( self, loc_id: str, - new_state: str | None, - name: str | None, + new_state: str, + name: str, ) -> None: """Activate/deactivate the Schedule, with the given name, on the relevant Thermostat. @@ -325,14 +323,6 @@ async def set_schedule_state( if name == OFF: new_state = STATE_OFF - # Handle no schedule-name / Off-schedule provided - if name is None or name == OFF: - if schedule_name := self._last_active[loc_id]: - name = schedule_name - else: - return - - assert isinstance(name, str) schedule_rule = self._rule_ids_by_name(name, loc_id) # Raise an error when the schedule name does not exist if not schedule_rule or schedule_rule is None: @@ -377,7 +367,6 @@ def determine_contexts( subject = etree.fromstring(subject) if state == STATE_OFF: - self._last_active[loc_id] = name contexts.remove(subject) if state == STATE_ON: contexts.append(subject) diff --git a/tests/test_adam.py b/tests/test_adam.py index dac24e17f..df99647e4 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -34,10 +34,6 @@ async def test_connect_adam_plus_anna_new(self): await self.device_test(api, "2025-10-12 00:00:01", testdata) assert api.gateway_id == "da224107914542988a88561b4452b0f6" - assert api._last_active["f2bf9048bef64cc5b6d5110154e33c81"] == "Weekschema" - assert ( - api._last_active["f871b8c4d63549319221e294e4f88074"] == "Weekschema" - ) # Badkamer assert self.entity_items == 216 assert self.entity_list == [ "da224107914542988a88561b4452b0f6", @@ -216,11 +212,6 @@ async def test_connect_adam_zone_per_device(self): await self.device_test(api, "2022-05-16 00:00:01", testdata) assert api.gateway_id == "fe799307f1624099878210aa0b9f1475" - assert api._last_active["12493538af164a409c6a1c79e38afe1c"] == BADKAMER_SCHEMA - assert api._last_active["c50f167537524366a5af7aa3942feb1e"] == GF7_WOONKAMER - assert api._last_active["82fa13f017d240daa0d0ea1775420f24"] == CV_JESSIE - assert api._last_active["08963fec7c53423ca5680aa4cb502c63"] == BADKAMER_SCHEMA - assert api._last_active["446ac08dd04d4eff8ac57489757b7314"] == BADKAMER_SCHEMA assert self.entity_items == 379 assert "af82e4ccf9c548528166d38e560662a4" in self.notifications @@ -294,11 +285,6 @@ async def test_connect_adam_multiple_devices_per_zone(self): ) await self.device_test(api, "2022-05-16 00:00:01", testdata) - assert api._last_active["12493538af164a409c6a1c79e38afe1c"] == BADKAMER_SCHEMA - assert api._last_active["c50f167537524366a5af7aa3942feb1e"] == GF7_WOONKAMER - assert api._last_active["82fa13f017d240daa0d0ea1775420f24"] == CV_JESSIE - assert api._last_active["08963fec7c53423ca5680aa4cb502c63"] == BADKAMER_SCHEMA - assert api._last_active["446ac08dd04d4eff8ac57489757b7314"] == BADKAMER_SCHEMA assert self.entity_items == 385 assert "af82e4ccf9c548528166d38e560662a4" in self.notifications @@ -335,16 +321,6 @@ async def test_adam_heatpump_cooling(self): server, api, client = await self.connect_wrapper() await self.device_test(api, "2022-01-02 00:00:01", testdata) - assert api._last_active["b52908550469425b812c87f766fe5303"] == WERKDAG_SCHEMA - assert api._last_active["20e735858f8146cead98b873177a4f99"] == WERKDAG_SCHEMA - assert api._last_active["e39529c79ab54fda9bed26cfc0447546"] == WERKDAG_SCHEMA - assert api._last_active["9a27714b970547ee9a6bdadc2b815ad5"] == WERKDAG_SCHEMA - assert api._last_active["93ac3f7bf25342f58cbb77c4a99ac0b3"] == WERKDAG_SCHEMA - assert api._last_active["fa5fa6b34f6b40a0972988b20e888ed4"] == WERKDAG_SCHEMA - assert api._last_active["04b15f6e884448288f811d29fb7b1b30"] == WERKDAG_SCHEMA - assert api._last_active["a562019b0b1f47a4bde8ebe3dbe3e8a9"] == WERKDAG_SCHEMA - assert api._last_active["8cf650a4c10c44819e426bed406aec34"] == WERKDAG_SCHEMA - assert api._last_active["5cc21042f87f4b4c94ccb5537c47a53f"] == WERKDAG_SCHEMA assert self.entity_items == 518 assert self.cooling_present assert self._cooling_enabled @@ -392,7 +368,6 @@ async def test_connect_adam_plus_anna(self): await self.device_test(api, "2020-03-22 00:00:01", testdata) assert api.gateway_id == "b128b4bbbd1f47e9bf4d756e8fb5ee94" - assert api._last_active["009490cc2f674ce6b576863fbb64f867"] == "Weekschema" assert self.entity_items == 82 assert "6fb89e35caeb4b1cb275184895202d84" in self.notifications @@ -433,10 +408,6 @@ async def test_adam_plus_jip(self): await self.device_test(api, "2021-06-20 00:00:01", testdata) assert api.gateway_id == "b5c2386c6f6342669e50fe49dd05b188" - assert api._last_active["d58fec52899f4f1c92e4f8fad6d8c48c"] is None - assert api._last_active["06aecb3d00354375924f50c47af36bd2"] is None - assert api._last_active["d27aede973b54be484f6842d1b2802ad"] is None - assert api._last_active["13228dab8ce04617af318a2888b3c548"] is None assert self.entity_items == 261 # Negative test diff --git a/tests/test_anna.py b/tests/test_anna.py index f577a0bc0..a37ef4ad3 100644 --- a/tests/test_anna.py +++ b/tests/test_anna.py @@ -29,7 +29,6 @@ async def test_connect_anna_v4(self): await self.device_test(api, "2020-04-05 00:00:01", testdata) assert api.gateway_id == "0466eae8520144c78afb29628384edeb" - assert api._last_active["eb5309212bf5407bb143e5bfa3b18aee"] == "Standaard" assert self.entity_items == 60 assert not self.notifications @@ -102,7 +101,6 @@ async def test_connect_anna_v4_dhw(self): ) await self.device_test(api, "2020-04-05 00:00:01", testdata) - assert api._last_active["eb5309212bf5407bb143e5bfa3b18aee"] == "Standaard" assert self.entity_items == 60 assert not self.notifications @@ -160,7 +158,6 @@ async def test_connect_anna_without_boiler_fw441(self): ) await self.device_test(api, "2022-05-16 00:00:01", testdata) - assert api._last_active["c34c6864216446528e95d88985e714cc"] == "Normaal" assert self.entity_items == 41 assert not self.notifications @@ -189,7 +186,6 @@ async def test_connect_anna_heatpump_heating(self): await self.device_test(api, "2020-04-12 00:00:01", testdata) assert api.gateway_id == "015ae9ea3f964e668e490fa39da3870b" - assert api._last_active["c784ee9fdab44e1395b8dee7d7a497d5"] == "standaard" assert self.entity_items == 69 assert not self.notifications assert self.cooling_present @@ -245,7 +241,6 @@ async def test_connect_anna_heatpump_cooling(self): ) await self.device_test(api, "2020-04-19 00:00:01", testdata) - assert api._last_active["c784ee9fdab44e1395b8dee7d7a497d5"] == "standaard" assert self.entity_items == 66 assert self.cooling_present assert not self.notifications @@ -318,7 +313,6 @@ async def test_connect_anna_elga_no_cooling(self): await self.device_test(api, "2020-04-12 00:00:01", testdata) assert api.gateway_id == "015ae9ea3f964e668e490fa39da3870b" - assert api._last_active["c784ee9fdab44e1395b8dee7d7a497d5"] == "standaard" assert self.entity_items == 65 assert not self.notifications assert not self.cooling_present @@ -342,9 +336,6 @@ async def test_connect_anna_elga_2(self): ) await self.device_test(api, "2022-03-13 00:00:01", testdata) - assert ( - api._last_active["d3ce834534114348be628b61b26d9220"] == THERMOSTAT_SCHEDULE - ) assert self.entity_items == 61 assert api.gateway_id == "fb49af122f6e4b0f91267e1cf7666d6f" assert self.cooling_present @@ -364,10 +355,6 @@ async def test_connect_anna_elga_2_schedule_off(self): assert api.smile.hostname == "smile000000" await self.device_test(api, "2022-03-13 00:00:01", testdata) - assert ( - api._last_active["d3ce834534114348be628b61b26d9220"] == THERMOSTAT_SCHEDULE - ) - assert self.cooling_present assert not self._cooling_enabled assert self.entity_items == 65 @@ -394,9 +381,6 @@ async def test_connect_anna_elga_2_cooling(self): ) await self.device_test(api, "2022-03-10 00:00:01", testdata) - assert ( - api._last_active["d3ce834534114348be628b61b26d9220"] == THERMOSTAT_SCHEDULE - ) assert self.entity_items == 65 assert not self.notifications @@ -450,7 +434,6 @@ async def test_connect_anna_loria_heating_idle(self): ) await self.device_test(api, "2022-05-16 00:00:01", testdata) - assert api._last_active["15da035090b847e7a21f93e08c015ebc"] == "Winter" assert self.entity_items == 68 assert self.cooling_present assert not self._cooling_enabled @@ -516,7 +499,6 @@ async def test_connect_anna_loria_cooling_active(self): ) await self.device_test(api, "2022-05-16 00:00:01", testdata) - assert api._last_active["15da035090b847e7a21f93e08c015ebc"] == "Winter" assert self.entity_items == 68 assert self.cooling_present assert self._cooling_enabled From a4853af0b908a794460ef6507be206e3813b1204 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 22 Oct 2025 12:32:52 +0200 Subject: [PATCH 02/10] Clean up now unused imports --- plugwise/helper.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index f761339de..0d7355ee0 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -5,7 +5,6 @@ from __future__ import annotations -import datetime as dt from typing import cast from plugwise.common import SmileCommon @@ -50,9 +49,6 @@ skip_obsolete_measurements, ) -# Time related -from dateutil import tz -from dateutil.parser import parse from defusedxml import ElementTree as etree from munch import Munch from packaging import version From 08863b8fd27e41352d223f7c726fd013f03d8e4b Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 22 Oct 2025 12:34:46 +0200 Subject: [PATCH 03/10] Revert removals and adapt --- plugwise/smile.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index 54bf154b5..880e604d9 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -307,8 +307,8 @@ async def set_regulation_mode(self, mode: str) -> None: async def set_schedule_state( self, loc_id: str, - new_state: str, - name: str, + new_state: str | None, + name: str | None, ) -> None: """Activate/deactivate the Schedule, with the given name, on the relevant Thermostat. @@ -323,6 +323,12 @@ async def set_schedule_state( if name == OFF: new_state = STATE_OFF + # Handle no schedule-name / schedule-off requested: find the active schedule + if name is None or name == OFF: + _, name = self._schedules(loc_id) + if name == OFF: # no active schedule found, nothing to do + return + schedule_rule = self._rule_ids_by_name(name, loc_id) # Raise an error when the schedule name does not exist if not schedule_rule or schedule_rule is None: From 1f0e8c63668a5ae32741b12aac6009ac181cff65 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 22 Oct 2025 12:51:21 +0200 Subject: [PATCH 04/10] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f0f82f3e..750aacf39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Ongoing + +- Remove storing the last active schedules, to be handled by the HA Integration via PR [#806](https://github.com/plugwise/python-plugwise/pull/806) + ## v1.8.2 - Add support for Emma Pro wired, rename wireless Emma to Emma Pro via PR [#804](https://github.com/plugwise/python-plugwise/pull/804) From c20654483e6ce516ae101965c891a12d124f8e51 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 22 Oct 2025 13:01:50 +0200 Subject: [PATCH 05/10] Bump to v1.8.3a0 for testing --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 34bb89e09..3fb51fac3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise" -version = "1.8.2" +version = "1.8.3a0" license = "MIT" description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3." readme = "README.md" From 244b965a0af5f1d2be303a23cb3f4f5f28eb18b1 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 22 Oct 2025 13:06:34 +0200 Subject: [PATCH 06/10] Remove now unused argument --- plugwise/smile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index 880e604d9..bfeb61817 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -347,7 +347,7 @@ async def set_schedule_state( template_id = self._domain_objects.find(locator).attrib["id"] template = f'