From 7551d55c08df552c25faccc7772b4c2f12867a2e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 12 Jun 2025 15:05:36 +0200 Subject: [PATCH 01/47] Let set-switch functions return a bool, improve handling of locked state --- plugwise/__init__.py | 4 ++-- plugwise/legacy/smile.py | 22 +++++++++++----------- plugwise/smile.py | 20 ++++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index 35250323d..65f6c51dd 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -398,10 +398,10 @@ async def set_temperature_offset(self, dev_id: str, offset: float) -> None: async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str - ) -> None: + ) -> bool: """Set the given State of the relevant Switch.""" try: - await self._smile_api.set_switch_state(appl_id, members, model, state) + return await self._smile_api.set_switch_state(appl_id, members, model, state) except ConnectionFailedError as exc: raise ConnectionFailedError( f"Failed to set switch state: {str(exc)}" diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index eedd360f7..6e9fdcdfb 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -234,7 +234,7 @@ async def set_schedule_state( async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str - ) -> None: + ) -> bool: """Set the given state of the relevant switch. For individual switches, sets the state directly. @@ -269,7 +269,7 @@ async def set_switch_state( "" ) await self.call_request(APPLIANCES, method="post", data=data) - return + return True # Handle group of switches data = f"<{switch.func_type}>{state}" @@ -280,26 +280,26 @@ async def set_switch_state( # Handle individual relay switches uri = f"{APPLIANCES};id={appl_id}/relay" - if model == "relay": - locator = ( - f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}/lock' - ) + if model == "relay" and self.gw_entities[appl_id]["switches"]["lock"]: # Don't bother switching a relay when the corresponding lock-state is true - if self._appliances.find(locator).text == "true": - raise PlugwiseError("Plugwise: the locked Relay was not switched.") + return False await self.call_request(uri, method="put", data=data) + return True async def _set_groupswitch_member_state( self, data: str, members: list[str], state: str, switch: Munch - ) -> None: + ) -> bool: """Helper-function for set_switch_state(). Set the given State of the relevant Switch (relay) within a group of members. """ for member in members: - uri = f"{APPLIANCES};id={member}/relay" - await self.call_request(uri, method="put", data=data) + if not self.gw_entities[member]["switches"]["lock"]: + uri = f"{APPLIANCES};id={member}/relay" + await self.call_request(uri, method="put", data=data) + + return True async def set_temperature(self, _: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" diff --git a/plugwise/smile.py b/plugwise/smile.py index f3b259ff8..af0ffad38 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -377,7 +377,7 @@ def determine_contexts( async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str - ) -> None: + ) -> bool: """Set the given State of the relevant Switch.""" switch = Munch() switch.actuator = "actuator_functionalities" @@ -418,19 +418,16 @@ async def set_switch_state( f"" ) uri = f"{APPLIANCES};id={appl_id}/{switch.device};id={switch_id}" - if model == "relay": - locator = ( - f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}/lock' - ) - # Don't bother switching a relay when the corresponding lock-state is true - if self._domain_objects.find(locator).text == "true": - raise PlugwiseError("Plugwise: the locked Relay was not switched.") + if model == "relay" and self.gw_entities[appl_id]["switches"]["lock"]: + # Don't switch a relay when its corresponding lock-state is true + return False await self.call_request(uri, method="put", data=data) + return True async def _set_groupswitch_member_state( self, members: list[str], state: str, switch: Munch - ) -> None: + ) -> bool: """Helper-function for set_switch_state(). Set the given State of the relevant Switch within a group of members. @@ -444,7 +441,10 @@ async def _set_groupswitch_member_state( f"<{switch.func}>{state}" f"" ) - await self.call_request(uri, method="put", data=data) + if not self.gw_entities[member]["switches"].get("lock"): + await self.call_request(uri, method="put", data=data) + + return True async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" From f89fe4ede37d1394df4c6a56546afdd284afa124 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 12 Jun 2025 15:25:51 +0200 Subject: [PATCH 02/47] Adapt tinker_switch() test function --- tests/test_init.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 0369f6503..3f2980263 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -688,8 +688,9 @@ async def tinker_switch( for new_state in ["false", "true", "false"]: _LOGGER.info("- Switching %s", new_state) try: - await smile.set_switch_state(dev_id, members, model, new_state) - tinker_switch_passed = True + tinker_switch_passed = await smile.set_switch_state( + dev_id, members, model, new_state + ) _LOGGER.info(" + tinker_switch worked as intended") except pw_exceptions.PlugwiseError: _LOGGER.info(" + locked, not switched as expected") From 46e05d64547e6ce3a8ba2b8122daff823f6a635e Mon Sep 17 00:00:00 2001 From: autoruff Date: Thu, 12 Jun 2025 14:02:36 +0000 Subject: [PATCH 03/47] fixup: relay-sw-improve Python code fixed using ruff --- plugwise/__init__.py | 4 +++- plugwise/legacy/smile.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index 65f6c51dd..8cce15321 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -401,7 +401,9 @@ async def set_switch_state( ) -> bool: """Set the given State of the relevant Switch.""" try: - return await self._smile_api.set_switch_state(appl_id, members, model, state) + return await self._smile_api.set_switch_state( + appl_id, members, model, state + ) except ConnectionFailedError as exc: raise ConnectionFailedError( f"Failed to set switch state: {str(exc)}" diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 6e9fdcdfb..e5257d55a 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -298,7 +298,7 @@ async def _set_groupswitch_member_state( if not self.gw_entities[member]["switches"]["lock"]: uri = f"{APPLIANCES};id={member}/relay" await self.call_request(uri, method="put", data=data) - + return True async def set_temperature(self, _: str, items: dict[str, float]) -> None: From 6a1634fb0bea084e83f08202d451d0d3236f2a07 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 08:07:40 +0200 Subject: [PATCH 04/47] Return fixes --- plugwise/constants.py | 1 + plugwise/legacy/smile.py | 8 ++++++-- plugwise/smile.py | 8 ++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/plugwise/constants.py b/plugwise/constants.py index f12c8bbd9..5d306e9a3 100644 --- a/plugwise/constants.py +++ b/plugwise/constants.py @@ -23,6 +23,7 @@ PRESET_AWAY: Final = "away" PRESSURE_BAR: Final = "bar" SIGNAL_STRENGTH_DECIBELS_MILLIWATT: Final = "dBm" +STATE_ON: Final = "on" TEMP_CELSIUS: Final = "°C" TEMP_KELVIN: Final = "°K" TIME_MILLISECONDS: Final = "ms" diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index e5257d55a..ca1792984 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -18,6 +18,7 @@ OFF, REQUIRE_APPLIANCES, RULES, + STATE_ON, GwEntityData, ThermoLoc, ) @@ -241,6 +242,7 @@ async def set_switch_state( For group switches, sets the state for each member in the group separately. For switch-locks, sets the lock state using a different data format. """ + req_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" switch.func_type = "relay_functionality" @@ -285,7 +287,7 @@ async def set_switch_state( return False await self.call_request(uri, method="put", data=data) - return True + return req_state async def _set_groupswitch_member_state( self, data: str, members: list[str], state: str, switch: Munch @@ -294,12 +296,14 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch (relay) within a group of members. """ + switched = 0 for member in members: if not self.gw_entities[member]["switches"]["lock"]: uri = f"{APPLIANCES};id={member}/relay" await self.call_request(uri, method="put", data=data) + switched += 1 - return True + return switched > 0 async def set_temperature(self, _: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" diff --git a/plugwise/smile.py b/plugwise/smile.py index af0ffad38..9fb8547cd 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -22,6 +22,7 @@ NOTIFICATIONS, OFF, RULES, + STATE_ON, GwEntityData, ThermoLoc, ) @@ -379,6 +380,7 @@ async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: """Set the given State of the relevant Switch.""" + req_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" switch.device = "relay" @@ -423,7 +425,7 @@ async def set_switch_state( return False await self.call_request(uri, method="put", data=data) - return True + return req_state async def _set_groupswitch_member_state( self, members: list[str], state: str, switch: Munch @@ -432,6 +434,7 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch within a group of members. """ + switched = 0 for member in members: locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}' switch_id = self._domain_objects.find(locator).attrib["id"] @@ -443,8 +446,9 @@ async def _set_groupswitch_member_state( ) if not self.gw_entities[member]["switches"].get("lock"): await self.call_request(uri, method="put", data=data) + switched += 1 - return True + return switched > 0 async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" From e3edfe96401a4fdb257de1f41304c8ae1f1a6d1e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 08:24:47 +0200 Subject: [PATCH 05/47] Improve test --- tests/test_init.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 3f2980263..986148e00 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -688,10 +688,15 @@ async def tinker_switch( for new_state in ["false", "true", "false"]: _LOGGER.info("- Switching %s", new_state) try: - tinker_switch_passed = await smile.set_switch_state( + result = await smile.set_switch_state( dev_id, members, model, new_state ) - _LOGGER.info(" + tinker_switch worked as intended") + if result == new_state: + tinker_switch_passed = True + _LOGGER.info(" + tinker_switch worked as intended") + else: + _LOGGER.info(" + tinker_switch failed unexpectedly") + return False except pw_exceptions.PlugwiseError: _LOGGER.info(" + locked, not switched as expected") return False From 93d47b303d2a5fdb38c0a7f838c914fdf4d2b2c9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 08:31:23 +0200 Subject: [PATCH 06/47] More return fixes --- plugwise/legacy/smile.py | 6 +++++- plugwise/smile.py | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index ca1792984..b229b8745 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -296,6 +296,7 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch (relay) within a group of members. """ + req_state = state == STATE_ON switched = 0 for member in members: if not self.gw_entities[member]["switches"]["lock"]: @@ -303,7 +304,10 @@ async def _set_groupswitch_member_state( await self.call_request(uri, method="put", data=data) switched += 1 - return switched > 0 + if switched > 0: + return req_state + else: + return not req_state async def set_temperature(self, _: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" diff --git a/plugwise/smile.py b/plugwise/smile.py index 9fb8547cd..e0b29f0b8 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -434,6 +434,7 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch within a group of members. """ + req_state = state == STATE_ON switched = 0 for member in members: locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}' @@ -448,7 +449,11 @@ async def _set_groupswitch_member_state( await self.call_request(uri, method="put", data=data) switched += 1 - return switched > 0 + if switched > 0: + return req_state + else: + return not req_state + async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" From 323c39ac648fc772a342351e182cb2526e646a1f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 16:59:41 +0200 Subject: [PATCH 07/47] Fix expected state value --- plugwise/legacy/smile.py | 5 ++--- plugwise/smile.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index b229b8745..a4d452a45 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -18,7 +18,6 @@ OFF, REQUIRE_APPLIANCES, RULES, - STATE_ON, GwEntityData, ThermoLoc, ) @@ -242,7 +241,7 @@ async def set_switch_state( For group switches, sets the state for each member in the group separately. For switch-locks, sets the lock state using a different data format. """ - req_state = state == STATE_ON + req_state = state == "true" switch = Munch() switch.actuator = "actuator_functionalities" switch.func_type = "relay_functionality" @@ -296,7 +295,7 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch (relay) within a group of members. """ - req_state = state == STATE_ON + req_state = state == "true" switched = 0 for member in members: if not self.gw_entities[member]["switches"]["lock"]: diff --git a/plugwise/smile.py b/plugwise/smile.py index e0b29f0b8..292363a97 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -22,7 +22,6 @@ NOTIFICATIONS, OFF, RULES, - STATE_ON, GwEntityData, ThermoLoc, ) @@ -380,7 +379,7 @@ async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: """Set the given State of the relevant Switch.""" - req_state = state == STATE_ON + req_state = state == "true" switch = Munch() switch.actuator = "actuator_functionalities" switch.device = "relay" @@ -434,7 +433,7 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch within a group of members. """ - req_state = state == STATE_ON + req_state = state == "true" switched = 0 for member in members: locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}' From 0b2d616313b8962c100b52a084383fdef54eebb9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 17:06:59 +0200 Subject: [PATCH 08/47] Fix testcode --- tests/test_init.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_init.py b/tests/test_init.py index 986148e00..d804a1b88 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -684,6 +684,7 @@ async def tinker_switch( """Turn a Switch on and off to test functionality.""" _LOGGER.info("Asserting modifying settings for switch devices:") _LOGGER.info("- Devices (%s):", dev_id) + convert = {"true": True, "false": False} tinker_switch_passed = False for new_state in ["false", "true", "false"]: _LOGGER.info("- Switching %s", new_state) @@ -691,7 +692,7 @@ async def tinker_switch( result = await smile.set_switch_state( dev_id, members, model, new_state ) - if result == new_state: + if result == convert[new_state]: tinker_switch_passed = True _LOGGER.info(" + tinker_switch worked as intended") else: From 59c03a202ab42e10123d570f42c22ca8e15afbec Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 17:11:39 +0200 Subject: [PATCH 09/47] Fix legacy set_switch for lock --- plugwise/legacy/smile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index a4d452a45..62ebaefbc 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -270,7 +270,7 @@ async def set_switch_state( "" ) await self.call_request(APPLIANCES, method="post", data=data) - return True + return req_state # Handle group of switches data = f"<{switch.func_type}>{state}" From 2eb36bab68e2105df1da0160df96de75d85eb2dc Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 17:12:37 +0200 Subject: [PATCH 10/47] Ruff fix --- tests/test_init.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index d804a1b88..3216cb3c1 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -689,9 +689,7 @@ async def tinker_switch( for new_state in ["false", "true", "false"]: _LOGGER.info("- Switching %s", new_state) try: - result = await smile.set_switch_state( - dev_id, members, model, new_state - ) + result = await smile.set_switch_state(dev_id, members, model, new_state) if result == convert[new_state]: tinker_switch_passed = True _LOGGER.info(" + tinker_switch worked as intended") From 45b500fb9f03c6bc2199ddec1ea07f2ac3b5b96f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 17:26:24 +0200 Subject: [PATCH 11/47] Add testcase for a blocked switchgroup-change --- .../adam/adam_multiple_devices_per_zone.json | 13 +++++ tests/test_adam.py | 8 +++ .../core.domain_objects.xml | 56 ++++++++++++++++++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/tests/data/adam/adam_multiple_devices_per_zone.json b/tests/data/adam/adam_multiple_devices_per_zone.json index 523f9cfcc..1c0f73634 100644 --- a/tests/data/adam/adam_multiple_devices_per_zone.json +++ b/tests/data/adam/adam_multiple_devices_per_zone.json @@ -567,6 +567,19 @@ "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A08" }, + "e8ef2a01ed3b4139a53bf749204fe6b4": { + "dev_class": "switching", + "members": [ + "02cf28bfec924855854c544690a609ef", + "4a810418d5394b3f82727340b91ba740" + ], + "model": "Switchgroup", + "name": "Test", + "switches": { + "relay": true + }, + "vendor": "Plugwise" + }, "fe799307f1624099878210aa0b9f1475": { "binary_sensors": { "plugwise_notification": true diff --git a/tests/test_adam.py b/tests/test_adam.py index 2378d7f41..1be49423c 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -304,6 +304,14 @@ async def test_connect_adam_multiple_devices_per_zone(self): smile, "675416a629f343c495449970e2ca37b5" ) assert not switch_change + # Test a blocked group-change, both relays are locked. + group_change = await self.tinker_switch( + smile, + "e8ef2a01ed3b4139a53bf749204fe6b4", + ["02cf28bfec924855854c544690a609ef", "4a810418d5394b3f82727340b91ba740"], + ) + assert not group_change + await smile.close_connection() await self.disconnect(server, client) diff --git a/userdata/adam_multiple_devices_per_zone/core.domain_objects.xml b/userdata/adam_multiple_devices_per_zone/core.domain_objects.xml index e38e3a9b1..9a91994ab 100644 --- a/userdata/adam_multiple_devices_per_zone/core.domain_objects.xml +++ b/userdata/adam_multiple_devices_per_zone/core.domain_objects.xml @@ -1425,7 +1425,9 @@ 2020-03-20T17:44:58.716+01:00 - + + + 2020-03-20T17:30:00+01:00 @@ -3146,7 +3148,9 @@ 2020-03-20T17:39:34.219+01:00 - + + + 2020-03-20T17:28:23.547+01:00 @@ -3517,6 +3521,54 @@ + + Test + + switching + 2021-12-23T08:25:07.571+01:00 + 2023-12-22T16:29:14.088+01:00 + + + + + + + + relay + + + + + + + electricity_produced + W + 2023-12-22T16:29:13.997+01:00 + 2023-08-16T23:58:55.515+02:00 + + + 0.00 + + + + electricity_consumed + W + 2023-12-22T16:29:13.997+01:00 + 2023-08-16T23:58:55.515+02:00 + + + 14.81 + + + + + + + false + single + + + From 68ed57ceab80557e75ed9f30d02fe42894390b56 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 17:33:07 +0200 Subject: [PATCH 12/47] Update related entity_items --- tests/test_adam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_adam.py b/tests/test_adam.py index 1be49423c..d1521e5eb 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -288,7 +288,7 @@ async def test_connect_adam_multiple_devices_per_zone(self): assert smile._last_active["82fa13f017d240daa0d0ea1775420f24"] == CV_JESSIE assert smile._last_active["08963fec7c53423ca5680aa4cb502c63"] == BADKAMER_SCHEMA assert smile._last_active["446ac08dd04d4eff8ac57489757b7314"] == BADKAMER_SCHEMA - assert self.entity_items == 370 + assert self.entity_items == 375 assert "af82e4ccf9c548528166d38e560662a4" in self.notifications From e0f35ab9f9daf3c3feb487b2439245e134172785 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 17:36:55 +0200 Subject: [PATCH 13/47] Save updates --- .../adam_multiple_devices_per_zone/data.json | 13 +++++++++++++ .../m_adam_multiple_devices_per_zone/data.json | 13 +++++++++++++ plugwise/smile.py | 1 - .../adam/adam_multiple_devices_per_zone.json | 16 ++++++++-------- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/fixtures/adam_multiple_devices_per_zone/data.json b/fixtures/adam_multiple_devices_per_zone/data.json index d3e13a175..8937cd465 100644 --- a/fixtures/adam_multiple_devices_per_zone/data.json +++ b/fixtures/adam_multiple_devices_per_zone/data.json @@ -540,6 +540,19 @@ "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A11" }, + "e8ef2a01ed3b4139a53bf749204fe6b4": { + "dev_class": "switching", + "members": [ + "02cf28bfec924855854c544690a609ef", + "4a810418d5394b3f82727340b91ba740" + ], + "model": "Switchgroup", + "name": "Test", + "switches": { + "relay": true + }, + "vendor": "Plugwise" + }, "f1fee6043d3642a9b0a65297455f008e": { "available": true, "binary_sensors": { diff --git a/fixtures/m_adam_multiple_devices_per_zone/data.json b/fixtures/m_adam_multiple_devices_per_zone/data.json index 7c38b1b21..06459a117 100644 --- a/fixtures/m_adam_multiple_devices_per_zone/data.json +++ b/fixtures/m_adam_multiple_devices_per_zone/data.json @@ -531,6 +531,19 @@ "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A11" }, + "e8ef2a01ed3b4139a53bf749204fe6b4": { + "dev_class": "switching", + "members": [ + "02cf28bfec924855854c544690a609ef", + "4a810418d5394b3f82727340b91ba740" + ], + "model": "Switchgroup", + "name": "Test", + "switches": { + "relay": true + }, + "vendor": "Plugwise" + }, "f1fee6043d3642a9b0a65297455f008e": { "available": true, "binary_sensors": { diff --git a/plugwise/smile.py b/plugwise/smile.py index 292363a97..e2b20f9e8 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -453,7 +453,6 @@ async def _set_groupswitch_member_state( else: return not req_state - async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" setpoint: float | None = None diff --git a/tests/data/adam/adam_multiple_devices_per_zone.json b/tests/data/adam/adam_multiple_devices_per_zone.json index 1c0f73634..2780afe76 100644 --- a/tests/data/adam/adam_multiple_devices_per_zone.json +++ b/tests/data/adam/adam_multiple_devices_per_zone.json @@ -568,17 +568,17 @@ "zigbee_mac_address": "ABCD012345670A08" }, "e8ef2a01ed3b4139a53bf749204fe6b4": { - "dev_class": "switching", - "members": [ + "dev_class": "switching", + "members": [ "02cf28bfec924855854c544690a609ef", "4a810418d5394b3f82727340b91ba740" - ], - "model": "Switchgroup", - "name": "Test", - "switches": { + ], + "model": "Switchgroup", + "name": "Test", + "switches": { "relay": true - }, - "vendor": "Plugwise" + }, + "vendor": "Plugwise" }, "fe799307f1624099878210aa0b9f1475": { "binary_sensors": { From 75fae4f4920a1f6f9822a93c04d29a0a9c66a938 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 17:39:29 +0200 Subject: [PATCH 14/47] Add pragma-no-cover for legacy --- plugwise/legacy/smile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 62ebaefbc..df932cc12 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -306,7 +306,7 @@ async def _set_groupswitch_member_state( if switched > 0: return req_state else: - return not req_state + return not req_state # pragma: no cover async def set_temperature(self, _: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" @@ -317,7 +317,7 @@ async def set_temperature(self, _: str, items: dict[str, float]) -> None: if setpoint is None: raise PlugwiseError( "Plugwise: failed setting temperature: no valid input provided" - ) # pragma: no cover" + ) # pragma: no cover temperature = str(setpoint) data = ( From 9f48e392e071e17badac7058f70743b702c7b7d9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 17:40:47 +0200 Subject: [PATCH 15/47] Test: clear no longer expected exception --- tests/test_init.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 3216cb3c1..69a9b9d8f 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -696,9 +696,6 @@ async def tinker_switch( else: _LOGGER.info(" + tinker_switch failed unexpectedly") return False - except pw_exceptions.PlugwiseError: - _LOGGER.info(" + locked, not switched as expected") - return False except ( pw_exceptions.ConnectionFailedError ): # leave for-loop at connect-error From a9abbe3dd9ad6cb140a869cf55d890ef88ae184c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 18:11:43 +0200 Subject: [PATCH 16/47] Bump to v1.7.6a0 test-version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a2dd35387..c8d1067fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise" -version = "1.7.5" +version = "1.7.6a0" license = "MIT" description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3." readme = "README.md" From 93b9b2d52abdbf4d2c253f0e8847869a8e7cd127 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 19:29:01 +0200 Subject: [PATCH 17/47] Correct switch state input --- plugwise/legacy/smile.py | 7 ++++--- plugwise/smile.py | 7 ++++--- tests/test_init.py | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index df932cc12..0dae3ab72 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -18,6 +18,7 @@ OFF, REQUIRE_APPLIANCES, RULES, + STATE_ON, GwEntityData, ThermoLoc, ) @@ -241,7 +242,7 @@ async def set_switch_state( For group switches, sets the state for each member in the group separately. For switch-locks, sets the lock state using a different data format. """ - req_state = state == "true" + req_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" switch.func_type = "relay_functionality" @@ -251,7 +252,7 @@ async def set_switch_state( # Handle switch-lock if model == "lock": - state = "false" if state == "off" else "true" + state = "true" if state == STATE_ON else "false" appliance = self._appliances.find(f'appliance[@id="{appl_id}"]') appl_name = appliance.find("name").text appl_type = appliance.find("type").text @@ -295,7 +296,7 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch (relay) within a group of members. """ - req_state = state == "true" + req_state = state == STATE_ON switched = 0 for member in members: if not self.gw_entities[member]["switches"]["lock"]: diff --git a/plugwise/smile.py b/plugwise/smile.py index e2b20f9e8..cff95913c 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -22,6 +22,7 @@ NOTIFICATIONS, OFF, RULES, + STATE_ON, GwEntityData, ThermoLoc, ) @@ -379,7 +380,7 @@ async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: """Set the given State of the relevant Switch.""" - req_state = state == "true" + req_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" switch.device = "relay" @@ -397,7 +398,7 @@ async def set_switch_state( if model == "lock": switch.func = "lock" - state = "false" if state == "off" else "true" + state = "true" if state == STATE_ON else "false" if members is not None: return await self._set_groupswitch_member_state(members, state, switch) @@ -433,7 +434,7 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch within a group of members. """ - req_state = state == "true" + req_state = state == STATE_ON switched = 0 for member in members: locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}' diff --git a/tests/test_init.py b/tests/test_init.py index 69a9b9d8f..b6bf1bd6d 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -684,9 +684,9 @@ async def tinker_switch( """Turn a Switch on and off to test functionality.""" _LOGGER.info("Asserting modifying settings for switch devices:") _LOGGER.info("- Devices (%s):", dev_id) - convert = {"true": True, "false": False} + convert = {"on": True, "off": False} tinker_switch_passed = False - for new_state in ["false", "true", "false"]: + for new_state in ["off", "on", "off"]: _LOGGER.info("- Switching %s", new_state) try: result = await smile.set_switch_state(dev_id, members, model, new_state) From 8a9085c7c46e34a036b6f2e18f2e4cfc5c6c77d2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 19:30:08 +0200 Subject: [PATCH 18/47] Bump to a1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c8d1067fe..c42ea7b5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise" -version = "1.7.6a0" +version = "1.7.6a1" license = "MIT" description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3." readme = "README.md" From 31bc6a4b831df2cd87d225e6cc1ceb74667d0048 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 13 Jun 2025 20:14:19 +0200 Subject: [PATCH 19/47] Improve var-name, correct returns --- plugwise/legacy/smile.py | 16 ++++++++-------- plugwise/smile.py | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 0dae3ab72..915bc2cff 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -242,7 +242,7 @@ async def set_switch_state( For group switches, sets the state for each member in the group separately. For switch-locks, sets the lock state using a different data format. """ - req_state = state == STATE_ON + requested_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" switch.func_type = "relay_functionality" @@ -271,7 +271,7 @@ async def set_switch_state( "" ) await self.call_request(APPLIANCES, method="post", data=data) - return req_state + return requested_state # Handle group of switches data = f"<{switch.func_type}>{state}" @@ -284,10 +284,10 @@ async def set_switch_state( uri = f"{APPLIANCES};id={appl_id}/relay" if model == "relay" and self.gw_entities[appl_id]["switches"]["lock"]: # Don't bother switching a relay when the corresponding lock-state is true - return False + return not requested_state await self.call_request(uri, method="put", data=data) - return req_state + return requested_state async def _set_groupswitch_member_state( self, data: str, members: list[str], state: str, switch: Munch @@ -296,7 +296,7 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch (relay) within a group of members. """ - req_state = state == STATE_ON + requested_state = state == STATE_ON switched = 0 for member in members: if not self.gw_entities[member]["switches"]["lock"]: @@ -305,9 +305,9 @@ async def _set_groupswitch_member_state( switched += 1 if switched > 0: - return req_state - else: - return not req_state # pragma: no cover + return requested_state + + return not requested_state # pragma: no cover async def set_temperature(self, _: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" diff --git a/plugwise/smile.py b/plugwise/smile.py index cff95913c..5b0c2dcae 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -380,7 +380,7 @@ async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: """Set the given State of the relevant Switch.""" - req_state = state == STATE_ON + requested_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" switch.device = "relay" @@ -422,10 +422,10 @@ async def set_switch_state( uri = f"{APPLIANCES};id={appl_id}/{switch.device};id={switch_id}" if model == "relay" and self.gw_entities[appl_id]["switches"]["lock"]: # Don't switch a relay when its corresponding lock-state is true - return False + return not requested_state await self.call_request(uri, method="put", data=data) - return req_state + return requested_state async def _set_groupswitch_member_state( self, members: list[str], state: str, switch: Munch @@ -434,7 +434,7 @@ async def _set_groupswitch_member_state( Set the given State of the relevant Switch within a group of members. """ - req_state = state == STATE_ON + requested_state = state == STATE_ON switched = 0 for member in members: locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}' @@ -450,9 +450,9 @@ async def _set_groupswitch_member_state( switched += 1 if switched > 0: - return req_state - else: - return not req_state + return requested_state + + return not requested_state async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" From bae729709b32a38c0ad71bae1453bedeb18dca72 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 14 Jun 2025 08:01:04 +0200 Subject: [PATCH 20/47] Bump to a2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c42ea7b5a..dbf61458c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise" -version = "1.7.6a1" +version = "1.7.6a2" license = "MIT" description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3." readme = "README.md" From a25ee26e1c41d98ad4e1dbd660908546a69c5628 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 14:35:38 +0200 Subject: [PATCH 21/47] Implement improvements as suggested --- plugwise/legacy/smile.py | 12 +++++++----- plugwise/smile.py | 30 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 915bc2cff..c5b63e274 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -242,6 +242,7 @@ async def set_switch_state( For group switches, sets the state for each member in the group separately. For switch-locks, sets the lock state using a different data format. """ + current_state = self.gw_entities[appl_id]["switches"]["relay"] requested_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" @@ -277,25 +278,26 @@ async def set_switch_state( data = f"<{switch.func_type}>{state}" if members is not None: return await self._set_groupswitch_member_state( - data, members, state, switch + appl_id, data, members, state, switch ) # Handle individual relay switches uri = f"{APPLIANCES};id={appl_id}/relay" - if model == "relay" and self.gw_entities[appl_id]["switches"]["lock"]: + if model == "relay" and self.gw_entities[appl_id]["switches"].get("lock"): # Don't bother switching a relay when the corresponding lock-state is true - return not requested_state + return current_state await self.call_request(uri, method="put", data=data) return requested_state async def _set_groupswitch_member_state( - self, data: str, members: list[str], state: str, switch: Munch + self, appl_id: str, data: str, members: list[str], state: str, switch: Munch ) -> bool: """Helper-function for set_switch_state(). Set the given State of the relevant Switch (relay) within a group of members. """ + current_state = self.gw_entities[appl_id]["switches"]["relay"] requested_state = state == STATE_ON switched = 0 for member in members: @@ -307,7 +309,7 @@ async def _set_groupswitch_member_state( if switched > 0: return requested_state - return not requested_state # pragma: no cover + return current_state # pragma: no cover async def set_temperature(self, _: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" diff --git a/plugwise/smile.py b/plugwise/smile.py index 5b0c2dcae..6c3416a60 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -380,6 +380,7 @@ async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: """Set the given State of the relevant Switch.""" + current_state = self.gw_entities[appl_id]["switches"]["relay"] requested_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" @@ -400,8 +401,16 @@ async def set_switch_state( switch.func = "lock" state = "true" if state == STATE_ON else "false" + data = ( + f"<{switch.func_type}>" + f"<{switch.func}>{state}" + f"" + ) + if members is not None: - return await self._set_groupswitch_member_state(members, state, switch) + return await self._set_groupswitch_member_state( + appl_id, data, members, state, switch + ) locator = f'appliance[@id="{appl_id}"]/{switch.actuator}/{switch.func_type}' found = self._domain_objects.findall(locator) @@ -414,37 +423,28 @@ async def set_switch_state( else: # actuators with a single item like relay_functionality switch_id = item.attrib["id"] - data = ( - f"<{switch.func_type}>" - f"<{switch.func}>{state}" - f"" - ) uri = f"{APPLIANCES};id={appl_id}/{switch.device};id={switch_id}" - if model == "relay" and self.gw_entities[appl_id]["switches"]["lock"]: + if model == "relay" and self.gw_entities[appl_id]["switches"].get("lock"): # Don't switch a relay when its corresponding lock-state is true - return not requested_state + return current_state await self.call_request(uri, method="put", data=data) return requested_state async def _set_groupswitch_member_state( - self, members: list[str], state: str, switch: Munch + self, appl_id: str, data: str, members: list[str], state: str, switch: Munch ) -> bool: """Helper-function for set_switch_state(). Set the given State of the relevant Switch within a group of members. """ + current_state = self.gw_entities[appl_id]["switches"]["relay"] requested_state = state == STATE_ON switched = 0 for member in members: locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}' switch_id = self._domain_objects.find(locator).attrib["id"] uri = f"{APPLIANCES};id={member}/{switch.device};id={switch_id}" - data = ( - f"<{switch.func_type}>" - f"<{switch.func}>{state}" - f"" - ) if not self.gw_entities[member]["switches"].get("lock"): await self.call_request(uri, method="put", data=data) switched += 1 @@ -452,7 +452,7 @@ async def _set_groupswitch_member_state( if switched > 0: return requested_state - return not requested_state + return current_state async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None: """Set the given Temperature on the relevant Thermostat.""" From 889006521a17765cf897b1d02bec6a8522589547 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 14:39:12 +0200 Subject: [PATCH 22/47] Handle no lock-attribute present --- plugwise/smile.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index 6c3416a60..e916e5d5b 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -380,7 +380,7 @@ async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: """Set the given State of the relevant Switch.""" - current_state = self.gw_entities[appl_id]["switches"]["relay"] + current_state = self.gw_entities[appl_id]["switches"][model] requested_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" @@ -425,7 +425,8 @@ async def set_switch_state( uri = f"{APPLIANCES};id={appl_id}/{switch.device};id={switch_id}" if model == "relay" and self.gw_entities[appl_id]["switches"].get("lock"): - # Don't switch a relay when its corresponding lock-state is true + # Don't switch a relay when its corresponding lock-state is true or no + # lock is present. That means the relay can't be controlled by the user. return current_state await self.call_request(uri, method="put", data=data) From 0032a9059c987109b3cc3a61880fff2dd90b0ab2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 14:50:41 +0200 Subject: [PATCH 23/47] Correct testcase --- tests/test_adam.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_adam.py b/tests/test_adam.py index d1521e5eb..4383f0ab6 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -106,10 +106,11 @@ async def test_connect_adam_plus_anna_new(self): smile, "056ee145a816487eaa69243c3280f8bf", model="dhw_cm_switch" ) assert switch_change + # Test relay without lock-attribute switch_change = await self.tinker_switch( - smile, "854f8a9b0e7e425db97f1f110e1ce4b3", model="lock" + smile, "854f8a9b0e7e425db97f1f110e1ce4b3", ) - assert switch_change + assert not switch_change switch_change = await self.tinker_switch( smile, "2568cc4b9c1e401495d4741a5f89bee1" ) From ca3fd2c7a8befd8dedb07b9df0ac4525e8312036 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 14:56:28 +0200 Subject: [PATCH 24/47] Debug --- plugwise/smile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index e916e5d5b..63e8cfe75 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -16,6 +16,7 @@ DOMAIN_OBJECTS, GATEWAY_REBOOT, LOCATIONS, + LOGGER, MAX_SETPOINT, MIN_SETPOINT, NONE, @@ -424,7 +425,9 @@ async def set_switch_state( switch_id = item.attrib["id"] uri = f"{APPLIANCES};id={appl_id}/{switch.device};id={switch_id}" - if model == "relay" and self.gw_entities[appl_id]["switches"].get("lock"): + lock_blocked = self.gw_entities[appl_id]["switches"].get("lock") + LOGGER.debug("HOI lock_blocked=%s", lock_blocked) + if model == "relay" and lock_blocked: # Don't switch a relay when its corresponding lock-state is true or no # lock is present. That means the relay can't be controlled by the user. return current_state From 806752adf4e8c893ca9a71e01bd06a650f377036 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 15:03:56 +0200 Subject: [PATCH 25/47] Try --- plugwise/smile.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index 63e8cfe75..edcaa9452 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -425,12 +425,13 @@ async def set_switch_state( switch_id = item.attrib["id"] uri = f"{APPLIANCES};id={appl_id}/{switch.device};id={switch_id}" - lock_blocked = self.gw_entities[appl_id]["switches"].get("lock") - LOGGER.debug("HOI lock_blocked=%s", lock_blocked) - if model == "relay" and lock_blocked: + if model == "relay": + lock_blocked = self.gw_entities[appl_id]["switches"].get("lock") + LOGGER.debug("HOI lock_blocked=%s", lock_blocked) + if lock_blocked or lock_blocked is None: # Don't switch a relay when its corresponding lock-state is true or no # lock is present. That means the relay can't be controlled by the user. - return current_state + return current_state await self.call_request(uri, method="put", data=data) return requested_state From e4adde60e78e6a680bcbc691335b80da4bd58fff Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 15:05:36 +0200 Subject: [PATCH 26/47] Ruff fixes --- plugwise/smile.py | 4 ++-- tests/test_adam.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index edcaa9452..e441d16c8 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -429,8 +429,8 @@ async def set_switch_state( lock_blocked = self.gw_entities[appl_id]["switches"].get("lock") LOGGER.debug("HOI lock_blocked=%s", lock_blocked) if lock_blocked or lock_blocked is None: - # Don't switch a relay when its corresponding lock-state is true or no - # lock is present. That means the relay can't be controlled by the user. + # Don't switch a relay when its corresponding lock-state is true or no + # lock is present. That means the relay can't be controlled by the user. return current_state await self.call_request(uri, method="put", data=data) diff --git a/tests/test_adam.py b/tests/test_adam.py index 4383f0ab6..8380b656f 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -108,7 +108,8 @@ async def test_connect_adam_plus_anna_new(self): assert switch_change # Test relay without lock-attribute switch_change = await self.tinker_switch( - smile, "854f8a9b0e7e425db97f1f110e1ce4b3", + smile, + "854f8a9b0e7e425db97f1f110e1ce4b3", ) assert not switch_change switch_change = await self.tinker_switch( From 4cf708ec35987073fb9bf1d094b6d4f95fd3d405 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 15:06:17 +0200 Subject: [PATCH 27/47] Clean up --- plugwise/smile.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index e441d16c8..7849ecd06 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -16,7 +16,6 @@ DOMAIN_OBJECTS, GATEWAY_REBOOT, LOCATIONS, - LOGGER, MAX_SETPOINT, MIN_SETPOINT, NONE, @@ -427,7 +426,6 @@ async def set_switch_state( uri = f"{APPLIANCES};id={appl_id}/{switch.device};id={switch_id}" if model == "relay": lock_blocked = self.gw_entities[appl_id]["switches"].get("lock") - LOGGER.debug("HOI lock_blocked=%s", lock_blocked) if lock_blocked or lock_blocked is None: # Don't switch a relay when its corresponding lock-state is true or no # lock is present. That means the relay can't be controlled by the user. From e4c5825e99f42f54e3d47f6c8d505d21419e7e85 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 15:09:47 +0200 Subject: [PATCH 28/47] Add testcase covering switching of lock --- tests/test_adam.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_adam.py b/tests/test_adam.py index 8380b656f..5e2210f7a 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -116,6 +116,10 @@ async def test_connect_adam_plus_anna_new(self): smile, "2568cc4b9c1e401495d4741a5f89bee1" ) assert not switch_change + switch_change = await self.tinker_switch( + smile, "2568cc4b9c1e401495d4741a5f89bee1", model="lock", + ) + assert switch_change tinkered = await self.tinker_gateway_mode(smile) assert not tinkered From a7dc2118b1cad0740ff6285f80b616c100187e8c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 15:10:27 +0200 Subject: [PATCH 29/47] Ruff fix --- tests/test_adam.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_adam.py b/tests/test_adam.py index 5e2210f7a..53567c177 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -117,7 +117,9 @@ async def test_connect_adam_plus_anna_new(self): ) assert not switch_change switch_change = await self.tinker_switch( - smile, "2568cc4b9c1e401495d4741a5f89bee1", model="lock", + smile, + "2568cc4b9c1e401495d4741a5f89bee1", + model="lock", ) assert switch_change From 8ae7acf2b7e37b7bb281620a9bafd1a591b4fd24 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 15:12:23 +0200 Subject: [PATCH 30/47] For legacy lock is always present --- plugwise/legacy/smile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index c5b63e274..b7fa7a571 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -283,7 +283,7 @@ async def set_switch_state( # Handle individual relay switches uri = f"{APPLIANCES};id={appl_id}/relay" - if model == "relay" and self.gw_entities[appl_id]["switches"].get("lock"): + if model == "relay" and self.gw_entities[appl_id]["switches"]["lock"]: # Don't bother switching a relay when the corresponding lock-state is true return current_state From c6438ba7e346108632f3ffbcbe4fd26ab61c432c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 15:18:40 +0200 Subject: [PATCH 31/47] Mypy fix --- plugwise/smile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index 7849ecd06..d9814f455 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable import datetime as dt -from typing import Any +from typing import Any, cast from plugwise.constants import ( ADAM, @@ -24,6 +24,7 @@ RULES, STATE_ON, GwEntityData, + SwitchType, ThermoLoc, ) from plugwise.data import SmileData @@ -380,7 +381,8 @@ async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: """Set the given State of the relevant Switch.""" - current_state = self.gw_entities[appl_id]["switches"][model] + model_type = cast(SwitchType, model) + current_state = self.gw_entities[appl_id]["switches"][model_type] requested_state = state == STATE_ON switch = Munch() switch.actuator = "actuator_functionalities" From 9687d4e54a75058a93899caa79ac79d5537c3f22 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 15 Jun 2025 15:25:44 +0200 Subject: [PATCH 32/47] Doc-string updates --- plugwise/legacy/smile.py | 2 ++ plugwise/smile.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index b7fa7a571..14277589d 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -241,6 +241,7 @@ async def set_switch_state( For individual switches, sets the state directly. For group switches, sets the state for each member in the group separately. For switch-locks, sets the lock state using a different data format. + Return the requested state when succesful, the current state otherwise. """ current_state = self.gw_entities[appl_id]["switches"]["relay"] requested_state = state == STATE_ON @@ -296,6 +297,7 @@ async def _set_groupswitch_member_state( """Helper-function for set_switch_state(). Set the given State of the relevant Switch (relay) within a group of members. + Return the requested state when at least one requested change was succesful, the current state otherwise. """ current_state = self.gw_entities[appl_id]["switches"]["relay"] requested_state = state == STATE_ON diff --git a/plugwise/smile.py b/plugwise/smile.py index d9814f455..875e8e6d8 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -380,7 +380,10 @@ def determine_contexts( async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: - """Set the given State of the relevant Switch.""" + """Set the given State of the relevant Switch. + + Return the requested state when succesful, the current state otherwise. + """ model_type = cast(SwitchType, model) current_state = self.gw_entities[appl_id]["switches"][model_type] requested_state = state == STATE_ON @@ -442,6 +445,7 @@ async def _set_groupswitch_member_state( """Helper-function for set_switch_state(). Set the given State of the relevant Switch within a group of members. + Return the requested state when at least one requested change was succesful, the current state otherwise. """ current_state = self.gw_entities[appl_id]["switches"]["relay"] requested_state = state == STATE_ON From aae419148c6bef9e050b06fe4659e2b4c3536f4e Mon Sep 17 00:00:00 2001 From: autoruff Date: Sun, 15 Jun 2025 13:37:01 +0000 Subject: [PATCH 33/47] fixup: relay-sw-improve Python code fixed using ruff --- plugwise/smile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index 875e8e6d8..792a64719 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -381,7 +381,7 @@ async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: """Set the given State of the relevant Switch. - + Return the requested state when succesful, the current state otherwise. """ model_type = cast(SwitchType, model) From a10fdaee22e048bb83e6a1b296a0ee638adb3d40 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 15:34:00 +0200 Subject: [PATCH 34/47] Bump to a3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dbf61458c..c6b2297ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise" -version = "1.7.6a2" +version = "1.7.6a3" license = "MIT" description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3." readme = "README.md" From aa3803558b6210d8139e4a83b1178b738858a752 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 15:43:10 +0200 Subject: [PATCH 35/47] Improve docstrings --- plugwise/__init__.py | 8 +++++++- plugwise/legacy/smile.py | 4 ++-- plugwise/smile.py | 9 ++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index 8cce15321..95add6d1e 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -399,7 +399,13 @@ async def set_temperature_offset(self, dev_id: str, offset: float) -> None: async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: - """Set the given State of the relevant Switch.""" + """Set the given State of the relevant Switch. + + Return the result: + - True when switched to state on, + - False when switched to state off, + - the unchanged state when the switch is for instance locked. + """ try: return await self._smile_api.set_switch_state( appl_id, members, model, state diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 14277589d..d3b410d16 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -296,8 +296,8 @@ async def _set_groupswitch_member_state( ) -> bool: """Helper-function for set_switch_state(). - Set the given State of the relevant Switch (relay) within a group of members. - Return the requested state when at least one requested change was succesful, the current state otherwise. + Set the requested state of the relevant switch within a group of switches. + Return the current group-state when none of the switches has changed its state, the requested state otherwise. """ current_state = self.gw_entities[appl_id]["switches"]["relay"] requested_state = state == STATE_ON diff --git a/plugwise/smile.py b/plugwise/smile.py index 792a64719..b2e47a3ac 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -380,8 +380,11 @@ def determine_contexts( async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: - """Set the given State of the relevant Switch. + """Set the given state of the relevant Switch. + For individual switches, sets the state directly. + For group switches, sets the state for each member in the group separately. + For switch-locks, sets the lock state using a different data format. Return the requested state when succesful, the current state otherwise. """ model_type = cast(SwitchType, model) @@ -444,8 +447,8 @@ async def _set_groupswitch_member_state( ) -> bool: """Helper-function for set_switch_state(). - Set the given State of the relevant Switch within a group of members. - Return the requested state when at least one requested change was succesful, the current state otherwise. + Set the requested state of the relevant switch within a group of switches. + Return the current group-state when none of the switches has changed its state, the requested state otherwise. """ current_state = self.gw_entities[appl_id]["switches"]["relay"] requested_state = state == STATE_ON From c511870776fa4e65a8396683126d90e2fdacc620 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 16:04:42 +0200 Subject: [PATCH 36/47] Add input-check as suggested --- plugwise/__init__.py | 3 +++ plugwise/constants.py | 1 + 2 files changed, 4 insertions(+) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index 95add6d1e..4aa0e6f85 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -406,6 +406,9 @@ async def set_switch_state( - False when switched to state off, - the unchanged state when the switch is for instance locked. """ + if state not in (STATE_ON, STATE_OFF): + raise PlugwiseError("Invalid state supplied to set_switch_state") + try: return await self._smile_api.set_switch_state( appl_id, members, model, state diff --git a/plugwise/constants.py b/plugwise/constants.py index 5d306e9a3..4e1becd56 100644 --- a/plugwise/constants.py +++ b/plugwise/constants.py @@ -23,6 +23,7 @@ PRESET_AWAY: Final = "away" PRESSURE_BAR: Final = "bar" SIGNAL_STRENGTH_DECIBELS_MILLIWATT: Final = "dBm" +STATE_OFF: Final = "off" STATE_ON: Final = "on" TEMP_CELSIUS: Final = "°C" TEMP_KELVIN: Final = "°K" From b06106a3794827267a63ec51c68129fc818ec591 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 16:17:42 +0200 Subject: [PATCH 37/47] Move preset input-checking to __init__.py, use constants --- plugwise/__init__.py | 4 ++++ plugwise/legacy/smile.py | 10 +++------- plugwise/smile.py | 14 +++++--------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index 4aa0e6f85..ff2e3347d 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -359,6 +359,10 @@ async def set_schedule_state( async def set_preset(self, loc_id: str, preset: str) -> None: """Set the given Preset on the relevant Thermostat.""" + if (presets := self._presets(loc_id)) is None: + raise PlugwiseError("Plugwise: no presets available.") # pragma: no cover + if preset not in list(presets): + raise PlugwiseError("Plugwise: invalid preset.") try: await self._smile_api.set_preset(loc_id, preset) except ConnectionFailedError as exc: diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index d3b410d16..e5dea9969 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -18,6 +18,7 @@ OFF, REQUIRE_APPLIANCES, RULES, + STATE_OFF, STATE_ON, GwEntityData, ThermoLoc, @@ -168,11 +169,6 @@ async def set_offset(self, dev_id: str, offset: float) -> None: async def set_preset(self, _: str, preset: str) -> None: """Set the given Preset on the relevant Thermostat - from DOMAIN_OBJECTS.""" - if (presets := self._presets()) is None: - raise PlugwiseError("Plugwise: no presets available.") # pragma: no cover - if preset not in list(presets): - raise PlugwiseError("Plugwise: invalid preset.") - locator = f'rule/directives/when/then[@icon="{preset}"].../.../...' rule_id = self._domain_objects.find(locator).attrib["id"] data = f"true" @@ -196,7 +192,7 @@ async def set_schedule_state( 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 (STATE_OFF, STATE_ON): raise PlugwiseError("Plugwise: invalid schedule state.") # Handle no schedule-name / Off-schedule provided @@ -215,7 +211,7 @@ async def set_schedule_state( ) # pragma: no cover new_state = "false" - if state == "on": + if state == STATE_ON: new_state = "true" locator = f'.//*[@id="{schedule_rule_id}"]/template' diff --git a/plugwise/smile.py b/plugwise/smile.py index b2e47a3ac..705c644da 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -22,6 +22,7 @@ NOTIFICATIONS, OFF, RULES, + STATE_OFF, STATE_ON, GwEntityData, SwitchType, @@ -201,11 +202,6 @@ async def set_offset(self, dev_id: str, offset: float) -> None: async def set_preset(self, loc_id: str, preset: str) -> None: """Set the given Preset on the relevant Thermostat - from LOCATIONS.""" - if (presets := self._presets(loc_id)) is None: - raise PlugwiseError("Plugwise: no presets available.") # pragma: no cover - if preset not in list(presets): - raise PlugwiseError("Plugwise: invalid preset.") - current_location = self._domain_objects.find(f'location[@id="{loc_id}"]') location_name = current_location.find("name").text location_type = current_location.find("type").text @@ -311,12 +307,12 @@ 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 (STATE_OFF, STATE_ON): raise PlugwiseError("Plugwise: invalid schedule state.") # Translate selection of Off-schedule-option to disabling the active schedule if name == OFF: - new_state = "off" + new_state = STATE_OFF # Handle no schedule-name / Off-schedule provided if name is None or name == OFF: @@ -369,10 +365,10 @@ def determine_contexts( subject = f'' subject = etree.fromstring(subject) - if state == "off": + if state == STATE_OFF: self._last_active[loc_id] = name contexts.remove(subject) - if state == "on": + if state == STATE_ON: contexts.append(subject) return str(etree.tostring(contexts, encoding="unicode").rstrip()) From 9e80cfb75a047636335a3f8b204ca1a879f31669 Mon Sep 17 00:00:00 2001 From: autoruff Date: Mon, 23 Jun 2025 14:19:31 +0000 Subject: [PATCH 38/47] fixup: relay-sw-improve Python code fixed using ruff --- plugwise/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index ff2e3347d..4aa80c3f0 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -404,7 +404,7 @@ async def set_switch_state( self, appl_id: str, members: list[str] | None, model: str, state: str ) -> bool: """Set the given State of the relevant Switch. - + Return the result: - True when switched to state on, - False when switched to state off, From e602444a316dbbf38a28b30d75cfc0c43e211d37 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 16:23:02 +0200 Subject: [PATCH 39/47] Add missing constant imports --- plugwise/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index 4aa80c3f0..e8069633b 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -15,6 +15,8 @@ MODULES, NONE, SMILES, + STATE_OFF, + STATE_ON, STATUS, SYSTEM, GwEntityData, @@ -410,7 +412,7 @@ async def set_switch_state( - False when switched to state off, - the unchanged state when the switch is for instance locked. """ - if state not in (STATE_ON, STATE_OFF): + if state not in (STATE_OFF, STATE_ON): raise PlugwiseError("Invalid state supplied to set_switch_state") try: From dca583513f69c01c4ac6a91221d7c5846efdfbc3 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 16:31:25 +0200 Subject: [PATCH 40/47] Revert move preset input-checking --- plugwise/__init__.py | 4 ---- plugwise/legacy/smile.py | 5 +++++ plugwise/smile.py | 5 +++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index e8069633b..9c56faa6a 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -361,10 +361,6 @@ async def set_schedule_state( async def set_preset(self, loc_id: str, preset: str) -> None: """Set the given Preset on the relevant Thermostat.""" - if (presets := self._presets(loc_id)) is None: - raise PlugwiseError("Plugwise: no presets available.") # pragma: no cover - if preset not in list(presets): - raise PlugwiseError("Plugwise: invalid preset.") try: await self._smile_api.set_preset(loc_id, preset) except ConnectionFailedError as exc: diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index e5dea9969..fc4f3a2c5 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -169,6 +169,11 @@ async def set_offset(self, dev_id: str, offset: float) -> None: async def set_preset(self, _: str, preset: str) -> None: """Set the given Preset on the relevant Thermostat - from DOMAIN_OBJECTS.""" + if (presets := self._presets()) is None: + raise PlugwiseError("Plugwise: no presets available.") # pragma: no cover + if preset not in list(presets): + raise PlugwiseError("Plugwise: invalid preset.") + locator = f'rule/directives/when/then[@icon="{preset}"].../.../...' rule_id = self._domain_objects.find(locator).attrib["id"] data = f"true" diff --git a/plugwise/smile.py b/plugwise/smile.py index 705c644da..848205467 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -202,6 +202,11 @@ async def set_offset(self, dev_id: str, offset: float) -> None: async def set_preset(self, loc_id: str, preset: str) -> None: """Set the given Preset on the relevant Thermostat - from LOCATIONS.""" + if (presets := self._presets(loc_id)) is None: + raise PlugwiseError("Plugwise: no presets available.") # pragma: no cover + if preset not in list(presets): + raise PlugwiseError("Plugwise: invalid preset.") + current_location = self._domain_objects.find(f'location[@id="{loc_id}"]') location_name = current_location.find("name").text location_type = current_location.find("type").text From 05d53003a72e0bf5f3de6af07b23435b8a8d789f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 18:08:13 +0200 Subject: [PATCH 41/47] Add tinker_switch_bad_input() test-function --- tests/test_init.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_init.py b/tests/test_init.py index b6bf1bd6d..c0c9143bf 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -708,6 +708,19 @@ async def tinker_switch( return tinker_switch_passed + @pytest.mark.asyncio + async def tinker_switch_bad_input( + self, smile, dev_id=None, members=None, model="relay", unhappy=False + ): + _LOGGER.info("Test entering bad input set_switch_state:") + _LOGGER.info("- Devices (%s):", dev_id) + new_state = "false" + try: + result = await smile.set_switch_state(dev_id, members, model, new_state) + except pw_exceptions.PlugwiseError: + _LOGGER.info(" + failed input-check as expected") + return True # test is pass! + @pytest.mark.asyncio async def tinker_thermostat_temp( self, smile, loc_id, block_cooling=False, fail_cooling=False, unhappy=False From 089e156afdbb4d0a8b023021309e6def7dbcb07d Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 18:09:33 +0200 Subject: [PATCH 42/47] And implement --- tests/test_adam.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_adam.py b/tests/test_adam.py index 53567c177..b38bb6937 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -123,6 +123,11 @@ async def test_connect_adam_plus_anna_new(self): ) assert switch_change + assert self.tinker_switch_bad_input( + smile, + "854f8a9b0e7e425db97f1f110e1ce4b3", + ) + tinkered = await self.tinker_gateway_mode(smile) assert not tinkered From 2b0bf399c9fc098be30d8053699f020be0bd6f64 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 18:12:54 +0200 Subject: [PATCH 43/47] Fixes --- tests/test_adam.py | 4 ++-- tests/test_init.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_adam.py b/tests/test_adam.py index b38bb6937..96b015ce5 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -123,9 +123,9 @@ async def test_connect_adam_plus_anna_new(self): ) assert switch_change - assert self.tinker_switch_bad_input( + assert await self.tinker_switch_bad_input( smile, - "854f8a9b0e7e425db97f1f110e1ce4b3", + "854f8a9b0e7e425db97f1f110e1ce4b3", ) tinkered = await self.tinker_gateway_mode(smile) diff --git a/tests/test_init.py b/tests/test_init.py index c0c9143bf..c80468ec6 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -712,11 +712,12 @@ async def tinker_switch( async def tinker_switch_bad_input( self, smile, dev_id=None, members=None, model="relay", unhappy=False ): + """Enter a wrong state as input to toggle a Switch.""" _LOGGER.info("Test entering bad input set_switch_state:") _LOGGER.info("- Devices (%s):", dev_id) new_state = "false" try: - result = await smile.set_switch_state(dev_id, members, model, new_state) + await smile.set_switch_state(dev_id, members, model, new_state) except pw_exceptions.PlugwiseError: _LOGGER.info(" + failed input-check as expected") return True # test is pass! From 9246b5d61ff43e4e44efe72d9f1c58679dc0b40c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 19:54:50 +0200 Subject: [PATCH 44/47] Improve group-switch logic for Adam --- plugwise/smile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index 848205467..0947401d4 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -458,7 +458,9 @@ async def _set_groupswitch_member_state( locator = f'appliance[@id="{member}"]/{switch.actuator}/{switch.func_type}' switch_id = self._domain_objects.find(locator).attrib["id"] uri = f"{APPLIANCES};id={member}/{switch.device};id={switch_id}" - if not self.gw_entities[member]["switches"].get("lock"): + lock_blocked = self.gw_entities[member]["switches"].get("lock") + # Assume Plugs under Plugwise control are not part of a group + if lock_blocked is not None and not lock_blocked: await self.call_request(uri, method="put", data=data) switched += 1 From 73cf3eaf1e6317e9b411058a7c4f85e40b3cb6c7 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 20:04:45 +0200 Subject: [PATCH 45/47] Update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5e02bd4d..614402d4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Changelog -## Ongoing +## v1.7.6 - Maintenance chores (mostly reworking Github CI Actions) backporting from efforts on Python Plugwise [USB: 264](https://github.com/plugwise/python-plugwise-usb/pull/264) after porting our progress using [USB: 263](https://github.com/plugwise/python-plugwise-usb/pull/263) +- Don't raise an error when a locked switch is being toggled, and other switch-related improvements via [755](https://github.com/plugwise/python-plugwise/pull/755) ## v1.7.5 From 8b1fda14690222711132a1dbea412b04be804a78 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 20:09:09 +0200 Subject: [PATCH 46/47] Bump to v1.7.6 release-version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c6b2297ff..ede4e456a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise" -version = "1.7.6a3" +version = "1.7.6" license = "MIT" description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3." readme = "README.md" From da059006bf46238e69ac438731326844db7e0c29 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 23 Jun 2025 20:17:59 +0200 Subject: [PATCH 47/47] Use #issue --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 614402d4d..5d47c3971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ ## v1.7.6 -- Maintenance chores (mostly reworking Github CI Actions) backporting from efforts on Python Plugwise [USB: 264](https://github.com/plugwise/python-plugwise-usb/pull/264) after porting our progress using [USB: 263](https://github.com/plugwise/python-plugwise-usb/pull/263) -- Don't raise an error when a locked switch is being toggled, and other switch-related improvements via [755](https://github.com/plugwise/python-plugwise/pull/755) +- Maintenance chores (mostly reworking Github CI Actions) backporting from efforts on Python Plugwise [USB: #264](https://github.com/plugwise/python-plugwise-usb/pull/264) after porting our progress using [USB: #263](https://github.com/plugwise/python-plugwise-usb/pull/263) +- Don't raise an error when a locked switch is being toggled, and other switch-related improvements via [#755](https://github.com/plugwise/python-plugwise/pull/755) ## v1.7.5