From d206f04360280db7075196a49577a057853ae737 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 19:42:18 +0200 Subject: [PATCH 01/25] Add off-option to available legacy schedules --- plugwise/legacy/helper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index fd6713af3..a04805ef0 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -22,6 +22,7 @@ HEATER_CENTRAL_MEASUREMENTS, LIMITS, NONE, + OFF, P1_LEGACY_MEASUREMENTS, SENSORS, SPECIALS, @@ -468,7 +469,7 @@ def _schedules(self) -> tuple[list[str], str]: active = result.text == "on" if name is not None: - available = [name] + available = [name, OFF] if active: selected = name From 33a62f683a2336457abf8edc1a947188cefdaf5b Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 19:48:42 +0200 Subject: [PATCH 02/25] Adapt legacy set_schedule_state() --- plugwise/legacy/smile.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 7658c6e16..56fb72666 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -181,7 +181,7 @@ async def set_preset(self, _: str, preset: str) -> None: async def set_regulation_mode(self, mode: str) -> None: """Set-function placeholder for legacy devices.""" - async def set_schedule_state(self, _: str, state: str, __: str | None) -> None: + async def set_schedule_state(self, _: str, state: str, name: str | None) -> None: """Activate/deactivate the Schedule. Determined from - DOMAIN_OBJECTS. @@ -190,7 +190,10 @@ async def set_schedule_state(self, _: str, state: str, __: str | None) -> None: if state not in ["on", "off"]: raise PlugwiseError("Plugwise: invalid schedule state.") - name = "Thermostat schedule" + # Handle no schedule-name / Off-schedule provided + if name is None or name == OFF: + name = "Thermostat schedule" + schedule_rule_id: str | None = None for rule in self._domain_objects.findall("rule"): if rule.find("name").text == name: From 6f9ab0a96f80cce8474388d5f6a69033535355ca Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 19:51:18 +0200 Subject: [PATCH 03/25] Adapt legacy test-assert-jsons --- tests/data/anna/legacy_anna.json | 2 +- tests/data/anna/legacy_anna_2.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/data/anna/legacy_anna.json b/tests/data/anna/legacy_anna.json index 5fa718947..3cfd88e7b 100644 --- a/tests/data/anna/legacy_anna.json +++ b/tests/data/anna/legacy_anna.json @@ -24,7 +24,7 @@ }, "preset_modes": ["away", "vacation", "asleep", "home", "no_frost"], "active_preset": "home", - "available_schedules": ["Thermostat schedule"], + "available_schedules": ["Thermostat schedule", "off"], "select_schedule": "Thermostat schedule", "mode": "auto", "sensors": { diff --git a/tests/data/anna/legacy_anna_2.json b/tests/data/anna/legacy_anna_2.json index ba988c637..540e814f5 100644 --- a/tests/data/anna/legacy_anna_2.json +++ b/tests/data/anna/legacy_anna_2.json @@ -27,7 +27,7 @@ }, "preset_modes": ["vacation", "away", "no_frost", "home", "asleep"], "active_preset": null, - "available_schedules": ["Thermostat schedule"], + "available_schedules": ["Thermostat schedule", "off"], "select_schedule": "None", "mode": "heat", "sensors": { From 18c3b23f7ff90d98723b6d7d3ed833caea12f256 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 19:52:34 +0200 Subject: [PATCH 04/25] Add required import --- plugwise/legacy/smile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 56fb72666..d9f4b062b 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -15,6 +15,7 @@ LOCATIONS, LOGGER, MODULES, + OFF, REQUIRE_APPLIANCES, RULES, DeviceData, From 10d79aff2b0a478653aa2eaea10bffe30dce7b51 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 19:53:18 +0200 Subject: [PATCH 05/25] Save updated fixtures --- fixtures/legacy_anna/all_data.json | 2 +- fixtures/legacy_anna_2/all_data.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fixtures/legacy_anna/all_data.json b/fixtures/legacy_anna/all_data.json index f3f35b76b..784bb856e 100644 --- a/fixtures/legacy_anna/all_data.json +++ b/fixtures/legacy_anna/all_data.json @@ -36,7 +36,7 @@ }, "0d266432d64443e283b5d708ae98b455": { "active_preset": "home", - "available_schedules": ["Thermostat schedule"], + "available_schedules": ["Thermostat schedule", "off"], "dev_class": "thermostat", "firmware": "2017-03-13T11:54:58+01:00", "hardware": "6539-1301-500", diff --git a/fixtures/legacy_anna_2/all_data.json b/fixtures/legacy_anna_2/all_data.json index 413a5febe..476f6052e 100644 --- a/fixtures/legacy_anna_2/all_data.json +++ b/fixtures/legacy_anna_2/all_data.json @@ -2,7 +2,7 @@ "devices": { "9e7377867dc24e51b8098a5ba02bd89d": { "active_preset": null, - "available_schedules": ["Thermostat schedule"], + "available_schedules": ["Thermostat schedule", "off"], "dev_class": "thermostat", "firmware": "2017-03-13T11:54:58+01:00", "hardware": "6539-1301-5002", From b941207fe05fb165817da9aa8bf5042d35ecde53 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 20:01:30 +0200 Subject: [PATCH 06/25] Improve --- tests/test_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_init.py b/tests/test_init.py index c489e7502..fbc6cc727 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -763,7 +763,7 @@ async def tinker_legacy_thermostat_schedule(self, smile, unhappy=False): for state in states: _LOGGER.info("- Adjusting schedule to state %s", state) try: - await smile.set_schedule_state(None, state, None) + await smile.set_schedule_state(None, state) tinker_schedule_passed = True _LOGGER.info(" + working as intended") except pw_exceptions.PlugwiseError: From 7be843a2c8cbe99f819f188b450cefb68e5eed2e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 20:03:21 +0200 Subject: [PATCH 07/25] Further improve legacy test-assert-json --- tests/data/anna/legacy_anna_2.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data/anna/legacy_anna_2.json b/tests/data/anna/legacy_anna_2.json index 540e814f5..61c20873e 100644 --- a/tests/data/anna/legacy_anna_2.json +++ b/tests/data/anna/legacy_anna_2.json @@ -28,7 +28,7 @@ "preset_modes": ["vacation", "away", "no_frost", "home", "asleep"], "active_preset": null, "available_schedules": ["Thermostat schedule", "off"], - "select_schedule": "None", + "select_schedule": "off", "mode": "heat", "sensors": { "temperature": 21.4, From dff71966e06735ac7632ee2d516b7192ef87d8f4 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 20:05:36 +0200 Subject: [PATCH 08/25] Init selected as OFF --- plugwise/legacy/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index a04805ef0..b8a378c3e 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -453,7 +453,7 @@ def _presets(self) -> dict[str, list[float]]: def _schedules(self) -> tuple[list[str], str]: """Collect available schedules/schedules for the legacy thermostat.""" available: list[str] = [NONE] - selected = NONE + selected: str = OFF name: str | None = None search = self._domain_objects From 27f378255d92e6b95153a1774dcf35da8884d597 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 20:13:02 +0200 Subject: [PATCH 09/25] Fix schedule-off detection --- plugwise/data.py | 2 +- plugwise/legacy/data.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugwise/data.py b/plugwise/data.py index 1df72b30d..608f778a3 100644 --- a/plugwise/data.py +++ b/plugwise/data.py @@ -204,7 +204,7 @@ def _device_data_climate(self, device: DeviceData, data: DeviceData) -> None: # Operation modes: auto, heat, heat_cool, cool and off data["mode"] = "auto" self._count += 1 - if sel_schedule == NONE: + if sel_schedule in (NONE, OFF): data["mode"] = "heat" if self._cooling_present: data["mode"] = "cool" if self.check_reg_mode("cooling") else "heat_cool" diff --git a/plugwise/legacy/data.py b/plugwise/legacy/data.py index 908888c9b..06a9abf65 100644 --- a/plugwise/legacy/data.py +++ b/plugwise/legacy/data.py @@ -6,7 +6,7 @@ # Dict as class # Version detection -from plugwise.constants import NONE, DeviceData +from plugwise.constants import NONE, OFF, DeviceData from plugwise.legacy.helper import SmileLegacyHelper from plugwise.util import remove_empty_platform_dicts @@ -88,5 +88,5 @@ def _device_data_climate(self, device: DeviceData, data: DeviceData) -> None: # Operation modes: auto, heat data["mode"] = "auto" self._count += 1 - if sel_schedule == NONE: + if sel_schedule in (NONE, OFF): data["mode"] = "heat" From df246dd89cb018fbd6995c0f0ea6a17ee34c17f0 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 20:13:51 +0200 Subject: [PATCH 10/25] Save updated fixture --- fixtures/legacy_anna_2/all_data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fixtures/legacy_anna_2/all_data.json b/fixtures/legacy_anna_2/all_data.json index 476f6052e..9e7443924 100644 --- a/fixtures/legacy_anna_2/all_data.json +++ b/fixtures/legacy_anna_2/all_data.json @@ -11,7 +11,7 @@ "model": "ThermoTouch", "name": "Anna", "preset_modes": ["vacation", "away", "no_frost", "home", "asleep"], - "select_schedule": "None", + "select_schedule": "off", "sensors": { "illuminance": 19.5, "setpoint": 15.0, From d8d1fb19d9cf09c87e75215fcc1b4ed4290c2103 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 May 2024 20:27:34 +0200 Subject: [PATCH 11/25] Make sure to cover corner-cases Improve --- plugwise/helper.py | 2 ++ plugwise/legacy/helper.py | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index 1eb427446..a0f12f01a 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -1014,6 +1014,8 @@ def _schedules(self, location: str) -> tuple[list[str], str]: if schedules: available.remove(NONE) 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) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index b8a378c3e..6d00a6a05 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -453,7 +453,7 @@ def _presets(self) -> dict[str, list[float]]: def _schedules(self) -> tuple[list[str], str]: """Collect available schedules/schedules for the legacy thermostat.""" available: list[str] = [NONE] - selected: str = OFF + selected: str = NONE name: str | None = None search = self._domain_objects @@ -470,8 +470,7 @@ def _schedules(self) -> tuple[list[str], str]: if name is not None: available = [name, OFF] - if active: - selected = name + selected = name if active else OFF return available, selected From edbd96e1a45a5678a97597248735f6e9a0905487 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 08:32:51 +0200 Subject: [PATCH 12/25] Simplify --- plugwise/legacy/helper.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index 6d00a6a05..b5d291825 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -451,22 +451,21 @@ def _presets(self) -> dict[str, list[float]]: return presets def _schedules(self) -> tuple[list[str], str]: - """Collect available schedules/schedules for the legacy thermostat.""" + """Collect the schedule for the legacy thermostat.""" available: list[str] = [NONE] selected: str = NONE name: str | None = None search = self._domain_objects - for schedule in search.findall("./rule"): - if rule_name := schedule.find("name").text: - if "preset" not in rule_name: - name = rule_name + if search.find("./rule[name='Thermostat schedule']") is not None: + name = "Thermostat schedule" log_type = "schedule_state" locator = f"./appliance[type='thermostat']/logs/point_log[type='{log_type}']/period/measurement" active = False if (result := search.find(locator)) is not None: active = result.text == "on" + # check if schedule is empty if name is not None: available = [name, OFF] From 1f5087e2d88a9ade36520a93630e0777e0748e25 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 16:32:58 +0200 Subject: [PATCH 13/25] Remove added typing, not needed --- plugwise/legacy/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index b5d291825..8d0549810 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -453,7 +453,7 @@ def _presets(self) -> dict[str, list[float]]: def _schedules(self) -> tuple[list[str], str]: """Collect the schedule for the legacy thermostat.""" available: list[str] = [NONE] - selected: str = NONE + selected = NONE name: str | None = None search = self._domain_objects From c4ff7bb7ba6af9d8af570f351860df12b1bbf621 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 16:42:37 +0200 Subject: [PATCH 14/25] Show an active but empty schedule as no schedule --- plugwise/legacy/helper.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index 8d0549810..c7d786bae 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -465,9 +465,9 @@ def _schedules(self) -> tuple[list[str], str]: active = False if (result := search.find(locator)) is not None: active = result.text == "on" - # check if schedule is empty - if name is not None: + # Show an empty schedule as no schedule found + if search.find("./rule[name='Thermostat schedule']/directives") is not None and name is not None: available = [name, OFF] selected = name if active else OFF @@ -477,5 +477,4 @@ def _thermostat_uri(self) -> str: """Determine the location-set_temperature uri - from APPLIANCES.""" locator = "./appliance[type='thermostat']" appliance_id = self._appliances.find(locator).attrib["id"] - return f"{APPLIANCES};id={appliance_id}/thermostat" From f2e57e6ce3fffa918408a45b6f605b3297626a7a Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 16:50:12 +0200 Subject: [PATCH 15/25] Change --- plugwise/legacy/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index c7d786bae..ab0c97697 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -467,7 +467,7 @@ def _schedules(self) -> tuple[list[str], str]: active = result.text == "on" # Show an empty schedule as no schedule found - if search.find("./rule[name='Thermostat schedule']/directives") is not None and name is not None: + if search.find('f./rule[name="{name}"]/directives') is not None and name is not None: available = [name, OFF] selected = name if active else OFF From 03f0367f6f08522003dd9caaa4c74bef98327e0e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 16:51:56 +0200 Subject: [PATCH 16/25] Correct/adapt related test-assert-json --- tests/data/anna/legacy_anna.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/data/anna/legacy_anna.json b/tests/data/anna/legacy_anna.json index 3cfd88e7b..28bc2f8c0 100644 --- a/tests/data/anna/legacy_anna.json +++ b/tests/data/anna/legacy_anna.json @@ -24,9 +24,9 @@ }, "preset_modes": ["away", "vacation", "asleep", "home", "no_frost"], "active_preset": "home", - "available_schedules": ["Thermostat schedule", "off"], - "select_schedule": "Thermostat schedule", - "mode": "auto", + "available_schedules": ["None"], + "select_schedule": "None", + "mode": "heat", "sensors": { "temperature": 20.4, "illuminance": 151, From 9ebf31aeb2270becee589e570dc8f56be03758a5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 17:25:15 +0200 Subject: [PATCH 17/25] Correct syntax, improve directives-locator --- plugwise/legacy/helper.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index ab0c97697..8b507ef53 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -456,9 +456,11 @@ def _schedules(self) -> tuple[list[str], str]: selected = NONE name: str | None = None + rule_id = NONE search = self._domain_objects - if search.find("./rule[name='Thermostat schedule']") is not None: + if (result := search.find("./rule[name='Thermostat schedule']")) is not None: name = "Thermostat schedule" + rule_id = result.attrib["id"] log_type = "schedule_state" locator = f"./appliance[type='thermostat']/logs/point_log[type='{log_type}']/period/measurement" @@ -467,7 +469,8 @@ def _schedules(self) -> tuple[list[str], str]: active = result.text == "on" # Show an empty schedule as no schedule found - if search.find('f./rule[name="{name}"]/directives') is not None and name is not None: + directives = search.find(f'./rule[@id="{rule_id}"]/directives/when/then') is not None + if directives and name is not None: available = [name, OFF] selected = name if active else OFF From c5faf0b84dedcbe4e9ccba84f36ceb4f6bb929a1 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 17:27:54 +0200 Subject: [PATCH 18/25] Save updated fixture --- fixtures/legacy_anna/all_data.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fixtures/legacy_anna/all_data.json b/fixtures/legacy_anna/all_data.json index 784bb856e..baa8721cc 100644 --- a/fixtures/legacy_anna/all_data.json +++ b/fixtures/legacy_anna/all_data.json @@ -36,16 +36,16 @@ }, "0d266432d64443e283b5d708ae98b455": { "active_preset": "home", - "available_schedules": ["Thermostat schedule", "off"], + "available_schedules": ["None"], "dev_class": "thermostat", "firmware": "2017-03-13T11:54:58+01:00", "hardware": "6539-1301-500", "location": "0000aaaa0000aaaa0000aaaa0000aa00", - "mode": "auto", + "mode": "heat", "model": "ThermoTouch", "name": "Anna", "preset_modes": ["away", "vacation", "asleep", "home", "no_frost"], - "select_schedule": "Thermostat schedule", + "select_schedule": "None", "sensors": { "illuminance": 151, "setpoint": 20.5, From a2b3719bfc1bb77d132062088b1733b4aff1dc10 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 17:31:36 +0200 Subject: [PATCH 19/25] Optimize --- plugwise/legacy/helper.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index 8b507ef53..d3b758375 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -453,10 +453,9 @@ def _presets(self) -> dict[str, list[float]]: def _schedules(self) -> tuple[list[str], str]: """Collect the schedule for the legacy thermostat.""" available: list[str] = [NONE] - selected = NONE + rule_id = selected = NONE name: str | None = None - rule_id = NONE search = self._domain_objects if (result := search.find("./rule[name='Thermostat schedule']")) is not None: name = "Thermostat schedule" From 6c73800ce518853341334aff90cbe5a4e7d93e5b Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 19:50:46 +0200 Subject: [PATCH 20/25] Update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df518d7bf..ba4642ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog -## Ongoing +## v0.37.6 +- Schedule-related improvements. - Revert removal of set_temperature_offset() function. ## v0.37.5 From 2cc0d7f29c6a9682e8c86d63b14f81cea8b6ba64 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 17 May 2024 19:51:21 +0200 Subject: [PATCH 21/25] Bump to v0.37.6 release-version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bd79dde10..e58a50803 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise" -version = "0.37.5" +version = "0.37.6" license = {file = "LICENSE"} description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3." readme = "README.md" From d731bff65b3aa44312544d47c4b2ea6b22cd72b2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 18 May 2024 09:53:28 +0200 Subject: [PATCH 22/25] Update all `in [..., ...]` constructs to using set() --- plugwise/helper.py | 10 +++++----- plugwise/legacy/helper.py | 6 +++--- plugwise/legacy/smile.py | 2 +- plugwise/smile.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index a0f12f01a..c0f9609e1 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -214,7 +214,7 @@ def __init__(self) -> None: self._thermo_locs: dict[str, ThermoLoc] = {} ################################################################### # '_cooling_enabled' can refer to the state of the Elga heatpump - # connected to an Anna. For Elga, 'elga_status_code' in [8, 9] + # connected to an Anna. For Elga, 'elga_status_code' in (8, 9) # means cooling mode is available, next to heating mode. # 'elga_status_code' = 8 means cooling is active, 9 means idle. # @@ -523,7 +523,7 @@ def _get_measurement_data(self, dev_id: str) -> DeviceData: # Techneco Elga has cooling-capability self._cooling_present = True data["model"] = "Generic heater/cooler" - self._cooling_enabled = data["elga_status_code"] in [8, 9] + self._cooling_enabled = data["elga_status_code"] in (8, 9) data["binary_sensors"]["cooling_state"] = self._cooling_active = ( data["elga_status_code"] == 8 ) @@ -590,7 +590,7 @@ def _appliance_measurements( data["select_dhw_mode"] = appl_p_loc.text case _ as measurement if measurement in BINARY_SENSORS: bs_key = cast(BinarySensorType, measurement) - bs_value = appl_p_loc.text in ["on", "true"] + bs_value = appl_p_loc.text in ("on", "true") data["binary_sensors"][bs_key] = bs_value case _ as measurement if measurement in SENSORS: s_key = cast(SensorType, measurement) @@ -600,11 +600,11 @@ def _appliance_measurements( data["sensors"][s_key] = s_value case _ as measurement if measurement in SWITCHES: sw_key = cast(SwitchType, measurement) - sw_value = appl_p_loc.text in ["on", "true"] + sw_value = appl_p_loc.text in ("on", "true") data["switches"][sw_key] = sw_value case _ as measurement if measurement in SPECIALS: sp_key = cast(SpecialType, measurement) - sp_value = appl_p_loc.text in ["on", "true"] + sp_value = appl_p_loc.text in ("on", "true") data[sp_key] = sp_value case "elga_status_code": data["elga_status_code"] = int(appl_p_loc.text) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index d3b758375..a1b10a413 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -344,7 +344,7 @@ def _appliance_measurements( match measurement: case _ as measurement if measurement in BINARY_SENSORS: bs_key = cast(BinarySensorType, measurement) - bs_value = appl_p_loc.text in ["on", "true"] + bs_value = appl_p_loc.text in ("on", "true") data["binary_sensors"][bs_key] = bs_value case _ as measurement if measurement in SENSORS: s_key = cast(SensorType, measurement) @@ -354,11 +354,11 @@ def _appliance_measurements( data["sensors"][s_key] = s_value case _ as measurement if measurement in SWITCHES: sw_key = cast(SwitchType, measurement) - sw_value = appl_p_loc.text in ["on", "true"] + sw_value = appl_p_loc.text in ("on", "true") data["switches"][sw_key] = sw_value case _ as measurement if measurement in SPECIALS: sp_key = cast(SpecialType, measurement) - sp_value = appl_p_loc.text in ["on", "true"] + sp_value = appl_p_loc.text in ("on", "true") data[sp_key] = sp_value i_locator = f'.//logs/interval_log[type="{measurement}"]/period/measurement' diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index d9f4b062b..293f0dfb3 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -188,7 +188,7 @@ async def set_schedule_state(self, _: str, state: str, name: str | None) -> None Determined from - DOMAIN_OBJECTS. Used in HA Core to set the hvac_mode: in practice switch between schedule on - off. """ - if state not in ["on", "off"]: + if state not in ("on", "off"): raise PlugwiseError("Plugwise: invalid schedule state.") # Handle no schedule-name / Off-schedule provided diff --git a/plugwise/smile.py b/plugwise/smile.py index 555c8679d..e1cbdc359 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -266,7 +266,7 @@ async def set_schedule_state( Used in HA Core to set the hvac_mode: in practice switch between schedule on - off. """ # Input checking - if new_state not in ["on", "off"]: + if new_state not in ("on", "off"): raise PlugwiseError("Plugwise: invalid schedule state.") # Translate selection of Off-schedule-option to disabling the active schedule From 771055183c8e14d1bc922764b141bc329244f34b Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 18 May 2024 11:07:31 +0200 Subject: [PATCH 23/25] Replace list[str] typing by tuple[str, ...] --- plugwise/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise/constants.py b/plugwise/constants.py index 3b959d68c..74a7d9ba3 100644 --- a/plugwise/constants.py +++ b/plugwise/constants.py @@ -207,13 +207,13 @@ "stretch_v2": SMILE(STRETCH, "Stretch"), "stretch_v3": SMILE(STRETCH, "Stretch"), } -REQUIRE_APPLIANCES: Final[list[str]] = [ +REQUIRE_APPLIANCES: Final[tuple[str, ...]] = ( "smile_thermo_v1", "smile_thermo_v3", "smile_thermo_v4", "stretch_v2", "stretch_v3", -] +) # Class, Literal and related tuple-definitions From 43483d8f3022ed1f0578af82fbb9e64c95e03a14 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 18 May 2024 12:28:21 +0200 Subject: [PATCH 24/25] Fix duplication, add docstring --- plugwise/helper.py | 34 +++++----------------------------- plugwise/legacy/helper.py | 35 +++++++---------------------------- plugwise/util.py | 39 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 58 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index c0f9609e1..e0f34c2fb 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -15,8 +15,6 @@ ADAM, ANNA, ATTR_NAME, - ATTR_UNIT_OF_MEASUREMENT, - BINARY_SENSORS, DATA, DEVICE_MEASUREMENTS, DHW_SETPOINT, @@ -29,9 +27,6 @@ NONE, OFF, P1_MEASUREMENTS, - SENSORS, - SPECIALS, - SWITCHES, TEMP_CELSIUS, THERMOSTAT_CLASSES, TOGGLES, @@ -39,12 +34,9 @@ ActuatorData, ActuatorDataType, ActuatorType, - BinarySensorType, DeviceData, GatewayData, SensorType, - SpecialType, - SwitchType, ThermoLoc, ToggleNameType, ) @@ -58,6 +50,7 @@ check_model, escape_illegal_xml_characters, format_measure, + match_on_true_cases, skip_obsolete_measurements, ) @@ -585,29 +578,12 @@ def _appliance_measurements( measurement = new_name match measurement: - # measurements with states "on" or "off" that need to be passed directly - case "select_dhw_mode": - data["select_dhw_mode"] = appl_p_loc.text - case _ as measurement if measurement in BINARY_SENSORS: - bs_key = cast(BinarySensorType, measurement) - bs_value = appl_p_loc.text in ("on", "true") - data["binary_sensors"][bs_key] = bs_value - case _ as measurement if measurement in SENSORS: - s_key = cast(SensorType, measurement) - s_value = format_measure( - appl_p_loc.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT) - ) - data["sensors"][s_key] = s_value - case _ as measurement if measurement in SWITCHES: - sw_key = cast(SwitchType, measurement) - sw_value = appl_p_loc.text in ("on", "true") - data["switches"][sw_key] = sw_value - case _ as measurement if measurement in SPECIALS: - sp_key = cast(SpecialType, measurement) - sp_value = appl_p_loc.text in ("on", "true") - data[sp_key] = sp_value case "elga_status_code": data["elga_status_code"] = int(appl_p_loc.text) + case "select_dhw_mode": + data["select_dhw_mode"] = appl_p_loc.text + + match_on_true_cases(measurement, attrs, appl_p_loc, data) i_locator = f'.//logs/interval_log[type="{measurement}"]/period/measurement' if (appl_i_loc := appliance.find(i_locator)) is not None: diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index a1b10a413..45638fc5a 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -12,8 +12,6 @@ ACTUATOR_CLASSES, APPLIANCES, ATTR_NAME, - ATTR_UNIT_OF_MEASUREMENT, - BINARY_SENSORS, DATA, DEVICE_MEASUREMENTS, ENERGY_WATT_HOUR, @@ -24,9 +22,6 @@ NONE, OFF, P1_LEGACY_MEASUREMENTS, - SENSORS, - SPECIALS, - SWITCHES, TEMP_CELSIUS, THERMOSTAT_CLASSES, UOM, @@ -34,15 +29,17 @@ ActuatorDataType, ActuatorType, ApplianceType, - BinarySensorType, DeviceData, GatewayData, SensorType, - SpecialType, - SwitchType, ThermoLoc, ) -from plugwise.util import format_measure, skip_obsolete_measurements, version_to_model +from plugwise.util import ( + format_measure, + match_on_true_cases, + skip_obsolete_measurements, + version_to_model, +) # This way of importing aiohttp is because of patch/mocking in testing (aiohttp timeouts) from defusedxml import ElementTree as etree @@ -341,25 +338,7 @@ def _appliance_measurements( if new_name := getattr(attrs, ATTR_NAME, None): measurement = new_name - match measurement: - case _ as measurement if measurement in BINARY_SENSORS: - bs_key = cast(BinarySensorType, measurement) - bs_value = appl_p_loc.text in ("on", "true") - data["binary_sensors"][bs_key] = bs_value - case _ as measurement if measurement in SENSORS: - s_key = cast(SensorType, measurement) - s_value = format_measure( - appl_p_loc.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT) - ) - data["sensors"][s_key] = s_value - case _ as measurement if measurement in SWITCHES: - sw_key = cast(SwitchType, measurement) - sw_value = appl_p_loc.text in ("on", "true") - data["switches"][sw_key] = sw_value - case _ as measurement if measurement in SPECIALS: - sp_key = cast(SpecialType, measurement) - sp_value = appl_p_loc.text in ("on", "true") - data[sp_key] = sp_value + match_on_true_cases(measurement, attrs, appl_p_loc, data) i_locator = f'.//logs/interval_log[type="{measurement}"]/period/measurement' if (appl_i_loc := appliance.find(i_locator)) is not None: diff --git a/plugwise/util.py b/plugwise/util.py index 53e1232d4..439371ede 100644 --- a/plugwise/util.py +++ b/plugwise/util.py @@ -3,19 +3,30 @@ import datetime as dt import re +from typing import cast from plugwise.constants import ( ATTR_UNIT_OF_MEASUREMENT, + BINARY_SENSORS, + DATA, ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, HW_MODELS, OBSOLETE_MEASUREMENTS, PERCENTAGE, POWER_WATT, + SENSORS, SPECIAL_FORMAT, + SPECIALS, + SWITCHES, TEMP_CELSIUS, + UOM, + BinarySensorType, DeviceData, ModelData, + SensorType, + SpecialType, + SwitchType, ) from defusedxml import ElementTree as etree @@ -23,7 +34,7 @@ def check_alternative_location(loc: Munch, legacy: bool) -> Munch: - """Try.""" + """Helper-function for _power_data_peak_value().""" if in_alternative_location(loc, legacy): # Avoid double processing by skipping one peak-list option if loc.peak_select == "nl_offpeak": @@ -146,6 +157,32 @@ def get_vendor_name(module: etree, model_data: ModelData) -> ModelData: return model_data +def match_on_true_cases( + measurement: str, + attrs: DATA | UOM, + location: etree, + data: DeviceData, +) -> None: + """Helper-function for common match-case execution.""" + value = location.text in ("on", "true") + match measurement: + case _ as measurement if measurement in BINARY_SENSORS: + bs_key = cast(BinarySensorType, measurement) + data["binary_sensors"][bs_key] = value + case _ as measurement if measurement in SENSORS: + s_key = cast(SensorType, measurement) + s_value = format_measure( + location.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT) + ) + data["sensors"][s_key] = s_value + case _ as measurement if measurement in SWITCHES: + sw_key = cast(SwitchType, measurement) + data["switches"][sw_key] = value + case _ as measurement if measurement in SPECIALS: + sp_key = cast(SpecialType, measurement) + data[sp_key] = value + + def power_data_local_format( attrs: dict[str, str], key_string: str, val: str ) -> float | int: From b31d57b45d2e4a4600cf39de1b2db9adeac2f9d8 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 18 May 2024 18:30:32 +0200 Subject: [PATCH 25/25] Improve common function-name --- plugwise/helper.py | 4 +-- plugwise/legacy/helper.py | 4 +-- plugwise/util.py | 52 +++++++++++++++++++-------------------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index e0f34c2fb..d54cff2c0 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -48,9 +48,9 @@ ) from plugwise.util import ( check_model, + common_match_cases, escape_illegal_xml_characters, format_measure, - match_on_true_cases, skip_obsolete_measurements, ) @@ -583,7 +583,7 @@ def _appliance_measurements( case "select_dhw_mode": data["select_dhw_mode"] = appl_p_loc.text - match_on_true_cases(measurement, attrs, appl_p_loc, data) + common_match_cases(measurement, attrs, appl_p_loc, data) i_locator = f'.//logs/interval_log[type="{measurement}"]/period/measurement' if (appl_i_loc := appliance.find(i_locator)) is not None: diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index 45638fc5a..862a1c3fb 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -35,8 +35,8 @@ ThermoLoc, ) from plugwise.util import ( + common_match_cases, format_measure, - match_on_true_cases, skip_obsolete_measurements, version_to_model, ) @@ -338,7 +338,7 @@ def _appliance_measurements( if new_name := getattr(attrs, ATTR_NAME, None): measurement = new_name - match_on_true_cases(measurement, attrs, appl_p_loc, data) + common_match_cases(measurement, attrs, appl_p_loc, data) i_locator = f'.//logs/interval_log[type="{measurement}"]/period/measurement' if (appl_i_loc := appliance.find(i_locator)) is not None: diff --git a/plugwise/util.py b/plugwise/util.py index 439371ede..0aba3d2d8 100644 --- a/plugwise/util.py +++ b/plugwise/util.py @@ -113,6 +113,32 @@ def check_model(name: str | None, vendor_name: str | None) -> str | None: return name +def common_match_cases( + measurement: str, + attrs: DATA | UOM, + location: etree, + data: DeviceData, +) -> None: + """Helper-function for common match-case execution.""" + value = location.text in ("on", "true") + match measurement: + case _ as measurement if measurement in BINARY_SENSORS: + bs_key = cast(BinarySensorType, measurement) + data["binary_sensors"][bs_key] = value + case _ as measurement if measurement in SENSORS: + s_key = cast(SensorType, measurement) + s_value = format_measure( + location.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT) + ) + data["sensors"][s_key] = s_value + case _ as measurement if measurement in SWITCHES: + sw_key = cast(SwitchType, measurement) + data["switches"][sw_key] = value + case _ as measurement if measurement in SPECIALS: + sp_key = cast(SpecialType, measurement) + data[sp_key] = value + + def escape_illegal_xml_characters(xmldata: str) -> str: """Replace illegal &-characters.""" return re.sub(r"&([^a-zA-Z#])", r"&\1", xmldata) @@ -157,32 +183,6 @@ def get_vendor_name(module: etree, model_data: ModelData) -> ModelData: return model_data -def match_on_true_cases( - measurement: str, - attrs: DATA | UOM, - location: etree, - data: DeviceData, -) -> None: - """Helper-function for common match-case execution.""" - value = location.text in ("on", "true") - match measurement: - case _ as measurement if measurement in BINARY_SENSORS: - bs_key = cast(BinarySensorType, measurement) - data["binary_sensors"][bs_key] = value - case _ as measurement if measurement in SENSORS: - s_key = cast(SensorType, measurement) - s_value = format_measure( - location.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT) - ) - data["sensors"][s_key] = s_value - case _ as measurement if measurement in SWITCHES: - sw_key = cast(SwitchType, measurement) - data["switches"][sw_key] = value - case _ as measurement if measurement in SPECIALS: - sp_key = cast(SpecialType, measurement) - data[sp_key] = value - - def power_data_local_format( attrs: dict[str, str], key_string: str, val: str ) -> float | int: