From 0fd19786934b4bee716b69642d4564bf42e08df2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 08:10:49 +0200 Subject: [PATCH 01/94] Circle: add relay-lock --- plugwise_usb/nodes/circle.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index b760353ce..231a419ec 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -84,6 +84,7 @@ def __init__( super().__init__(mac, address, controller, loaded_callback) # Relay + self._relay_lock = False self._relay_state: RelayState = RelayState() self._relay_config: RelayConfig = RelayConfig() @@ -175,6 +176,15 @@ async def relay_init_on(self) -> None: """Switch relay on.""" await self._relay_init_set(True) + @property + def relay_lock(self) -> bool: + """State of the relay lock.""" + return self._relay_lock + + def set_relay_lock(self, state: bool) -> None: + """Set the state of the relay-lock.""" + self._relay_lock = state + # endregion async def calibration_update(self) -> bool: @@ -628,6 +638,10 @@ async def set_relay(self, state: bool) -> bool: raise FeatureError( f"Changing state of relay is not supported for node {self.mac}" ) + + if self._relay_lock: + raise NodeError("Changing state of relay failed, it is locked") + _LOGGER.debug("set_relay() start") request = CircleRelaySwitchRequest(self._send, self._mac_in_bytes, state) response = await request.send() From 31fca5c0bccf4fbb9a0b9135b5d47af92f965a31 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 08:39:39 +0200 Subject: [PATCH 02/94] Test relay-locking --- tests/test_usb.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_usb.py b/tests/test_usb.py index 6f1de88f2..f96c34d08 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -788,6 +788,14 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No assert not await self.test_relay_state_off assert not stick.nodes["0098765432101234"].relay + # Test blocked async switching due to relay-lock active + stick.nodes["0098765432101234"].set_relay_lock(True) + with pytest.raises(pw_exceptions.NodeError): + await stick.nodes["0098765432101234"].set_relay(True) + assert not stick.nodes["0098765432101234"].relay + # Make sure to turn lock off for further testing + stick.nodes["0098765432101234"].set_relay_lock(False) + # Test async switching back from off to on self.test_relay_state_on = asyncio.Future() assert await stick.nodes["0098765432101234"].set_relay(True) From 7a9659e8a05731a0b8be111d5cdb5516e226b494 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 08:40:26 +0200 Subject: [PATCH 03/94] Bump to v0.43.0a0 test-version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ad51ec355..e8947fbf1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.42.1" +version = "0.43.0a0" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From d3b5268358939564c351b7d430b0c9ac776a11f9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 08:41:36 +0200 Subject: [PATCH 04/94] Back to limited test-output --- scripts/tests_and_coverage.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/tests_and_coverage.sh b/scripts/tests_and_coverage.sh index 7e62cab10..884a5221b 100755 --- a/scripts/tests_and_coverage.sh +++ b/scripts/tests_and_coverage.sh @@ -23,8 +23,7 @@ set +u if [ -z "${GITHUB_ACTIONS}" ] || [ "$1" == "test_and_coverage" ] ; then # Python tests (rerun with debug if failures) - # PYTHONPATH=$(pwd) pytest -qx tests/ --cov='.' --no-cov-on-fail --cov-report term-missing || - PYTHONPATH=$(pwd) pytest -xrpP --log-level debug tests/ + PYTHONPATH=$(pwd) pytest -qx tests/ --cov='.' --no-cov-on-fail --cov-report term-missing || PYTHONPATH=$(pwd) pytest -xrpP --log-level debug tests/ fi if [ -z "${GITHUB_ACTIONS}" ] || [ "$1" == "linting" ] ; then From 6e16b65ce869d3342bc26c72f2708db0348cb234 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 10:52:46 +0200 Subject: [PATCH 05/94] Add RELAY_LOCK NodeFeature --- plugwise_usb/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugwise_usb/api.py b/plugwise_usb/api.py index ce9cdbf38..632df3524 100644 --- a/plugwise_usb/api.py +++ b/plugwise_usb/api.py @@ -50,6 +50,7 @@ class NodeFeature(str, Enum): POWER = "power" RELAY = "relay" RELAY_INIT = "relay_init" + RELAY_LOCK = "relay_lock" SWITCH = "switch" TEMPERATURE = "temperature" From 67b314dae62acc48ce53ae318859ca85c0dd4509 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 11:01:05 +0200 Subject: [PATCH 06/94] Update api with set_relay_state() --- plugwise_usb/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugwise_usb/api.py b/plugwise_usb/api.py index 632df3524..b3d6ef615 100644 --- a/plugwise_usb/api.py +++ b/plugwise_usb/api.py @@ -421,6 +421,9 @@ async def set_relay(self, state: bool) -> bool: """ + async def set_relay_lock(self, state: bool) -> bool: + """Change the state of the relay-lock.""" + # endregion # region configuration properties From 09dbb3ef0beb1634216be2119f26df9f3b54a08c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 11:08:07 +0200 Subject: [PATCH 07/94] Implement NodeFeature.RELAY_LOCK --- plugwise_usb/nodes/circle.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 231a419ec..96a701a96 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -754,6 +754,7 @@ async def load(self) -> bool: ( NodeFeature.RELAY, NodeFeature.RELAY_INIT, + NodeFeature.RELAY_LOCK, NodeFeature.ENERGY, NodeFeature.POWER, ), @@ -792,6 +793,7 @@ async def load(self) -> bool: ( NodeFeature.RELAY, NodeFeature.RELAY_INIT, + NodeFeature.RELAY_LOCK, NodeFeature.ENERGY, NodeFeature.POWER, ), @@ -1112,6 +1114,8 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any self._mac_in_str, states[feature], ) + elif feature == NodeFeature.RELAY_LOCK: + states[feature] = self._relay_lock elif feature == NodeFeature.RELAY_INIT: states[feature] = self._relay_config elif feature == NodeFeature.POWER: From c606ce52efeb9fcf4de5ef116d206121b15ac2d5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 12:04:03 +0200 Subject: [PATCH 08/94] Set NodeFeature.RELAY_LOCK as well. --- plugwise_usb/nodes/circle.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 96a701a96..8e20ff1a6 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -182,8 +182,11 @@ def relay_lock(self) -> bool: return self._relay_lock def set_relay_lock(self, state: bool) -> None: - """Set the state of the relay-lock.""" - self._relay_lock = state + """Set the state of the relay-lock.""" + self._relay_lock = state + await self.publish_feature_update_to_subscribers( + NodeFeature.RELAY_LOCK, state + ) # endregion From 6d87d0207dd7963ce4a8eea4eb1fdae62716aed3 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 12:07:38 +0200 Subject: [PATCH 09/94] Fix to async --- plugwise_usb/nodes/circle.py | 2 +- tests/test_usb.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 8e20ff1a6..5684d3269 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -181,7 +181,7 @@ def relay_lock(self) -> bool: """State of the relay lock.""" return self._relay_lock - def set_relay_lock(self, state: bool) -> None: + async def set_relay_lock(self, state: bool) -> None: """Set the state of the relay-lock.""" self._relay_lock = state await self.publish_feature_update_to_subscribers( diff --git a/tests/test_usb.py b/tests/test_usb.py index f96c34d08..ac4865c53 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -789,12 +789,12 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No assert not stick.nodes["0098765432101234"].relay # Test blocked async switching due to relay-lock active - stick.nodes["0098765432101234"].set_relay_lock(True) + await stick.nodes["0098765432101234"].set_relay_lock(True) with pytest.raises(pw_exceptions.NodeError): await stick.nodes["0098765432101234"].set_relay(True) assert not stick.nodes["0098765432101234"].relay # Make sure to turn lock off for further testing - stick.nodes["0098765432101234"].set_relay_lock(False) + await stick.nodes["0098765432101234"].set_relay_lock(False) # Test async switching back from off to on self.test_relay_state_on = asyncio.Future() From c196796bce5a8acb83cfbf6146ec2e58e843a52c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 17:43:14 +0200 Subject: [PATCH 10/94] Add more test-asserts --- tests/test_usb.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index ac4865c53..402729cb9 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -779,7 +779,7 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No unsub_relay = stick.nodes["0098765432101234"].subscribe_to_feature_update( node_feature_callback=self.node_relay_state, - features=(pw_api.NodeFeature.RELAY,), + features=(pw_api.NodeFeature.RELAY, pw_api.NodeFeature.RELAY_LOCK,), ) # Test async switching back from on to off @@ -790,11 +790,13 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No # Test blocked async switching due to relay-lock active await stick.nodes["0098765432101234"].set_relay_lock(True) + assert stick.nodes["0098765432101234"].relay_lock with pytest.raises(pw_exceptions.NodeError): await stick.nodes["0098765432101234"].set_relay(True) assert not stick.nodes["0098765432101234"].relay # Make sure to turn lock off for further testing await stick.nodes["0098765432101234"].set_relay_lock(False) + assert not stick.nodes["0098765432101234"].relay_lock # Test async switching back from off to on self.test_relay_state_on = asyncio.Future() @@ -2445,6 +2447,7 @@ async def test_node_discovery_and_load( pw_api.NodeFeature.PING, pw_api.NodeFeature.INFO, pw_api.NodeFeature.RELAY, + pw_api.NodeFeature.RELAY_LOCK, ) ) @@ -2487,6 +2490,7 @@ async def test_node_discovery_and_load( pw_api.NodeFeature.INFO, pw_api.NodeFeature.PING, pw_api.NodeFeature.RELAY, + pw_api.NodeFeature.RELAY_LOCK, pw_api.NodeFeature.ENERGY, pw_api.NodeFeature.POWER, ) @@ -2507,11 +2511,17 @@ async def test_node_discovery_and_load( assert state[pw_api.NodeFeature.INFO].version == "070073" assert state[pw_api.NodeFeature.RELAY].state + assert state[pw_api.NodeFeature.RELAY_LOCK] # Check 1111111111111111 get_state_timestamp = dt.now(UTC).replace(minute=0, second=0, microsecond=0) state = await stick.nodes["1111111111111111"].get_state( - (pw_api.NodeFeature.PING, pw_api.NodeFeature.INFO, pw_api.NodeFeature.RELAY) + ( + pw_api.NodeFeature.PING, + pw_api.NodeFeature.INFO, + pw_api.NodeFeature.RELAY, + pw_api.NodeFeature.RELAY_LOCK, + ) ) assert state[pw_api.NodeFeature.INFO].mac == "1111111111111111" @@ -2531,12 +2541,15 @@ async def test_node_discovery_and_load( pw_api.NodeFeature.INFO, pw_api.NodeFeature.PING, pw_api.NodeFeature.RELAY, + pw_api.NodeFeature.RELAY_LOCK, pw_api.NodeFeature.ENERGY, pw_api.NodeFeature.POWER, ) ) assert state[pw_api.NodeFeature.AVAILABLE].state assert state[pw_api.NodeFeature.RELAY].state + assert state[pw_api.NodeFeature.RELAY_LOCK] + # region Scan self.test_node_awake = asyncio.Future() From 1f794d08417969af43ff2a93350bbe47ac7fd4ac Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 18:00:13 +0200 Subject: [PATCH 11/94] Try --- tests/test_usb.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index 402729cb9..9b5a3794a 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -710,11 +710,12 @@ async def node_relay_state( state: pw_api.RelayState, # type: ignore[name-defined] ) -> None: """Handle relay event callback.""" - if feature == pw_api.NodeFeature.RELAY: - if state.state: - self.test_relay_state_on.set_result(state.state) - else: - self.test_relay_state_off.set_result(state.state) + if feature in (pw_api.NodeFeature.RELAY, pw_api.NodeFeature.RELAY_LOCK): + if feature == pw_api.NodeFeature.RELAY: + if state.state: + self.test_relay_state_on.set_result(state.state) + else: + self.test_relay_state_off.set_result(state.state) else: self.test_relay_state_on.set_exception( BaseException( From 4dc14c95147948e0e73b4b35712bf5e90c431c1b Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 18:43:32 +0200 Subject: [PATCH 12/94] Add NodeFeature.RELAY_LOCK to CirclePlus --- plugwise_usb/nodes/circle_plus.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index fcda89dee..895d76793 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -34,6 +34,7 @@ async def load(self) -> bool: ( NodeFeature.RELAY, NodeFeature.RELAY_INIT, + NodeFeature.RELAY_LOCK, NodeFeature.ENERGY, NodeFeature.POWER, ), @@ -69,6 +70,7 @@ async def load(self) -> bool: ( NodeFeature.RELAY, NodeFeature.RELAY_INIT, + NodeFeature.RELAY_LOCK, NodeFeature.ENERGY, NodeFeature.POWER, ), From 80e9b6658ae82bd1a97282a7b164682264ed2252 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 5 Jun 2025 18:53:58 +0200 Subject: [PATCH 13/94] Add more relay-lock-related code --- plugwise_usb/nodes/node.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index d5d78d402..fe9a4044e 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -301,6 +301,14 @@ def relay_config(self) -> RelayConfig: ) raise NotImplementedError() + @property + @raise_not_loaded + def relay_lock(self) -> bool: + """Relay value.""" + if NodeFeature.RELAY_LOCK not in self._features: + raise FeatureError(f"Relay lock is not supported for node {self.mac}") + raise NotImplementedError() + @property @raise_not_loaded def switch(self) -> bool: @@ -766,7 +774,16 @@ async def set_relay(self, state: bool) -> bool: """Change the state of the relay.""" if NodeFeature.RELAY not in self._features: raise FeatureError( - f"Changing state of relay is not supported for node {self.mac}" + f"Changing relay-lock is not supported for node {self.mac}" + ) + raise NotImplementedError() + + @raise_not_loaded + async def set_relay_lock(self, state: bool) -> bool: + """Change lock of the relay.""" + if NodeFeature.RELAY_LOCK not in self._features: + raise FeatureError( + f"Changing relay-lock is not supported for node {self.mac}" ) raise NotImplementedError() From e83facdd80bd5171166db8459a213bc582dd8fed Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 08:07:09 +0200 Subject: [PATCH 14/94] Add NodeFeature.RELAY_LOCK to FEATURE_SUPPORTED_AT_FIRMWARE --- plugwise_usb/nodes/helpers/firmware.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugwise_usb/nodes/helpers/firmware.py b/plugwise_usb/nodes/helpers/firmware.py index e0e1600f7..b909e7b42 100644 --- a/plugwise_usb/nodes/helpers/firmware.py +++ b/plugwise_usb/nodes/helpers/firmware.py @@ -162,6 +162,7 @@ class SupportedVersions(NamedTuple): NodeFeature.POWER: 2.0, NodeFeature.RELAY: 2.0, NodeFeature.RELAY_INIT: 2.6, + NodeFeature.RELAY_LOCK: 2.0, NodeFeature.MOTION: 2.0, NodeFeature.MOTION_CONFIG: 2.0, NodeFeature.SWITCH: 2.0, From 857dd5fabcaaaf5a0256461c1f0bf9e9450dc552 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 08:09:31 +0200 Subject: [PATCH 15/94] Fix asserts --- tests/test_usb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index 9b5a3794a..dc13eba13 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -2512,7 +2512,7 @@ async def test_node_discovery_and_load( assert state[pw_api.NodeFeature.INFO].version == "070073" assert state[pw_api.NodeFeature.RELAY].state - assert state[pw_api.NodeFeature.RELAY_LOCK] + assert not state[pw_api.NodeFeature.RELAY_LOCK] # Check 1111111111111111 get_state_timestamp = dt.now(UTC).replace(minute=0, second=0, microsecond=0) @@ -2549,7 +2549,7 @@ async def test_node_discovery_and_load( ) assert state[pw_api.NodeFeature.AVAILABLE].state assert state[pw_api.NodeFeature.RELAY].state - assert state[pw_api.NodeFeature.RELAY_LOCK] + assert not state[pw_api.NodeFeature.RELAY_LOCK] # region Scan From f72fd1746279280a50a4ee1b4b9f871054513362 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 09:55:41 +0200 Subject: [PATCH 16/94] Add relay_lock to cache-handling, formatting --- plugwise_usb/nodes/circle.py | 45 ++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 5684d3269..ca6450fc2 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -53,6 +53,7 @@ CACHE_ENERGY_COLLECTION = "energy_collection" CACHE_RELAY = "relay" CACHE_RELAY_INIT = "relay_init" +CACHE_RELAY_LOCK = "relay_lock" FuncT = TypeVar("FuncT", bound=Callable[..., Any]) _LOGGER = logging.getLogger(__name__) @@ -184,6 +185,7 @@ def relay_lock(self) -> bool: async def set_relay_lock(self, state: bool) -> None: """Set the state of the relay-lock.""" self._relay_lock = state + await self._relay_update_lock(state) await self.publish_feature_update_to_subscribers( NodeFeature.RELAY_LOCK, state ) @@ -665,20 +667,26 @@ async def set_relay(self, state: bool) -> bool: ) async def _relay_load_from_cache(self) -> bool: - """Load relay state from cache.""" + """Load relay state and lock from cache.""" if (cached_relay_data := self._get_cache(CACHE_RELAY)) is not None: - _LOGGER.debug("Restore relay state cache for node %s", self._mac_in_str) - relay_state = False + cached_relay_lock = self._get_cache(CACHE_RELAY_LOCK) + _LOGGER.debug("Restore relay state and lock cache for node %s", self._mac_in_str) + relay_state = relay_lock = False if cached_relay_data == "True": relay_state = True + if cached_relay_lock == "True": + relay_lock = True await self._relay_update_state(relay_state) + await self._relay_update_lock(relay_lock) return True + _LOGGER.debug( "Failed to restore relay state from cache for node %s, try to request node info...", self._mac_in_str, ) if await self.node_info_update() is None: return False + return True async def _relay_update_state( @@ -690,10 +698,11 @@ async def _relay_update_state( self._set_cache(CACHE_RELAY, "True") if self._relay_state.state is None or not self._relay_state.state: state_update = True - if not state: + else: self._set_cache(CACHE_RELAY, "False") if self._relay_state.state is None or self._relay_state.state: state_update = True + self._relay_state = replace(self._relay_state, state=state, timestamp=timestamp) if state_update: await self.publish_feature_update_to_subscribers( @@ -701,6 +710,25 @@ async def _relay_update_state( ) await self.save_cache() + async def _relay_update_lock(self, lock: bool) -> None: + """Process relay lock update.""" + state_update = False + if lock: + self._set_cache(CACHE_RELAY_LOCK, "True") + if not self._relay_lock: + state_update = True + else: + self._set_cache(CACHE_RELAY_LOCK, "False") + if self._relay_lock: + state_update = True + + self._relay_lock = lock + if state_update: + await self.publish_feature_update_to_subscribers( + NodeFeature.RELAY_LOCK, self._relay_lock + ) + await self.save_cache() + async def clock_synchronize(self) -> bool: """Synchronize clock. Returns true if successful.""" get_clock_request = CircleClockGetRequest(self._send, self._mac_in_bytes) @@ -884,10 +912,13 @@ async def node_info_update( if node_info is None: if self.skip_update(self._node_info, 30): return self._node_info + node_request = NodeInfoRequest(self._send, self._mac_in_bytes) node_info = await node_request.send() + if node_info is None: return None + await super().node_info_update(node_info) await self._relay_update_state( node_info.relay_state, timestamp=node_info.timestamp @@ -909,6 +940,7 @@ async def node_info_update( CACHE_CURRENT_LOG_ADDRESS, node_info.current_logaddress_pointer ) await self.save_cache() + return self._node_info async def _node_info_load_from_cache(self) -> bool: @@ -919,6 +951,7 @@ async def _node_info_load_from_cache(self) -> bool: ) is not None: self._current_log_address = int(current_log_address) return result + return False # pylint: disable=too-many-arguments @@ -936,8 +969,10 @@ async def update_node_details( self._relay_state = replace( self._relay_state, state=relay_state, timestamp=timestamp ) + if logaddress_pointer is not None: self._current_log_address = logaddress_pointer + return await super().update_node_details( firmware, hardware, @@ -956,8 +991,10 @@ async def unload(self) -> None: ): self._retrieve_energy_logs_task.cancel() await self._retrieve_energy_logs_task + if self._cache_enabled: await self._energy_log_records_save_to_cache() + await super().unload() @raise_not_loaded From e2ac8c51446ff0dafbf145c0aafba05b5e3f9341 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 12:33:28 +0200 Subject: [PATCH 17/94] Formatting --- plugwise_usb/nodes/circle.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index ca6450fc2..c7865f793 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -1140,6 +1140,7 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any raise NodeError( f"Update of feature '{feature}' is not supported for {self.name}" ) + if feature == NodeFeature.ENERGY: states[feature] = await self.energy_update() _LOGGER.debug( @@ -1168,6 +1169,8 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any else: state_result = await super().get_state((feature,)) states[feature] = state_result[feature] + if NodeFeature.AVAILABLE not in states: states[NodeFeature.AVAILABLE] = self.available_state + return states From 11d932976208fdd734d5feaeb6af94b79501d0b6 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 12:56:53 +0200 Subject: [PATCH 18/94] Use match-case construct in get_state() --- plugwise_usb/nodes/circle.py | 57 ++++++++++++++++++------------------ plugwise_usb/nodes/node.py | 25 +++++++++------- plugwise_usb/nodes/scan.py | 17 ++++++----- plugwise_usb/nodes/sed.py | 17 ++++++----- plugwise_usb/nodes/sense.py | 22 ++++++++------ plugwise_usb/nodes/switch.py | 14 +++++---- 6 files changed, 85 insertions(+), 67 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index c7865f793..81e2d687c 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -1141,34 +1141,35 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any f"Update of feature '{feature}' is not supported for {self.name}" ) - if feature == NodeFeature.ENERGY: - states[feature] = await self.energy_update() - _LOGGER.debug( - "async_get_state %s - energy: %s", - self._mac_in_str, - states[feature], - ) - elif feature == NodeFeature.RELAY: - states[feature] = self._relay_state - _LOGGER.debug( - "async_get_state %s - relay: %s", - self._mac_in_str, - states[feature], - ) - elif feature == NodeFeature.RELAY_LOCK: - states[feature] = self._relay_lock - elif feature == NodeFeature.RELAY_INIT: - states[feature] = self._relay_config - elif feature == NodeFeature.POWER: - states[feature] = await self.power_update() - _LOGGER.debug( - "async_get_state %s - power: %s", - self._mac_in_str, - states[feature], - ) - else: - state_result = await super().get_state((feature,)) - states[feature] = state_result[feature] + match feature: + case NodeFeature.ENERGY: + states[feature] = await self.energy_update() + _LOGGER.debug( + "async_get_state %s - energy: %s", + self._mac_in_str, + states[feature], + ) + case NodeFeature.RELAY: + states[feature] = self._relay_state + _LOGGER.debug( + "async_get_state %s - relay: %s", + self._mac_in_str, + states[feature], + ) + case NodeFeature.RELAY_LOCK: + states[feature] = self._relay_lock + case NodeFeature.RELAY_INIT: + states[feature] = self._relay_config + case NodeFeature.POWER: + states[feature] = await self.power_update() + _LOGGER.debug( + "async_get_state %s - power: %s", + self._mac_in_str, + states[feature], + ) + case _: + state_result = await super().get_state((feature,)) + states[feature] = state_result[feature] if NodeFeature.AVAILABLE not in states: states[NodeFeature.AVAILABLE] = self.available_state diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index fe9a4044e..857929ee5 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -613,17 +613,20 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any f"Update of feature '{feature.name}' is " + f"not supported for {self.mac}" ) - if feature == NodeFeature.INFO: - states[NodeFeature.INFO] = await self.node_info_update() - elif feature == NodeFeature.AVAILABLE: - states[NodeFeature.AVAILABLE] = self.available_state - elif feature == NodeFeature.PING: - states[NodeFeature.PING] = await self.ping_update() - else: - raise NodeError( - f"Update of feature '{feature.name}' is " - + f"not supported for {self.mac}" - ) + + match feature: + case NodeFeature.INFO: + states[NodeFeature.INFO] = await self.node_info_update() + case NodeFeature.AVAILABLE: + states[NodeFeature.AVAILABLE] = self.available_state + case NodeFeature.PING: + states[NodeFeature.PING] = await self.ping_update() + case _: + raise NodeError( + f"Update of feature '{feature.name}' is " + + f"not supported for {self.mac}" + ) + return states async def unload(self) -> None: diff --git a/plugwise_usb/nodes/scan.py b/plugwise_usb/nodes/scan.py index 2beed8cf9..ce90c69fb 100644 --- a/plugwise_usb/nodes/scan.py +++ b/plugwise_usb/nodes/scan.py @@ -566,13 +566,16 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any f"Update of feature '{feature.name}' is " + f"not supported for {self.mac}" ) - if feature == NodeFeature.MOTION: - states[NodeFeature.MOTION] = self._motion_state - elif feature == NodeFeature.MOTION_CONFIG: - states[NodeFeature.MOTION_CONFIG] = self._motion_config - else: - state_result = await super().get_state((feature,)) - states[feature] = state_result[feature] + + match feature: + case NodeFeature.MOTION: + states[NodeFeature.MOTION] = self._motion_state + case NodeFeature.MOTION_CONFIG: + states[NodeFeature.MOTION_CONFIG] = self._motion_config + case _: + state_result = await super().get_state((feature,)) + states[feature] = state_result[feature] + if NodeFeature.AVAILABLE not in states: states[NodeFeature.AVAILABLE] = self.available_state return states diff --git a/plugwise_usb/nodes/sed.py b/plugwise_usb/nodes/sed.py index 66ff9eaca..45f18ef7f 100644 --- a/plugwise_usb/nodes/sed.py +++ b/plugwise_usb/nodes/sed.py @@ -756,11 +756,14 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any f"Update of feature '{feature.name}' is " + f"not supported for {self.mac}" ) - if feature == NodeFeature.INFO: - states[NodeFeature.INFO] = await self.node_info_update() - elif feature == NodeFeature.BATTERY: - states[NodeFeature.BATTERY] = self._battery_config - else: - state_result = await super().get_state((feature,)) - states[feature] = state_result[feature] + + match feature: + case NodeFeature.INFO: + states[NodeFeature.INFO] = await self.node_info_update() + case NodeFeature.BATTERY: + states[NodeFeature.BATTERY] = self._battery_config + case _: + state_result = await super().get_state((feature,)) + states[feature] = state_result[feature] + return states diff --git a/plugwise_usb/nodes/sense.py b/plugwise_usb/nodes/sense.py index 438ce6b0f..111c0efb7 100644 --- a/plugwise_usb/nodes/sense.py +++ b/plugwise_usb/nodes/sense.py @@ -134,15 +134,19 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any raise NodeError( f"Update of feature '{feature.name}' is not supported for {self.mac}" ) - if feature == NodeFeature.TEMPERATURE: - states[NodeFeature.TEMPERATURE] = self._temperature - elif feature == NodeFeature.HUMIDITY: - states[NodeFeature.HUMIDITY] = self._humidity - elif feature == NodeFeature.PING: - states[NodeFeature.PING] = await self.ping_update() - else: - state_result = await super().get_state((feature,)) - states[feature] = state_result[feature] + + match feature: + case NodeFeature.TEMPERATURE: + states[NodeFeature.TEMPERATURE] = self._temperature + case NodeFeature.HUMIDITY: + states[NodeFeature.HUMIDITY] = self._humidity + case NodeFeature.PING: + states[NodeFeature.PING] = await self.ping_update() + case _: + state_result = await super().get_state((feature,)) + states[feature] = state_result[feature] + if NodeFeature.AVAILABLE not in states: states[NodeFeature.AVAILABLE] = self.available_state + return states diff --git a/plugwise_usb/nodes/switch.py b/plugwise_usb/nodes/switch.py index 1a2fed77e..62d82262e 100644 --- a/plugwise_usb/nodes/switch.py +++ b/plugwise_usb/nodes/switch.py @@ -147,11 +147,15 @@ async def get_state(self, features: tuple[NodeFeature]) -> dict[NodeFeature, Any f"Update of feature '{feature.name}' is " + f"not supported for {self.mac}" ) - if feature == NodeFeature.SWITCH: - states[NodeFeature.SWITCH] = self._switch_state - else: - state_result = await super().get_state((feature,)) - states[feature] = state_result[feature] + + match feature: + case NodeFeature.SWITCH: + states[NodeFeature.SWITCH] = self._switch_state + case _: + state_result = await super().get_state((feature,)) + states[feature] = state_result[feature] + if NodeFeature.AVAILABLE not in states: states[NodeFeature.AVAILABLE] = self.available_state + return states From a9d05f13d79932171b18b4a49f666c7b02433815 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 15:21:29 +0200 Subject: [PATCH 19/94] Bump to a1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e8947fbf1..3f410d44e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a0" +version = "0.43.0a1" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From a10a6ad3d82bc0747f3982d7dd09a3ea7f2ad1c4 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 18:56:20 +0200 Subject: [PATCH 20/94] Define RelayLock class, implement --- plugwise_usb/api.py | 7 +++++++ plugwise_usb/nodes/circle.py | 16 ++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/plugwise_usb/api.py b/plugwise_usb/api.py index b3d6ef615..2e2c206c2 100644 --- a/plugwise_usb/api.py +++ b/plugwise_usb/api.py @@ -172,6 +172,13 @@ class RelayConfig: init_state: bool | None = None +@dataclass(frozen=True) +class RelayLock: + """Status of relay lock.""" + + lock_state: bool | None = None + + @dataclass(frozen=True) class RelayState: """Status of relay.""" diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 81e2d687c..2829a56a4 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -18,6 +18,7 @@ NodeType, PowerStatistics, RelayConfig, + RelayLock, RelayState, ) from ..connection import StickController @@ -85,7 +86,7 @@ def __init__( super().__init__(mac, address, controller, loaded_callback) # Relay - self._relay_lock = False + self._relay_lock: RelayLock = RelayLock(lock_state=False) self._relay_state: RelayState = RelayState() self._relay_config: RelayConfig = RelayConfig() @@ -178,13 +179,12 @@ async def relay_init_on(self) -> None: await self._relay_init_set(True) @property - def relay_lock(self) -> bool: + def relay_lock(self) -> RelayLock: """State of the relay lock.""" return self._relay_lock - async def set_relay_lock(self, state: bool) -> None: + async def set_relay_lock(self, state: bool) -> RelayLock: """Set the state of the relay-lock.""" - self._relay_lock = state await self._relay_update_lock(state) await self.publish_feature_update_to_subscribers( NodeFeature.RELAY_LOCK, state @@ -644,7 +644,7 @@ async def set_relay(self, state: bool) -> bool: f"Changing state of relay is not supported for node {self.mac}" ) - if self._relay_lock: + if getattr(self._relay_lock, "lock_state"): raise NodeError("Changing state of relay failed, it is locked") _LOGGER.debug("set_relay() start") @@ -710,10 +710,10 @@ async def _relay_update_state( ) await self.save_cache() - async def _relay_update_lock(self, lock: bool) -> None: + async def _relay_update_lock(self, state: bool) -> None: """Process relay lock update.""" state_update = False - if lock: + if state: self._set_cache(CACHE_RELAY_LOCK, "True") if not self._relay_lock: state_update = True @@ -722,7 +722,7 @@ async def _relay_update_lock(self, lock: bool) -> None: if self._relay_lock: state_update = True - self._relay_lock = lock + self._relay_lock = replace(self._relay_lock, lock_state=state) if state_update: await self.publish_feature_update_to_subscribers( NodeFeature.RELAY_LOCK, self._relay_lock From c27077d10c6d70572667d73c6b982632d51cdfa9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 19:03:34 +0200 Subject: [PATCH 21/94] Update test-asserts --- tests/test_usb.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index dc13eba13..6591b1057 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -792,12 +792,13 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No # Test blocked async switching due to relay-lock active await stick.nodes["0098765432101234"].set_relay_lock(True) assert stick.nodes["0098765432101234"].relay_lock + assert stick.nodes["0098765432101234"].relay_lock.lock_state with pytest.raises(pw_exceptions.NodeError): await stick.nodes["0098765432101234"].set_relay(True) assert not stick.nodes["0098765432101234"].relay # Make sure to turn lock off for further testing await stick.nodes["0098765432101234"].set_relay_lock(False) - assert not stick.nodes["0098765432101234"].relay_lock + assert not stick.nodes["0098765432101234"].relay_lock.lock_state # Test async switching back from off to on self.test_relay_state_on = asyncio.Future() @@ -2512,7 +2513,7 @@ async def test_node_discovery_and_load( assert state[pw_api.NodeFeature.INFO].version == "070073" assert state[pw_api.NodeFeature.RELAY].state - assert not state[pw_api.NodeFeature.RELAY_LOCK] + assert not state[pw_api.NodeFeature.RELAY_LOCK].lock_state # Check 1111111111111111 get_state_timestamp = dt.now(UTC).replace(minute=0, second=0, microsecond=0) @@ -2549,7 +2550,7 @@ async def test_node_discovery_and_load( ) assert state[pw_api.NodeFeature.AVAILABLE].state assert state[pw_api.NodeFeature.RELAY].state - assert not state[pw_api.NodeFeature.RELAY_LOCK] + assert not state[pw_api.NodeFeature.RELAY_LOCK].lock_state # region Scan From 2e0e24fe004a7a1a30f2a51252933c3d65be9f55 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 19:11:30 +0200 Subject: [PATCH 22/94] Simplify lock_state to state --- plugwise_usb/api.py | 2 +- plugwise_usb/nodes/circle.py | 6 +++--- tests/test_usb.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugwise_usb/api.py b/plugwise_usb/api.py index 2e2c206c2..b2f0c0439 100644 --- a/plugwise_usb/api.py +++ b/plugwise_usb/api.py @@ -176,7 +176,7 @@ class RelayConfig: class RelayLock: """Status of relay lock.""" - lock_state: bool | None = None + state: bool | None = None @dataclass(frozen=True) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 2829a56a4..a0a4dd7e1 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -86,7 +86,7 @@ def __init__( super().__init__(mac, address, controller, loaded_callback) # Relay - self._relay_lock: RelayLock = RelayLock(lock_state=False) + self._relay_lock: RelayLock = RelayLock(state=False) self._relay_state: RelayState = RelayState() self._relay_config: RelayConfig = RelayConfig() @@ -644,7 +644,7 @@ async def set_relay(self, state: bool) -> bool: f"Changing state of relay is not supported for node {self.mac}" ) - if getattr(self._relay_lock, "lock_state"): + if getattr(self._relay_lock, "state"): raise NodeError("Changing state of relay failed, it is locked") _LOGGER.debug("set_relay() start") @@ -722,7 +722,7 @@ async def _relay_update_lock(self, state: bool) -> None: if self._relay_lock: state_update = True - self._relay_lock = replace(self._relay_lock, lock_state=state) + self._relay_lock = replace(self._relay_lock, state=state) if state_update: await self.publish_feature_update_to_subscribers( NodeFeature.RELAY_LOCK, self._relay_lock diff --git a/tests/test_usb.py b/tests/test_usb.py index 6591b1057..c06ce2a39 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -792,13 +792,13 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No # Test blocked async switching due to relay-lock active await stick.nodes["0098765432101234"].set_relay_lock(True) assert stick.nodes["0098765432101234"].relay_lock - assert stick.nodes["0098765432101234"].relay_lock.lock_state + assert stick.nodes["0098765432101234"].relay_lock.state with pytest.raises(pw_exceptions.NodeError): await stick.nodes["0098765432101234"].set_relay(True) assert not stick.nodes["0098765432101234"].relay # Make sure to turn lock off for further testing await stick.nodes["0098765432101234"].set_relay_lock(False) - assert not stick.nodes["0098765432101234"].relay_lock.lock_state + assert not stick.nodes["0098765432101234"].relay_lock.state # Test async switching back from off to on self.test_relay_state_on = asyncio.Future() @@ -2513,7 +2513,7 @@ async def test_node_discovery_and_load( assert state[pw_api.NodeFeature.INFO].version == "070073" assert state[pw_api.NodeFeature.RELAY].state - assert not state[pw_api.NodeFeature.RELAY_LOCK].lock_state + assert not state[pw_api.NodeFeature.RELAY_LOCK].state # Check 1111111111111111 get_state_timestamp = dt.now(UTC).replace(minute=0, second=0, microsecond=0) @@ -2550,7 +2550,7 @@ async def test_node_discovery_and_load( ) assert state[pw_api.NodeFeature.AVAILABLE].state assert state[pw_api.NodeFeature.RELAY].state - assert not state[pw_api.NodeFeature.RELAY_LOCK].lock_state + assert not state[pw_api.NodeFeature.RELAY_LOCK].state # region Scan From af9e3fa2e7ce79abb1eb16715f360397f6e11ba4 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 6 Jun 2025 19:23:41 +0200 Subject: [PATCH 23/94] Add/update missing property, update set_relay_lock typing --- plugwise_usb/api.py | 7 +++++++ plugwise_usb/nodes/circle.py | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/api.py b/plugwise_usb/api.py index b2f0c0439..472d06ac0 100644 --- a/plugwise_usb/api.py +++ b/plugwise_usb/api.py @@ -377,6 +377,13 @@ def relay(self) -> bool: Raises NodeError when relay feature is not present at device. """ + @property + def relay_lock(self) -> RelayLock: + """Last known relay lock state information. + + Raises NodeError when relay lock feature is not present at device. + """ + @property def relay_state(self) -> RelayState: """Last known relay state information. diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index a0a4dd7e1..80918d2d5 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -183,12 +183,13 @@ def relay_lock(self) -> RelayLock: """State of the relay lock.""" return self._relay_lock - async def set_relay_lock(self, state: bool) -> RelayLock: + async def set_relay_lock(self, state: bool) -> bool: """Set the state of the relay-lock.""" await self._relay_update_lock(state) await self.publish_feature_update_to_subscribers( NodeFeature.RELAY_LOCK, state ) + return state # endregion From 85df557452f441b4a1abb910e5714e9d9e00f065 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 09:44:50 +0200 Subject: [PATCH 24/94] Correct _relay_update_lock() --- plugwise_usb/nodes/circle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 80918d2d5..dcb2e8223 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -716,11 +716,11 @@ async def _relay_update_lock(self, state: bool) -> None: state_update = False if state: self._set_cache(CACHE_RELAY_LOCK, "True") - if not self._relay_lock: + if self._relay_lock.state is None or not self._relay_lock.state: state_update = True else: self._set_cache(CACHE_RELAY_LOCK, "False") - if self._relay_lock: + if self._relay_lock.state is None or self._relay_lock.state: state_update = True self._relay_lock = replace(self._relay_lock, state=state) From 0a7211fbe1dae370c9f934d2c7a4d6ad56b577d7 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 09:49:16 +0200 Subject: [PATCH 25/94] Move and protect set_relay_lock() --- plugwise_usb/nodes/circle.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index dcb2e8223..daff4cb63 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -183,14 +183,6 @@ def relay_lock(self) -> RelayLock: """State of the relay lock.""" return self._relay_lock - async def set_relay_lock(self, state: bool) -> bool: - """Set the state of the relay-lock.""" - await self._relay_update_lock(state) - await self.publish_feature_update_to_subscribers( - NodeFeature.RELAY_LOCK, state - ) - return state - # endregion async def calibration_update(self) -> bool: @@ -667,6 +659,15 @@ async def set_relay(self, state: bool) -> bool: + "in response to CircleRelaySwitchRequest for node {self.mac}" ) + @raise_not_loaded + async def set_relay_lock(self, state: bool) -> bool: + """Set the state of the relay-lock.""" + await self._relay_update_lock(state) + await self.publish_feature_update_to_subscribers( + NodeFeature.RELAY_LOCK, state + ) + return state + async def _relay_load_from_cache(self) -> bool: """Load relay state and lock from cache.""" if (cached_relay_data := self._get_cache(CACHE_RELAY)) is not None: From 46410fe1ee467102d570cb95f318f029fa38792a Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 09:50:50 +0200 Subject: [PATCH 26/94] Test set_relay_lock() protection --- tests/test_usb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_usb.py b/tests/test_usb.py index c06ce2a39..5887eb826 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -775,6 +775,9 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No with pytest.raises(pw_exceptions.NodeError): await stick.nodes["0098765432101234"].set_relay(True) + with pytest.raises(pw_exceptions.NodeError): + await stick.nodes["0098765432101234"].set_relay_lock(True) + # Manually load node assert await stick.nodes["0098765432101234"].load() From cfbbad6837d778bac2da7a452775474779d5c70d Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 10:14:54 +0200 Subject: [PATCH 27/94] Add relay_lock to node-cache --- plugwise_usb/nodes/circle.py | 5 +++++ plugwise_usb/nodes/node.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index daff4cb63..25ac2baa7 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -963,6 +963,7 @@ async def update_node_details( hardware: str | None, node_type: NodeType | None, timestamp: datetime | None, + relay_lock: bool | None, relay_state: bool | None, logaddress_pointer: int | None, ) -> bool: @@ -972,6 +973,9 @@ async def update_node_details( self._relay_state, state=relay_state, timestamp=timestamp ) + if relay_lock is not None: + self._relay_lock = replace(self._relay_lock, state=relay_lock) + if logaddress_pointer is not None: self._current_log_address = logaddress_pointer @@ -980,6 +984,7 @@ async def update_node_details( hardware, node_type, timestamp, + relay_lock, relay_state, logaddress_pointer, ) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 857929ee5..cf0bf822a 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -469,6 +469,7 @@ async def node_info_update( node_type=node_info.node_type, hardware=node_info.hardware, timestamp=node_info.timestamp, + relay_lock=None, relay_state=node_info.relay_state, logaddress_pointer=node_info.current_logaddress_pointer, ) @@ -487,6 +488,7 @@ async def _node_info_load_from_cache(self) -> bool: hardware=hardware, node_type=node_type, timestamp=timestamp, + relay_lock=None, relay_state=None, logaddress_pointer=None, ) @@ -498,6 +500,7 @@ async def update_node_details( hardware: str | None, node_type: NodeType | None, timestamp: datetime | None, + relay_lock: bool | None, relay_state: bool | None, logaddress_pointer: int | None, ) -> bool: From 0d4b4ce4de2e8caccea350071c2816de883e00fb Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 10:17:41 +0200 Subject: [PATCH 28/94] Adapt related test-code --- tests/test_usb.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_usb.py b/tests/test_usb.py index 5887eb826..88001d698 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -2168,6 +2168,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign hardware="080007", node_type=None, timestamp=None, + relay_lock=None, relay_state=None, logaddress_pointer=None, ) @@ -2272,6 +2273,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign hardware="080007", node_type=None, timestamp=None, + relay_lock=None, relay_state=None, logaddress_pointer=None, ) @@ -2348,6 +2350,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign hardware="070051", node_type=None, timestamp=None, + relay_lock=None, relay_state=None, logaddress_pointer=None, ) @@ -2365,6 +2368,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign hardware="070051", node_type=None, timestamp=None, + relay_lock=None, relay_state=None, logaddress_pointer=None, ) From 5c12a19a6588349d6d6cd3398003d1a51526f56c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 10:23:31 +0200 Subject: [PATCH 29/94] Bump to a2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3f410d44e..999e0792b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a1" +version = "0.43.0a2" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 39825220e58c30119b86d299d9ab0016363d454d Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 13:19:11 +0200 Subject: [PATCH 30/94] Remove double publish_feature_... --- plugwise_usb/nodes/circle.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 25ac2baa7..774259633 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -663,9 +663,6 @@ async def set_relay(self, state: bool) -> bool: async def set_relay_lock(self, state: bool) -> bool: """Set the state of the relay-lock.""" await self._relay_update_lock(state) - await self.publish_feature_update_to_subscribers( - NodeFeature.RELAY_LOCK, state - ) return state async def _relay_load_from_cache(self) -> bool: From 60ab4c491eb0f96ff59d8364deda6603d616bbb6 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 13:47:31 +0200 Subject: [PATCH 31/94] Don't init self._relay_lock, will be set from cache --- plugwise_usb/nodes/circle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 774259633..03d6ae552 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -86,7 +86,7 @@ def __init__( super().__init__(mac, address, controller, loaded_callback) # Relay - self._relay_lock: RelayLock = RelayLock(state=False) + self._relay_lock: RelayLock = RelayLock() self._relay_state: RelayState = RelayState() self._relay_config: RelayConfig = RelayConfig() From 904e6e40e657b8470a8c2fbb9c7fcff401ea2a9c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 13:52:31 +0200 Subject: [PATCH 32/94] Bump to a3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 999e0792b..6d88e5cc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a2" +version = "0.43.0a3" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 1da9bb7ec96cee27b4ec98481f7f80a4f8a5bb0d Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 14:14:11 +0200 Subject: [PATCH 33/94] _relay_lock: only replace after state_update --- plugwise_usb/nodes/circle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 03d6ae552..4ece6b002 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -721,8 +721,8 @@ async def _relay_update_lock(self, state: bool) -> None: if self._relay_lock.state is None or self._relay_lock.state: state_update = True - self._relay_lock = replace(self._relay_lock, state=state) if state_update: + self._relay_lock = replace(self._relay_lock, state=state) await self.publish_feature_update_to_subscribers( NodeFeature.RELAY_LOCK, self._relay_lock ) From ff6ef3872ebec86cc8c117f855c30431f7493b2c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 14:17:39 +0200 Subject: [PATCH 34/94] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0e020de..2191b8383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.43.0 + +- Feature Request: add a lock to disable relay-switch-changes (energy devices only) + ## v0.42.1 - Implement code improvements, extend debug message [#253](https://github.com/plugwise/python-plugwise-usb/pull/247) From c771745ba9189cec1cfb338c5826b3bc55bacbec Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 14:30:04 +0200 Subject: [PATCH 35/94] Fix node property, as suggested --- plugwise_usb/nodes/node.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index cf0bf822a..5369c3d25 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -23,6 +23,7 @@ NodeType, PowerStatistics, RelayConfig, + RelayLock, RelayState, ) from ..connection import StickController @@ -277,10 +278,12 @@ def power(self) -> PowerStatistics: @property @raise_not_loaded - def relay_state(self) -> RelayState: - """State of relay.""" - if NodeFeature.RELAY not in self._features: - raise FeatureError(f"Relay state is not supported for node {self.mac}") + def relay_config(self) -> RelayConfig: + """Relay configuration.""" + if NodeFeature.RELAY_INIT not in self._features: + raise FeatureError( + f"Relay configuration is not supported for node {self.mac}" + ) raise NotImplementedError() @property @@ -293,18 +296,16 @@ def relay(self) -> bool: @property @raise_not_loaded - def relay_config(self) -> RelayConfig: - """Relay configuration.""" - if NodeFeature.RELAY_INIT not in self._features: - raise FeatureError( - f"Relay configuration is not supported for node {self.mac}" - ) + def relay_state(self) -> RelayState: + """State of relay.""" + if NodeFeature.RELAY not in self._features: + raise FeatureError(f"Relay state is not supported for node {self.mac}") raise NotImplementedError() @property @raise_not_loaded - def relay_lock(self) -> bool: - """Relay value.""" + def relay_lock(self) -> RelayLock: + """State of relay lock.""" if NodeFeature.RELAY_LOCK not in self._features: raise FeatureError(f"Relay lock is not supported for node {self.mac}") raise NotImplementedError() From 493e18cc0d072f32a2c964650993695b8e230dcf Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 14:38:44 +0200 Subject: [PATCH 36/94] Improve-fix error messages --- plugwise_usb/nodes/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 5369c3d25..689b97abb 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -781,7 +781,7 @@ async def set_relay(self, state: bool) -> bool: """Change the state of the relay.""" if NodeFeature.RELAY not in self._features: raise FeatureError( - f"Changing relay-lock is not supported for node {self.mac}" + f"Changing relay-state is not supported for node {self.mac}" ) raise NotImplementedError() @@ -790,7 +790,7 @@ async def set_relay_lock(self, state: bool) -> bool: """Change lock of the relay.""" if NodeFeature.RELAY_LOCK not in self._features: raise FeatureError( - f"Changing relay-lock is not supported for node {self.mac}" + f"Changing relay-lock state is not supported for node {self.mac}" ) raise NotImplementedError() From f050ee0d0ad1688a59cfbc84dc99de9011ace8f0 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 14:41:24 +0200 Subject: [PATCH 37/94] Remove blank spaces --- plugwise_usb/nodes/circle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 4ece6b002..bb6bbd39a 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -701,7 +701,7 @@ async def _relay_update_state( self._set_cache(CACHE_RELAY, "False") if self._relay_state.state is None or self._relay_state.state: state_update = True - + self._relay_state = replace(self._relay_state, state=state, timestamp=timestamp) if state_update: await self.publish_feature_update_to_subscribers( @@ -720,7 +720,7 @@ async def _relay_update_lock(self, state: bool) -> None: self._set_cache(CACHE_RELAY_LOCK, "False") if self._relay_lock.state is None or self._relay_lock.state: state_update = True - + if state_update: self._relay_lock = replace(self._relay_lock, state=state) await self.publish_feature_update_to_subscribers( From b7f1c2a034b709003a2c687756f7a6716915518f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 14:43:24 +0200 Subject: [PATCH 38/94] Update testcode as suggested --- tests/test_usb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_usb.py b/tests/test_usb.py index 88001d698..feb9efe80 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -716,6 +716,9 @@ async def node_relay_state( self.test_relay_state_on.set_result(state.state) else: self.test_relay_state_off.set_result(state.state) + if feature == pw_api.NodeFeature.RELAY_LOCK: + # Handle RELAY_LOCK callbacks if needed + pass else: self.test_relay_state_on.set_exception( BaseException( From 9c74ad2b3ad342687b4a1e4ab871c02526a7c0c8 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 14:45:15 +0200 Subject: [PATCH 39/94] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2191b8383..1a7477d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v0.43.0 -- Feature Request: add a lock to disable relay-switch-changes (energy devices only) +- Feature Request: add a lock to disable relay-switch-changes (energy devices only) [#254](https://github.com/plugwise/python-plugwise-usb/pull/254) ## v0.42.1 From 83e1ab26690dd80cf18ce75e85cf7aab6b1a8890 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 14:52:18 +0200 Subject: [PATCH 40/94] Bump to a4 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6d88e5cc4..21cf405a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a3" +version = "0.43.0a4" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From cdf40d8d20f6a2e6d78afcb28f71fb0f28cfdebf Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 16:15:17 +0200 Subject: [PATCH 41/94] Improve _relay_load_from_cache(): init relay_lock when not present --- plugwise_usb/nodes/circle.py | 38 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index bb6bbd39a..41bdcb88a 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -666,27 +666,43 @@ async def set_relay_lock(self, state: bool) -> bool: return state async def _relay_load_from_cache(self) -> bool: - """Load relay state and lock from cache.""" + """Load relay state from cache.""" if (cached_relay_data := self._get_cache(CACHE_RELAY)) is not None: - cached_relay_lock = self._get_cache(CACHE_RELAY_LOCK) - _LOGGER.debug("Restore relay state and lock cache for node %s", self._mac_in_str) - relay_state = relay_lock = False + _LOGGER.debug( + "Restore relay state from cache for node %s: relay: %s", + self._mac_in_str, + cached_relay_data, + ) + relay_state = False if cached_relay_data == "True": relay_state = True - if cached_relay_lock == "True": - relay_lock = True + await self._relay_update_state(relay_state) - await self._relay_update_lock(relay_lock) - return True + result = True _LOGGER.debug( "Failed to restore relay state from cache for node %s, try to request node info...", self._mac_in_str, ) if await self.node_info_update() is None: - return False + result = False - return True + if (cached_relay_lock := self._get_cache(CACHE_RELAY_LOCK)) is not None: + _LOGGER.debug( + "Restore relay_lock state from cache for node %s: relay_lock: %s", + self._mac_in_str, + cached_relay_lock, + ) + relay_lock = False + if cached_relay_lock == "True": + relay_lock = True + + await self._relay_update_lock(relay_lock) + + # Set to initial state False when not present in cache + await self._relay_update_lock(False) + + return result async def _relay_update_state( self, state: bool, timestamp: datetime | None = None @@ -960,7 +976,7 @@ async def update_node_details( hardware: str | None, node_type: NodeType | None, timestamp: datetime | None, - relay_lock: bool | None, + relay_lock: bool | None, relay_state: bool | None, logaddress_pointer: int | None, ) -> bool: From bdbff0e34184145b292d2cca4b52d26c6fc03935 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 16:21:38 +0200 Subject: [PATCH 42/94] Adapt testcode --- tests/test_usb.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index feb9efe80..0e8965853 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -784,6 +784,10 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No # Manually load node assert await stick.nodes["0098765432101234"].load() + # Check relay_lock is set to False when not in cache + assert stick.nodes["0098765432101234"].relay_lock + assert not stick.nodes["0098765432101234"].relay_lock.state + unsub_relay = stick.nodes["0098765432101234"].subscribe_to_feature_update( node_feature_callback=self.node_relay_state, features=(pw_api.NodeFeature.RELAY, pw_api.NodeFeature.RELAY_LOCK,), @@ -797,7 +801,6 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No # Test blocked async switching due to relay-lock active await stick.nodes["0098765432101234"].set_relay_lock(True) - assert stick.nodes["0098765432101234"].relay_lock assert stick.nodes["0098765432101234"].relay_lock.state with pytest.raises(pw_exceptions.NodeError): await stick.nodes["0098765432101234"].set_relay(True) From d876ff6cbb43712af31fb5de5f12052a7cea32f9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 16:37:44 +0200 Subject: [PATCH 43/94] Improve logic as suggested --- plugwise_usb/nodes/circle.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 41bdcb88a..081185a20 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -673,19 +673,17 @@ async def _relay_load_from_cache(self) -> bool: self._mac_in_str, cached_relay_data, ) - relay_state = False - if cached_relay_data == "True": - relay_state = True + relay_state = False if cached_relay_data == "True" else True await self._relay_update_state(relay_state) result = True - - _LOGGER.debug( - "Failed to restore relay state from cache for node %s, try to request node info...", - self._mac_in_str, - ) - if await self.node_info_update() is None: - result = False + else: + _LOGGER.debug( + "Failed to restore relay state from cache for node %s, try to request node info...", + self._mac_in_str, + ) + if await self.node_info_update() is None: + result = False if (cached_relay_lock := self._get_cache(CACHE_RELAY_LOCK)) is not None: _LOGGER.debug( @@ -693,14 +691,11 @@ async def _relay_load_from_cache(self) -> bool: self._mac_in_str, cached_relay_lock, ) - relay_lock = False - if cached_relay_lock == "True": - relay_lock = True - + relay_lock = False if cached_relay_lock == "True" else True await self._relay_update_lock(relay_lock) - - # Set to initial state False when not present in cache - await self._relay_update_lock(False) + else: + # Set to initial state False when not present in cache + await self._relay_update_lock(False) return result From 4ee805120dabfc8404f5e096ac96e9c26b233e20 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 16:44:54 +0200 Subject: [PATCH 44/94] Bump to a5 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 21cf405a2..600501abe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a4" +version = "0.43.0a5" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From b22296b831183a1b41a8448bf41a26f2a77a7f84 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 16:54:38 +0200 Subject: [PATCH 45/94] Pylint fixes --- plugwise_usb/nodes/circle.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 081185a20..72c017357 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -667,6 +667,7 @@ async def set_relay_lock(self, state: bool) -> bool: async def _relay_load_from_cache(self) -> bool: """Load relay state from cache.""" + result = True if (cached_relay_data := self._get_cache(CACHE_RELAY)) is not None: _LOGGER.debug( "Restore relay state from cache for node %s: relay: %s", @@ -674,9 +675,8 @@ async def _relay_load_from_cache(self) -> bool: cached_relay_data, ) - relay_state = False if cached_relay_data == "True" else True + relay_state = cached_relay_data != "True" await self._relay_update_state(relay_state) - result = True else: _LOGGER.debug( "Failed to restore relay state from cache for node %s, try to request node info...", @@ -691,7 +691,7 @@ async def _relay_load_from_cache(self) -> bool: self._mac_in_str, cached_relay_lock, ) - relay_lock = False if cached_relay_lock == "True" else True + relay_lock = cached_relay_lock != "True" await self._relay_update_lock(relay_lock) else: # Set to initial state False when not present in cache From 6683d1e053c0bb7e2221e66f4af50d01fc4b8934 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 17:33:21 +0200 Subject: [PATCH 46/94] update_node_details(); add debug logging --- plugwise_usb/nodes/node.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 689b97abb..8ec5d5b37 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -506,6 +506,19 @@ async def update_node_details( logaddress_pointer: int | None, ) -> bool: """Process new node info and return true if all fields are updated.""" + _LOGGER.debug( + "update_node_details | firmware=%s, hardware=%s, nodetype=%s, timestamp=%s", + firmware, + hardware, + node_type, + timestamp, + ) + _LOGGER.debug( + "update_node_details | relay_lock=%s, relay_state=%s, logaddress_pointer=%s,", + relay_lock, + relay_state, + logaddress_pointer, + ) complete = True if node_type is None: complete = False @@ -549,11 +562,14 @@ async def update_node_details( self.mac, hardware, ) + self._node_info.model_type = None if len(model_info) > 1: self._node_info.model_type = " ".join(model_info[1:]) + if self._node_info.model is not None: self._node_info.name = f"{model_info[0]} {self._node_info.mac[-5:]}" + self._set_cache(CACHE_HARDWARE, hardware) if timestamp is None: @@ -567,7 +583,7 @@ async def update_node_details( minutes=5 ): await self._available_update_state(True, timestamp) - + _LOGGER.debug("update_node_details | complete=%s", complete) return complete async def is_online(self) -> bool: From 235d8817a71264e8853488d9d45fac0726279080 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 17:34:26 +0200 Subject: [PATCH 47/94] Bump to a6 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 600501abe..e8e87a673 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a5" +version = "0.43.0a6" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From a8498e62640807345c02b632aa022c03f7034fe2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 17:50:25 +0200 Subject: [PATCH 48/94] _node_info_load_from_cache() debug result --- plugwise_usb/nodes/node.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 8ec5d5b37..3d4b83147 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -484,7 +484,7 @@ async def _node_info_load_from_cache(self) -> bool: node_type: NodeType | None = None if (node_type_str := self._get_cache(CACHE_NODE_TYPE)) is not None: node_type = NodeType(int(node_type_str)) - return await self.update_node_details( + result = await self.update_node_details( firmware=firmware, hardware=hardware, node_type=node_type, @@ -493,6 +493,8 @@ async def _node_info_load_from_cache(self) -> bool: relay_state=None, logaddress_pointer=None, ) + _LOGGER.debug("_node_info_load_from_cache returns %s", result) + return result # pylint: disable=too-many-arguments async def update_node_details( From 89746dafa7f47c8f3f804bdf41b22fed119b5975 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 17:50:50 +0200 Subject: [PATCH 49/94] Bump to a7 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e8e87a673..d2cbf8c9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a6" +version = "0.43.0a7" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 448b143068e1b46babad69bdd8167b9f3390fd40 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 18:06:16 +0200 Subject: [PATCH 50/94] More debugging --- plugwise_usb/nodes/node.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 3d4b83147..8e1c902b6 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -407,7 +407,9 @@ async def _load_from_cache(self) -> bool: _LOGGER.debug("Node %s failed to load cache file", self.mac) return False # Node Info - if not await self._node_info_load_from_cache(): + result: bool = await self._node_info_load_from_cache() + _LOGGER.debug("_load_from_cache | load node_info | result=%s", result) + if not result: _LOGGER.debug("Node %s failed to load node_info from cache", self.mac) return False return True From ec5839e0efb58966cd901e83560661336c6faed5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 18:06:37 +0200 Subject: [PATCH 51/94] Bump to a8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d2cbf8c9d..f96fcdb52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a7" +version = "0.43.0a8" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 9e3ad65341ad5e6b5f0bbbcae74429c6158c30c8 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 19:31:24 +0200 Subject: [PATCH 52/94] Correct/improve circle._load_from_cache(() --- plugwise_usb/nodes/circle.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 72c017357..fdf75c233 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -847,6 +847,7 @@ async def load(self) -> bool: async def _load_from_cache(self) -> bool: """Load states from previous cached information. Returns True if successful.""" if not await super()._load_from_cache(): + _LOGGER.debug("_load_from_cache | super-load failed") return False # Calibration settings @@ -855,25 +856,32 @@ async def _load_from_cache(self) -> bool: "Node %s failed to load calibration from cache", self._mac_in_str ) return False + # Energy collection - if await self._energy_log_records_load_from_cache(): + if not await self._energy_log_records_load_from_cache(): _LOGGER.warning( "Node %s failed to load energy_log_records from cache", self._mac_in_str, ) + return False + # Relay - if await self._relay_load_from_cache(): + if not await self._relay_load_from_cache(): _LOGGER.debug( - "Node %s successfully loaded relay state from cache", + "Node %s failed to load relay state from cache", self._mac_in_str, ) + return False + # Relay init config if feature is enabled if NodeFeature.RELAY_INIT in self._features: - if await self._relay_init_load_from_cache(): + if not await self._relay_init_load_from_cache(): _LOGGER.debug( - "Node %s successfully loaded relay_init state from cache", + "Node %s failed to load relay_init state from cache", self._mac_in_str, ) + return False + return True @raise_not_loaded From be0d125e7bbdc972f7cfd58bbc2e255449bc4c22 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 19:33:48 +0200 Subject: [PATCH 53/94] Formatting --- plugwise_usb/nodes/node.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 8e1c902b6..95b015200 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -403,15 +403,18 @@ async def _load_from_cache(self) -> bool: """Load states from previous cached information. Return True if successful.""" if self._loaded: return True + if not await self._load_cache_file(): _LOGGER.debug("Node %s failed to load cache file", self.mac) return False + # Node Info result: bool = await self._node_info_load_from_cache() _LOGGER.debug("_load_from_cache | load node_info | result=%s", result) if not result: _LOGGER.debug("Node %s failed to load node_info from cache", self.mac) return False + return True async def initialize(self) -> None: From b26ad87d894d15ad8e446d95d61c721882902464 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 19:34:57 +0200 Subject: [PATCH 54/94] Bump to a9 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f96fcdb52..d63c1b3a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a8" +version = "0.43.0a9" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From f364bcf296340a0332e0454479a769560ae80553 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 20:15:55 +0200 Subject: [PATCH 55/94] Debug circle._node_info_load_from_cache() --- plugwise_usb/nodes/circle.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index fdf75c233..e48d4102c 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -964,12 +964,18 @@ async def node_info_update( async def _node_info_load_from_cache(self) -> bool: """Load node info settings from cache.""" result = await super()._node_info_load_from_cache() + _LOGGER.debug("circle._node_info_load_from_cache | result=%s", result) if ( current_log_address := self._get_cache(CACHE_CURRENT_LOG_ADDRESS) ) is not None: self._current_log_address = int(current_log_address) + _LOGGER.debug( + "circle._node_info_load_from_cache | current_log_address=%s", + self._current_log_address + ) return result + _LOGGER.debug("circle._node_info_load_from_cache | current_log_address=None") return False # pylint: disable=too-many-arguments From fa7d78732fc132f43fdd2ea8b1af6c3b7570c185 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 7 Jun 2025 20:16:26 +0200 Subject: [PATCH 56/94] Bump to a10 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d63c1b3a5..5c7715f3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a9" +version = "0.43.0a10" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From a318e56b3bf924a5f9c825847b7f56dcfcb945eb Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 07:44:33 +0200 Subject: [PATCH 57/94] Improve comments, formatting --- plugwise_usb/nodes/circle.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index e48d4102c..bb93068cd 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -786,8 +786,9 @@ async def load(self) -> bool: """Load and activate Circle node features.""" if self._loaded: return True + if self._cache_enabled: - _LOGGER.debug("Load Circle node %s from cache", self._mac_in_str) + _LOGGER.debug("Loading Circle node %s from cache", self._mac_in_str) if await self._load_from_cache(): self._loaded = True self._setup_protocol( @@ -803,12 +804,13 @@ async def load(self) -> bool: if await self.initialize(): await self._loaded_callback(NodeEvent.LOADED, self.mac) return True + _LOGGER.debug( - "Load Circle node %s from cache failed", + "Loading Circle node %s from cache failed", self._mac_in_str, ) else: - _LOGGER.debug("Load Circle node %s", self._mac_in_str) + _LOGGER.debug("Loading Circle node %s", self._mac_in_str) # Check if node is online if not self._available and not await self.is_online(): @@ -828,6 +830,7 @@ async def load(self) -> bool: self._mac_in_str, ) return False + self._loaded = True self._setup_protocol( CIRCLE_FIRMWARE_SUPPORT, @@ -841,6 +844,7 @@ async def load(self) -> bool: ) if not await self.initialize(): return False + await self._loaded_callback(NodeEvent.LOADED, self.mac) return True From d75237df5239735360fcabffd46a3f0694c4ecba Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 08:06:51 +0200 Subject: [PATCH 58/94] Make sure to execute all steps in _load_from_cache() --- plugwise_usb/nodes/circle.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index bb93068cd..86a202a27 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -850,16 +850,19 @@ async def load(self) -> bool: async def _load_from_cache(self) -> bool: """Load states from previous cached information. Returns True if successful.""" + result = True if not await super()._load_from_cache(): _LOGGER.debug("_load_from_cache | super-load failed") - return False + if result: + result = False # Calibration settings if not await self._calibration_load_from_cache(): _LOGGER.debug( "Node %s failed to load calibration from cache", self._mac_in_str ) - return False + if result: + result = False # Energy collection if not await self._energy_log_records_load_from_cache(): @@ -867,7 +870,8 @@ async def _load_from_cache(self) -> bool: "Node %s failed to load energy_log_records from cache", self._mac_in_str, ) - return False + if result: + result = False # Relay if not await self._relay_load_from_cache(): @@ -875,7 +879,8 @@ async def _load_from_cache(self) -> bool: "Node %s failed to load relay state from cache", self._mac_in_str, ) - return False + if result: + result = False # Relay init config if feature is enabled if NodeFeature.RELAY_INIT in self._features: @@ -884,9 +889,10 @@ async def _load_from_cache(self) -> bool: "Node %s failed to load relay_init state from cache", self._mac_in_str, ) - return False + if result: + result = False - return True + return result @raise_not_loaded async def initialize(self) -> bool: From ca01f28237c383f7727ee49a1d96c0f9b789589c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 08:08:41 +0200 Subject: [PATCH 59/94] Bump to a11 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5c7715f3a..90721dafa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a10" +version = "0.43.0a11" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 86a7c41a7fd6376d2cf5538a4524e55c18d34242 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 08:14:12 +0200 Subject: [PATCH 60/94] Fix logic mistake, as suggested --- plugwise_usb/nodes/circle.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 86a202a27..67698dd95 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -674,8 +674,7 @@ async def _relay_load_from_cache(self) -> bool: self._mac_in_str, cached_relay_data, ) - - relay_state = cached_relay_data != "True" + relay_state = cached_relay_data == "True" await self._relay_update_state(relay_state) else: _LOGGER.debug( @@ -691,7 +690,7 @@ async def _relay_load_from_cache(self) -> bool: self._mac_in_str, cached_relay_lock, ) - relay_lock = cached_relay_lock != "True" + relay_lock = cached_relay_lock == "True" await self._relay_update_lock(relay_lock) else: # Set to initial state False when not present in cache From 73080132e299a2438e2f2ee4916100a9aa6ec917 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 08:15:52 +0200 Subject: [PATCH 61/94] Bump to a12 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 90721dafa..3804a7652 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a11" +version = "0.43.0a12" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 8efb91fa8ba103227862603351065a25760b428e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 08:17:57 +0200 Subject: [PATCH 62/94] Remove unneeded guard --- plugwise_usb/nodes/circle.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 67698dd95..f46f769c3 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -852,8 +852,7 @@ async def _load_from_cache(self) -> bool: result = True if not await super()._load_from_cache(): _LOGGER.debug("_load_from_cache | super-load failed") - if result: - result = False + result = False # Calibration settings if not await self._calibration_load_from_cache(): From d4cf635f4fdc4ac6f68951a9c3375f0d7d286b1d Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 08:35:09 +0200 Subject: [PATCH 63/94] Replace getattr() as suggested --- plugwise_usb/nodes/circle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index f46f769c3..80f464c27 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -637,7 +637,7 @@ async def set_relay(self, state: bool) -> bool: f"Changing state of relay is not supported for node {self.mac}" ) - if getattr(self._relay_lock, "state"): + if self._relay_lock.state: raise NodeError("Changing state of relay failed, it is locked") _LOGGER.debug("set_relay() start") From d9f4d3cded100b4174edc0cff90b550bf4fba9a5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 08:39:33 +0200 Subject: [PATCH 64/94] Remove double call to node._node_info_load_from_cache() --- plugwise_usb/nodes/circle.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 80f464c27..7dad9f650 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -971,8 +971,6 @@ async def node_info_update( async def _node_info_load_from_cache(self) -> bool: """Load node info settings from cache.""" - result = await super()._node_info_load_from_cache() - _LOGGER.debug("circle._node_info_load_from_cache | result=%s", result) if ( current_log_address := self._get_cache(CACHE_CURRENT_LOG_ADDRESS) ) is not None: @@ -981,7 +979,7 @@ async def _node_info_load_from_cache(self) -> bool: "circle._node_info_load_from_cache | current_log_address=%s", self._current_log_address ) - return result + return True _LOGGER.debug("circle._node_info_load_from_cache | current_log_address=None") return False From 1ca96a2c9945f915cde98d84709ddd182181e4ff Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 08:49:32 +0200 Subject: [PATCH 65/94] Bump to a13 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3804a7652..4907c5f9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a12" +version = "0.43.0a13" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 3d739936d246823dd7ecd28c354550cbcc2baa74 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 09:09:09 +0200 Subject: [PATCH 66/94] Remove test-debug-logging --- plugwise_usb/nodes/node.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 95b015200..4b1ecea7b 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -409,9 +409,7 @@ async def _load_from_cache(self) -> bool: return False # Node Info - result: bool = await self._node_info_load_from_cache() - _LOGGER.debug("_load_from_cache | load node_info | result=%s", result) - if not result: + if not await self._node_info_load_from_cache(): _LOGGER.debug("Node %s failed to load node_info from cache", self.mac) return False @@ -489,7 +487,8 @@ async def _node_info_load_from_cache(self) -> bool: node_type: NodeType | None = None if (node_type_str := self._get_cache(CACHE_NODE_TYPE)) is not None: node_type = NodeType(int(node_type_str)) - result = await self.update_node_details( + + return await self.update_node_details( firmware=firmware, hardware=hardware, node_type=node_type, @@ -498,8 +497,6 @@ async def _node_info_load_from_cache(self) -> bool: relay_state=None, logaddress_pointer=None, ) - _LOGGER.debug("_node_info_load_from_cache returns %s", result) - return result # pylint: disable=too-many-arguments async def update_node_details( @@ -590,7 +587,7 @@ async def update_node_details( minutes=5 ): await self._available_update_state(True, timestamp) - _LOGGER.debug("update_node_details | complete=%s", complete) + return complete async def is_online(self) -> bool: From 530fe0ff7dd4cc2e9cd44336602a633676e08f3f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 09:42:09 +0200 Subject: [PATCH 67/94] Revert addition of relay_lock to update_node_info() --- plugwise_usb/nodes/circle.py | 5 ----- plugwise_usb/nodes/node.py | 10 +++------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 7dad9f650..3de5dd6a6 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -991,7 +991,6 @@ async def update_node_details( hardware: str | None, node_type: NodeType | None, timestamp: datetime | None, - relay_lock: bool | None, relay_state: bool | None, logaddress_pointer: int | None, ) -> bool: @@ -1001,9 +1000,6 @@ async def update_node_details( self._relay_state, state=relay_state, timestamp=timestamp ) - if relay_lock is not None: - self._relay_lock = replace(self._relay_lock, state=relay_lock) - if logaddress_pointer is not None: self._current_log_address = logaddress_pointer @@ -1012,7 +1008,6 @@ async def update_node_details( hardware, node_type, timestamp, - relay_lock, relay_state, logaddress_pointer, ) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 4b1ecea7b..2d4e09dd5 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -473,7 +473,6 @@ async def node_info_update( node_type=node_info.node_type, hardware=node_info.hardware, timestamp=node_info.timestamp, - relay_lock=None, relay_state=node_info.relay_state, logaddress_pointer=node_info.current_logaddress_pointer, ) @@ -493,7 +492,6 @@ async def _node_info_load_from_cache(self) -> bool: hardware=hardware, node_type=node_type, timestamp=timestamp, - relay_lock=None, relay_state=None, logaddress_pointer=None, ) @@ -505,21 +503,19 @@ async def update_node_details( hardware: str | None, node_type: NodeType | None, timestamp: datetime | None, - relay_lock: bool | None, relay_state: bool | None, logaddress_pointer: int | None, ) -> bool: """Process new node info and return true if all fields are updated.""" _LOGGER.debug( - "update_node_details | firmware=%s, hardware=%s, nodetype=%s, timestamp=%s", + "update_node_details | firmware=%s, hardware=%s, nodetype=%s", firmware, hardware, node_type, - timestamp, ) _LOGGER.debug( - "update_node_details | relay_lock=%s, relay_state=%s, logaddress_pointer=%s,", - relay_lock, + "update_node_details | timestamp=%s, relay_state=%s, logaddress_pointer=%s,", + timestamp, relay_state, logaddress_pointer, ) From 61c5c8b8b7b119f2c421e71f58e43c4eda494374 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 09:43:58 +0200 Subject: [PATCH 68/94] Adapt test-code --- tests/test_usb.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index 0e8965853..d160c9348 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -2174,7 +2174,6 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign hardware="080007", node_type=None, timestamp=None, - relay_lock=None, relay_state=None, logaddress_pointer=None, ) @@ -2279,7 +2278,6 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign hardware="080007", node_type=None, timestamp=None, - relay_lock=None, relay_state=None, logaddress_pointer=None, ) @@ -2356,7 +2354,6 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign hardware="070051", node_type=None, timestamp=None, - relay_lock=None, relay_state=None, logaddress_pointer=None, ) @@ -2374,7 +2371,6 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign hardware="070051", node_type=None, timestamp=None, - relay_lock=None, relay_state=None, logaddress_pointer=None, ) From 2ce800b0d12e88392eabf6330e01e3629c1a3522 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 09:45:30 +0200 Subject: [PATCH 69/94] Spelling --- plugwise_usb/nodes/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 2d4e09dd5..bcf8bd6bf 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -797,7 +797,7 @@ async def set_relay(self, state: bool) -> bool: """Change the state of the relay.""" if NodeFeature.RELAY not in self._features: raise FeatureError( - f"Changing relay-state is not supported for node {self.mac}" + f"Changing relay state is not supported for node {self.mac}" ) raise NotImplementedError() @@ -806,7 +806,7 @@ async def set_relay_lock(self, state: bool) -> bool: """Change lock of the relay.""" if NodeFeature.RELAY_LOCK not in self._features: raise FeatureError( - f"Changing relay-lock state is not supported for node {self.mac}" + f"Changing relay lock state is not supported for node {self.mac}" ) raise NotImplementedError() From 80108a1b87d04d50c2a177c16ef7c3d428ffd705 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 09:48:41 +0200 Subject: [PATCH 70/94] Bump to a14 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4907c5f9e..b7550bacb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a13" +version = "0.43.0a14" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 0d17ad22de68828dd7e8f87a85135140cb7447c1 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 14:56:17 +0200 Subject: [PATCH 71/94] Add missing save_cache() --- plugwise_usb/nodes/circle.py | 6 ++++++ plugwise_usb/nodes/node.py | 3 +++ plugwise_usb/nodes/sed.py | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 3de5dd6a6..4c0297226 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -591,6 +591,7 @@ async def _energy_log_records_save_to_cache(self) -> None: cached_logs += f"-{log.timestamp.hour}-{log.timestamp.minute}" cached_logs += f"-{log.timestamp.second}:{log.pulses}" self._set_cache(CACHE_ENERGY_COLLECTION, cached_logs) + await self.save_cache() async def _energy_log_record_update_state( self, @@ -606,6 +607,7 @@ async def _energy_log_record_update_state( ) if not self._cache_enabled: return False + log_cache_record = f"{address}:{slot}:{timestamp.year}" log_cache_record += f"-{timestamp.month}-{timestamp.day}" log_cache_record += f"-{timestamp.hour}-{timestamp.minute}" @@ -621,12 +623,16 @@ async def _energy_log_record_update_state( self._set_cache( CACHE_ENERGY_COLLECTION, cached_logs + "|" + log_cache_record ) + await self.save_cache() return True + return False + _LOGGER.debug( "No existing energy collection log cached for %s", self._mac_in_str ) self._set_cache(CACHE_ENERGY_COLLECTION, log_cache_record) + await self.save_cache() return True @raise_not_loaded diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index bcf8bd6bf..373e0f773 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -690,6 +690,7 @@ def _set_cache(self, setting: str, value: Any) -> None: """Store setting with value in cache memory.""" if not self._cache_enabled: return + if isinstance(value, datetime): self._node_cache.update_state( setting, @@ -707,9 +708,11 @@ async def save_cache( """Save cached data to cache file when cache is enabled.""" if not self._cache_enabled or not self._loaded or not self._initialized: return + _LOGGER.debug("Save cache file for node %s", self.mac) if self._cache_save_task is not None and not self._cache_save_task.done(): await self._cache_save_task + if trigger_only: self._cache_save_task = create_task(self._node_cache.save_cache()) else: diff --git a/plugwise_usb/nodes/sed.py b/plugwise_usb/nodes/sed.py index 45f18ef7f..6d15f75d5 100644 --- a/plugwise_usb/nodes/sed.py +++ b/plugwise_usb/nodes/sed.py @@ -550,6 +550,7 @@ async def _awake_response(self, response: PlugwiseResponse) -> bool: tasks.append(self.update_ping_at_awake()) await gather(*tasks) + await self.save_cache() return True async def update_ping_at_awake(self) -> None: @@ -560,6 +561,7 @@ def _detect_maintenance_interval(self, timestamp: datetime) -> None: """Detect current maintenance interval.""" if self._last_awake[NodeAwakeResponseType.MAINTENANCE] == timestamp: return + new_interval_in_sec = ( timestamp - self._last_awake[NodeAwakeResponseType.MAINTENANCE] ).seconds @@ -594,6 +596,8 @@ def _detect_maintenance_interval(self, timestamp: datetime) -> None: self._set_cache( CACHE_MAINTENANCE_INTERVAL, SED_DEFAULT_MAINTENANCE_INTERVAL ) + + await self.save_cache() self._maintenance_interval_restored_from_cache = True async def _reset_awake(self, last_alive: datetime) -> None: From 92e967a3d8ff43c0e0f02bd363484243324b3047 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 15:11:24 +0200 Subject: [PATCH 72/94] Write initial current_log_address to cache at starting --- plugwise_usb/nodes/circle.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 4c0297226..40882676e 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -955,9 +955,16 @@ async def node_info_update( await self._relay_update_state( node_info.relay_state, timestamp=node_info.timestamp ) - if self._current_log_address is not None and ( + if self._current_log_address is None: + if node_info.current_logaddress_pointer: + self._set_cache( + CACHE_CURRENT_LOG_ADDRESS, + node_info.current_logaddress_pointer, + ) + await self.save_cache() + elif ( self._current_log_address > node_info.current_logaddress_pointer - or self._current_log_address == 1 + or self._current_log_address == 0 ): # Rollover of log address _LOGGER.debug( @@ -966,6 +973,7 @@ async def node_info_update( node_info.current_logaddress_pointer, self._mac_in_str, ) + if self._current_log_address != node_info.current_logaddress_pointer: self._current_log_address = node_info.current_logaddress_pointer self._set_cache( From 049ca5b094aecb2e29c25d2a1c2b6d2a96f81612 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 15:18:52 +0200 Subject: [PATCH 73/94] Remove double save_cache() --- plugwise_usb/nodes/sed.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugwise_usb/nodes/sed.py b/plugwise_usb/nodes/sed.py index 6d15f75d5..26a0d7a40 100644 --- a/plugwise_usb/nodes/sed.py +++ b/plugwise_usb/nodes/sed.py @@ -597,7 +597,6 @@ def _detect_maintenance_interval(self, timestamp: datetime) -> None: CACHE_MAINTENANCE_INTERVAL, SED_DEFAULT_MAINTENANCE_INTERVAL ) - await self.save_cache() self._maintenance_interval_restored_from_cache = True async def _reset_awake(self, last_alive: datetime) -> None: From b39396516bb2cf823acbb5e5d4c90b1203f903a5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 15:25:03 +0200 Subject: [PATCH 74/94] Bump to a15 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b7550bacb..d896ae312 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a14" +version = "0.43.0a15" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From f2ae55f972c68c8d8748b19a19816569d05e3043 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 15:37:02 +0200 Subject: [PATCH 75/94] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a7477d8a..2e64e3e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v0.43.0 - Feature Request: add a lock to disable relay-switch-changes (energy devices only) [#254](https://github.com/plugwise/python-plugwise-usb/pull/254) +- Fix cache-related bugs, improve related functions ## v0.42.1 From d0a2bbe406f13db1c7ed462cd9f354270802f665 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 15:46:30 +0200 Subject: [PATCH 76/94] Improve logic --- plugwise_usb/nodes/circle.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 40882676e..d10a7974f 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -955,14 +955,17 @@ async def node_info_update( await self._relay_update_state( node_info.relay_state, timestamp=node_info.timestamp ) - if self._current_log_address is None: - if node_info.current_logaddress_pointer: - self._set_cache( - CACHE_CURRENT_LOG_ADDRESS, - node_info.current_logaddress_pointer, - ) - await self.save_cache() - elif ( + if ( + self._get_cache(CACHE_CURRENT_LOG_ADDRESS) is None + and node_info.current_logaddress_pointer + ): + self._set_cache( + CACHE_CURRENT_LOG_ADDRESS, + node_info.current_logaddress_pointer, + ) + await self.save_cache() + + if self._current_log_address is not None and ( self._current_log_address > node_info.current_logaddress_pointer or self._current_log_address == 0 ): @@ -973,13 +976,12 @@ async def node_info_update( node_info.current_logaddress_pointer, self._mac_in_str, ) - - if self._current_log_address != node_info.current_logaddress_pointer: - self._current_log_address = node_info.current_logaddress_pointer - self._set_cache( - CACHE_CURRENT_LOG_ADDRESS, node_info.current_logaddress_pointer - ) - await self.save_cache() + if self._current_log_address != node_info.current_logaddress_pointer: + self._current_log_address = node_info.current_logaddress_pointer + self._set_cache( + CACHE_CURRENT_LOG_ADDRESS, node_info.current_logaddress_pointer + ) + await self.save_cache() return self._node_info From e5bc252463664f5a89a3adfa6adbc24ee165dc6b Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 15:47:56 +0200 Subject: [PATCH 77/94] Bump to a16 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d896ae312..199f8e54a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a15" +version = "0.43.0a16" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 9e93e3b2e3a300a46c3b1179c80cd9ee8e169436 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 16:24:51 +0200 Subject: [PATCH 78/94] NodeCache init: remove empty string --- plugwise_usb/nodes/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/node.py b/plugwise_usb/nodes/node.py index 373e0f773..fb099c9f2 100644 --- a/plugwise_usb/nodes/node.py +++ b/plugwise_usb/nodes/node.py @@ -74,7 +74,7 @@ def __init__( self._cache_enabled: bool = False self._cache_folder_create: bool = False self._cache_save_task: Task[None] | None = None - self._node_cache = NodeCache(mac, "") + self._node_cache = NodeCache(mac) # Sensors self._available: bool = False self._connected: bool = False From e392dd18a9d5783024fc0c92b26e06e39fbfbb91 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 20:06:31 +0200 Subject: [PATCH 79/94] Correct _current_log_address update logic --- plugwise_usb/nodes/circle.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index d10a7974f..ff52a53fb 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -966,17 +966,19 @@ async def node_info_update( await self.save_cache() if self._current_log_address is not None and ( - self._current_log_address > node_info.current_logaddress_pointer - or self._current_log_address == 0 + # Change note: _curr_log_addr lags behind c_l_a__pointer so must be < instead of > + self._current_log_address < node_info.current_logaddress_pointer + # Change note: rollover is from 6015 to 0 so must be 6015 instead of 0 + or self._current_log_address == 6015 ): - # Rollover of log address - _LOGGER.debug( - "Rollover log address from %s into %s for node %s", - self._current_log_address, - node_info.current_logaddress_pointer, - self._mac_in_str, - ) if self._current_log_address != node_info.current_logaddress_pointer: + # Rollover of log address + _LOGGER.debug( + "Rollover log address from %s into %s for node %s", + self._current_log_address, + node_info.current_logaddress_pointer, + self._mac_in_str, + ) self._current_log_address = node_info.current_logaddress_pointer self._set_cache( CACHE_CURRENT_LOG_ADDRESS, node_info.current_logaddress_pointer From 3407a3b9dfdc5ea31a17638183e117a5957509f5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 8 Jun 2025 20:16:54 +0200 Subject: [PATCH 80/94] Compare options --- plugwise_usb/nodes/circle.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index ff52a53fb..400f1e0b3 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -965,9 +965,15 @@ async def node_info_update( ) await self.save_cache() + if self._current_log_address is not None and ( + # This can be the case after an energy-reset + self._current_log_address > node_info.current_logaddress_pointer + or (self._current_log_address < node_info.current_logaddress_pointer and self._current_log_address == 0) + ): + if self._current_log_address is not None and ( # Change note: _curr_log_addr lags behind c_l_a__pointer so must be < instead of > - self._current_log_address < node_info.current_logaddress_pointer + self._current_log_address < node_info.current_logaddress_pointer # Change note: rollover is from 6015 to 0 so must be 6015 instead of 0 or self._current_log_address == 6015 ): From 74f26d54687cdd6414fcf678b2362171f7fec55a Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 09:39:14 +0200 Subject: [PATCH 81/94] Return to existing code --- plugwise_usb/nodes/circle.py | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 400f1e0b3..33173a59f 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -966,30 +966,23 @@ async def node_info_update( await self.save_cache() if self._current_log_address is not None and ( - # This can be the case after an energy-reset self._current_log_address > node_info.current_logaddress_pointer - or (self._current_log_address < node_info.current_logaddress_pointer and self._current_log_address == 0) + or self._current_log_address == 1 ): + # Rollover of log address + _LOGGER.debug( + "Rollover log address from %s into %s for node %s", + self._current_log_address, + node_info.current_logaddress_pointer, + self._mac_in_str, + ) - if self._current_log_address is not None and ( - # Change note: _curr_log_addr lags behind c_l_a__pointer so must be < instead of > - self._current_log_address < node_info.current_logaddress_pointer - # Change note: rollover is from 6015 to 0 so must be 6015 instead of 0 - or self._current_log_address == 6015 - ): - if self._current_log_address != node_info.current_logaddress_pointer: - # Rollover of log address - _LOGGER.debug( - "Rollover log address from %s into %s for node %s", - self._current_log_address, - node_info.current_logaddress_pointer, - self._mac_in_str, - ) - self._current_log_address = node_info.current_logaddress_pointer - self._set_cache( - CACHE_CURRENT_LOG_ADDRESS, node_info.current_logaddress_pointer - ) - await self.save_cache() + if self._current_log_address != node_info.current_logaddress_pointer: + self._current_log_address = node_info.current_logaddress_pointer + self._set_cache( + CACHE_CURRENT_LOG_ADDRESS, node_info.current_logaddress_pointer + ) + await self.save_cache() return self._node_info From b60d9b32b083787f8f4198889ee04277376d977f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 09:52:58 +0200 Subject: [PATCH 82/94] Remove save_cache()s, handled in the calling function --- plugwise_usb/nodes/circle.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 33173a59f..ea6999b99 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -623,7 +623,6 @@ async def _energy_log_record_update_state( self._set_cache( CACHE_ENERGY_COLLECTION, cached_logs + "|" + log_cache_record ) - await self.save_cache() return True return False @@ -632,7 +631,6 @@ async def _energy_log_record_update_state( "No existing energy collection log cached for %s", self._mac_in_str ) self._set_cache(CACHE_ENERGY_COLLECTION, log_cache_record) - await self.save_cache() return True @raise_not_loaded From 09df036442a1a85e6cbeecb186ab8593e1bbaaad Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 10:00:17 +0200 Subject: [PATCH 83/94] Improve logger-messages --- plugwise_usb/nodes/circle_plus.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 895d76793..4de82fd25 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -26,7 +26,7 @@ async def load(self) -> bool: if self._loaded: return True if self._cache_enabled: - _LOGGER.debug("Load Circle node %s from cache", self._node_info.mac) + _LOGGER.debug("Loading Circle node %s from cache", self._node_info.mac) if await self._load_from_cache(): self._loaded = True self._setup_protocol( @@ -43,11 +43,11 @@ async def load(self) -> bool: await self._loaded_callback(NodeEvent.LOADED, self.mac) return True _LOGGER.info( - "Load Circle+ node %s from cache failed", + "Loading Circle+ node %s from cache failed", self._node_info.mac, ) else: - _LOGGER.debug("Load Circle+ node %s", self._node_info.mac) + _LOGGER.debug("Loading Circle+ node %s", self._node_info.mac) # Check if node is online if not self._available and not await self.is_online(): From b639c1f5e12799b9b3185a3a992c73560c7d4940 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 10:12:40 +0200 Subject: [PATCH 84/94] Update CHANGELOG --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e64e3e26..bc2dd7fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ## v0.43.0 -- Feature Request: add a lock to disable relay-switch-changes (energy devices only) [#254](https://github.com/plugwise/python-plugwise-usb/pull/254) -- Fix cache-related bugs, improve related functions +- PR [#254](https://github.com/plugwise/python-plugwise-usb/pull/254): + - Feature Request: add a lock-function to disable relay-switch-changes (energy devices only) + - Fix data not loading from cache at (re)start, store `current_log_address` item to cache ## v0.42.1 From 7a50535b146a74fe7aac0f31432811977fc4b0fd Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 10:13:40 +0200 Subject: [PATCH 85/94] Set to v0.43.0 release -version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 199f8e54a..a6e3c71ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a16" +version = "0.43.0" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 7cc8cfbf926704aceb9d45f83f0719ee40ade815 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 10:22:31 +0200 Subject: [PATCH 86/94] Remove trailing space --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc2dd7fda..6fb109e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## v0.43.0 - PR [#254](https://github.com/plugwise/python-plugwise-usb/pull/254): - - Feature Request: add a lock-function to disable relay-switch-changes (energy devices only) + - Feature Request: add a lock-function to disable relay-switch-changes (energy devices only) - Fix data not loading from cache at (re)start, store `current_log_address` item to cache ## v0.42.1 From 644b0bd52d224853297ba3479556026710c39ad8 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 11:13:35 +0200 Subject: [PATCH 87/94] Remove added save_cache(), wrongly added --- plugwise_usb/nodes/circle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index ea6999b99..8db64c3ec 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -591,7 +591,6 @@ async def _energy_log_records_save_to_cache(self) -> None: cached_logs += f"-{log.timestamp.hour}-{log.timestamp.minute}" cached_logs += f"-{log.timestamp.second}:{log.pulses}" self._set_cache(CACHE_ENERGY_COLLECTION, cached_logs) - await self.save_cache() async def _energy_log_record_update_state( self, From 382026af4064956ec7ee00d328413ad972663439 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 11:22:40 +0200 Subject: [PATCH 88/94] SED: move save_cache() to task-list --- plugwise_usb/nodes/sed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/sed.py b/plugwise_usb/nodes/sed.py index 26a0d7a40..a583142bb 100644 --- a/plugwise_usb/nodes/sed.py +++ b/plugwise_usb/nodes/sed.py @@ -522,6 +522,7 @@ async def _awake_response(self, response: PlugwiseResponse) -> bool: NodeFeature.BATTERY, self._battery_config, ), + self.save_cache(), ] self._delayed_task = self._loop.create_task( self._send_tasks(), name=f"Delayed update for {self._mac_in_str}" @@ -550,7 +551,6 @@ async def _awake_response(self, response: PlugwiseResponse) -> bool: tasks.append(self.update_ping_at_awake()) await gather(*tasks) - await self.save_cache() return True async def update_ping_at_awake(self) -> None: From 46a5d43dd73fee755bfd62947bd07d51989fd81e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 11:24:36 +0200 Subject: [PATCH 89/94] Bump version to v0.43.0-1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a6e3c71ff..1adb2a11c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0" +version = "0.43.0-1" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From 6e716e1b00daaa2fdb3acceeb83bdedae95a2345 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 14:48:36 +0200 Subject: [PATCH 90/94] Block relay without raising an error --- plugwise_usb/nodes/circle.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 8db64c3ec..04c09fa4f 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -641,9 +641,10 @@ async def set_relay(self, state: bool) -> bool: ) if self._relay_lock.state: - raise NodeError("Changing state of relay failed, it is locked") + _LOGGER.debug("Relay switch blocked, relay is locked") + return not state - _LOGGER.debug("set_relay() start") + _LOGGER.debug("Switching relay to %s", state) request = CircleRelaySwitchRequest(self._send, self._mac_in_bytes, state) response = await request.send() From 49c12bffc4f6f0e941f053e0e686641ea5995673 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 14:49:43 +0200 Subject: [PATCH 91/94] Adapt related test-assert --- tests/test_usb.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index d160c9348..4d6eed36c 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -802,8 +802,7 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No # Test blocked async switching due to relay-lock active await stick.nodes["0098765432101234"].set_relay_lock(True) assert stick.nodes["0098765432101234"].relay_lock.state - with pytest.raises(pw_exceptions.NodeError): - await stick.nodes["0098765432101234"].set_relay(True) + assert not await stick.nodes["0098765432101234"].set_relay(True) assert not stick.nodes["0098765432101234"].relay # Make sure to turn lock off for further testing await stick.nodes["0098765432101234"].set_relay_lock(False) From 1f7c5264b0d2c8d8e8b21d1bece94c96e198d649 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 14:53:14 +0200 Subject: [PATCH 92/94] Bump to a17 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1adb2a11c..fc0401bf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0-1" +version = "0.43.0a17" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From adced7df5502230e75ba494dc3970a45d88273d5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 15:07:55 +0200 Subject: [PATCH 93/94] Bump to v0.43.0.2 release-version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fc0401bf3..bbfe68149 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.43.0a17" +version = "0.43.0.2" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From ba33eaf79a3640c4b74a092abb580afa6a9527f6 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Jun 2025 15:10:29 +0200 Subject: [PATCH 94/94] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fb109e8e..bc66cfccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v0.43.0 +## v0.43.0(.2) - PR [#254](https://github.com/plugwise/python-plugwise-usb/pull/254): - Feature Request: add a lock-function to disable relay-switch-changes (energy devices only)