From 6153f946b113bb2393d79f329cb2cfe729eb6199 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Sun, 14 Sep 2025 14:46:56 +0200 Subject: [PATCH 01/92] schedule clock synchronization every 60 seconds --- plugwise_usb/nodes/circle.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index b06f6c8e9..e5e5956aa 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -2,7 +2,7 @@ from __future__ import annotations -from asyncio import Task, create_task, gather +from asyncio import Task, create_task, gather, sleep from collections.abc import Awaitable, Callable from dataclasses import replace from datetime import UTC, datetime, timedelta @@ -141,6 +141,8 @@ def __init__( """Initialize base class for Sleeping End Device.""" super().__init__(mac, node_type, controller, loaded_callback) + # Clock + self._clock_synchronize_task: Task[None] | None = None # Relay self._relay_lock: RelayLock = RelayLock() self._relay_state: RelayState = RelayState() @@ -852,6 +854,12 @@ async def _relay_update_lock( ) await self.save_cache() + async def _clock_synchronize_scheduler(self) -> bool: + """Synchronize clock scheduler.""" + while True: + await sleep(60) + await self.clock_synchronize() + async def clock_synchronize(self) -> bool: """Synchronize clock. Returns true if successful.""" get_clock_request = CircleClockGetRequest(self._send, self._mac_in_bytes) @@ -992,6 +1000,10 @@ async def initialize(self) -> bool: ) self._initialized = False return False + if self._clock_synchronize_task is None or self._clock_synchronize_task.done(): + self._clock_synchronize_task = create_task( + self._clock_synchronize_scheduler() + ) if not self._calibration and not await self.calibration_update(): _LOGGER.debug( @@ -1082,6 +1094,13 @@ async def unload(self) -> None: if self._cache_enabled: await self._energy_log_records_save_to_cache() + if ( + hasattr(self, "_clock_synchronize_task") + and self._clock_synchronize_task + and not self._clock_synchronize_task.done() + ): + self._clock_synchronize_task.cancel() + await super().unload() @raise_not_loaded From 44651f2bf90539f5f61223f2f2257a1384058074 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Sun, 14 Sep 2025 15:08:23 +0200 Subject: [PATCH 02/92] =?UTF-8?q?CR:=20Drain=20cancellation=20to=20avoid?= =?UTF-8?q?=20=E2=80=9CTask=20exception=20was=20never=20retrieved=E2=80=9D?= =?UTF-8?q?.=20CR:=20Make=20the=20scheduler=20resilient:=20fix=20return=20?= =?UTF-8?q?type,=20handle=20cancellation,=20and=20keep=20the=20loop=20aliv?= =?UTF-8?q?e=20on=20errors.=20CR:=20Import=20CancelledError=20for=20proper?= =?UTF-8?q?=20task=20shutdown=20handling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugwise_usb/nodes/circle.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index e5e5956aa..b6d8c4f62 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -2,7 +2,7 @@ from __future__ import annotations -from asyncio import Task, create_task, gather, sleep +from asyncio import CancelledError, Task, create_task, gather, sleep from collections.abc import Awaitable, Callable from dataclasses import replace from datetime import UTC, datetime, timedelta @@ -854,11 +854,14 @@ async def _relay_update_lock( ) await self.save_cache() - async def _clock_synchronize_scheduler(self) -> bool: - """Synchronize clock scheduler.""" - while True: - await sleep(60) - await self.clock_synchronize() + async def _clock_synchronize_scheduler(self) -> None: + """Background task: periodically synchronize the clock until cancelled.""" + try: + while True: + await sleep(60) + await self.clock_synchronize() + except CancelledError: + _LOGGER.debug("Clock sync scheduler cancelled for %s", self.name) async def clock_synchronize(self) -> bool: """Synchronize clock. Returns true if successful.""" @@ -1094,12 +1097,10 @@ async def unload(self) -> None: if self._cache_enabled: await self._energy_log_records_save_to_cache() - if ( - hasattr(self, "_clock_synchronize_task") - and self._clock_synchronize_task - and not self._clock_synchronize_task.done() - ): + if self._clock_synchronize_task: self._clock_synchronize_task.cancel() + await gather(self._clock_synchronize_task, return_exceptions=True) + self._clock_synchronize_task = None await super().unload() From c0eeed6f2bd9bf25e3e8104fe1d749764aaba3ab Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Sun, 14 Sep 2025 15:15:20 +0200 Subject: [PATCH 03/92] add raise after except --- plugwise_usb/nodes/circle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index b6d8c4f62..4f8cd1a2b 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -862,6 +862,7 @@ async def _clock_synchronize_scheduler(self) -> None: await self.clock_synchronize() except CancelledError: _LOGGER.debug("Clock sync scheduler cancelled for %s", self.name) + raise async def clock_synchronize(self) -> bool: """Synchronize clock. Returns true if successful.""" From 35d294a95e37365b6b387f338d48f719e74dc522 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Mon, 15 Sep 2025 09:34:33 +0200 Subject: [PATCH 04/92] add additional try/except --- plugwise_usb/nodes/circle.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 4f8cd1a2b..788f8c096 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -859,7 +859,10 @@ async def _clock_synchronize_scheduler(self) -> None: try: while True: await sleep(60) - await self.clock_synchronize() + try: + await self.clock_synchronize() + except Exception: + _LOGGER.exception("Clock synchronisation failed for %s", self.name) except CancelledError: _LOGGER.debug("Clock sync scheduler cancelled for %s", self.name) raise From 67e3ba99c893a494ae456573b791a70b82e7cb91 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Mon, 15 Sep 2025 09:47:11 +0200 Subject: [PATCH 05/92] create declaration for clock sync period and set to 1 hour --- plugwise_usb/nodes/circle.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 788f8c096..58e8dd892 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -74,7 +74,9 @@ # Default firmware if not known DEFAULT_FIRMWARE: Final = datetime(2008, 8, 26, 15, 46, tzinfo=UTC) -MAX_LOG_HOURS = DAY_IN_HOURS +MAX_LOG_HOURS: Final = DAY_IN_HOURS + +CLOCK_SYNC_PERIOD: Final = 3600 FuncT = TypeVar("FuncT", bound=Callable[..., Any]) _LOGGER = logging.getLogger(__name__) @@ -858,7 +860,7 @@ async def _clock_synchronize_scheduler(self) -> None: """Background task: periodically synchronize the clock until cancelled.""" try: while True: - await sleep(60) + await sleep(CLOCK_SYNC_PERIOD) try: await self.clock_synchronize() except Exception: From 6c22b952b203ec6781f80a01927b71030df6ce31 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Mon, 15 Sep 2025 09:50:07 +0200 Subject: [PATCH 06/92] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7273f586..7e29a4dee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v0.46.1 - 2025-09-25 - PR [337](https://github.com/plugwise/python-plugwise-usb/pull/337): Improve node removal, remove and reset the node as executed by Source, and remove the cache-file. +- PR [341](https://github.com/plugwise/python-plugwise-usb/pull/341): Schedule clock synchronization every 3600 seconds - PR [342](https://github.com/plugwise/python-plugwise-usb/pull/342): Improve node_type caching. - PR [343](https://github.com/plugwise/python-plugwise-usb/pull/343): Improve writing of cache-files. - PR [344](https://github.com/plugwise/python-plugwise-usb/pull/344): Don't store plus-device in nodetypes cache. From 131a90f9c614037638b7f5e6b18cc60c9f3f9a24 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Mon, 15 Sep 2025 18:54:46 +0200 Subject: [PATCH 07/92] fix clock offset detection. Original code did not properly check for negative offsets. --- plugwise_usb/nodes/circle.py | 8 +++----- plugwise_usb/nodes/circle_plus.py | 4 +--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 58e8dd892..00b2d0ce4 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -864,9 +864,9 @@ async def _clock_synchronize_scheduler(self) -> None: try: await self.clock_synchronize() except Exception: - _LOGGER.exception("Clock synchronisation failed for %s", self.name) + _LOGGER.exception("Clock synchronisation failed for %s", self.mac_in_str) except CancelledError: - _LOGGER.debug("Clock sync scheduler cancelled for %s", self.name) + _LOGGER.debug("Clock sync scheduler cancelled for %s", self.mac_in_str) raise async def clock_synchronize(self) -> bool: @@ -883,9 +883,7 @@ async def clock_synchronize(self) -> bool: tzinfo=UTC, ) clock_offset = clock_response.timestamp.replace(microsecond=0) - _dt_of_circle - if (clock_offset.seconds < MAX_TIME_DRIFT) or ( - clock_offset.seconds > -(MAX_TIME_DRIFT) - ): + if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True _LOGGER.info( "Reset clock of node %s because time has drifted %s sec", diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 66a52fcdb..a5938cd97 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -86,9 +86,7 @@ async def clock_synchronize(self) -> bool: tzinfo=UTC, ) clock_offset = clock_response.timestamp.replace(microsecond=0) - _dt_of_circle - if (clock_offset.seconds < MAX_TIME_DRIFT) or ( - clock_offset.seconds > -(MAX_TIME_DRIFT) - ): + if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True _LOGGER.info( "Reset realtime clock of node %s because time has drifted %s seconds while max drift is set to %s seconds)", From 943369060978bba556ca14b97c546f4eb80d4da3 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Mon, 15 Sep 2025 19:08:00 +0200 Subject: [PATCH 08/92] attribute error --- 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 00b2d0ce4..8783d41ef 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -864,9 +864,9 @@ async def _clock_synchronize_scheduler(self) -> None: try: await self.clock_synchronize() except Exception: - _LOGGER.exception("Clock synchronisation failed for %s", self.mac_in_str) + _LOGGER.exception("Clock synchronisation failed for %s", self._mac_in_str) except CancelledError: - _LOGGER.debug("Clock sync scheduler cancelled for %s", self.mac_in_str) + _LOGGER.debug("Clock sync scheduler cancelled for %s", self._mac_in_str) raise async def clock_synchronize(self) -> bool: From 42801eea09860c92e2a822ff3f0a23155a19d4b0 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Mon, 15 Sep 2025 19:09:39 +0200 Subject: [PATCH 09/92] improve logging of clock offset value --- plugwise_usb/nodes/circle.py | 2 +- plugwise_usb/nodes/circle_plus.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 8783d41ef..90822adc6 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -888,7 +888,7 @@ async def clock_synchronize(self) -> bool: _LOGGER.info( "Reset clock of node %s because time has drifted %s sec", self._mac_in_str, - str(clock_offset.seconds), + str(int(abs(clock_offset.total_seconds()))), ) if self._node_protocols is None: raise NodeError( diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index a5938cd97..672afb61d 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -91,7 +91,7 @@ async def clock_synchronize(self) -> bool: _LOGGER.info( "Reset realtime clock of node %s because time has drifted %s seconds while max drift is set to %s seconds)", self._node_info.mac, - str(clock_offset.seconds), + str(int(abs(clock_offset.total_seconds()))), str(MAX_TIME_DRIFT), ) clock_set_request = CirclePlusRealTimeClockSetRequest( From 77093fecc8c66f43ee770dd064d891bdcb7c28e4 Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Mon, 15 Sep 2025 19:22:09 +0200 Subject: [PATCH 10/92] CR: Add small jitter to prevent synchronization bursts --- 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 90822adc6..adb5761b3 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -860,11 +860,11 @@ async def _clock_synchronize_scheduler(self) -> None: """Background task: periodically synchronize the clock until cancelled.""" try: while True: - await sleep(CLOCK_SYNC_PERIOD) + await sleep(CLOCK_SYNC_PERIOD + (random.uniform(-5, 5))) try: await self.clock_synchronize() except Exception: - _LOGGER.exception("Clock synchronisation failed for %s", self._mac_in_str) + _LOGGER.exception("Clock synchronization failed for %s", self._mac_in_str) except CancelledError: _LOGGER.debug("Clock sync scheduler cancelled for %s", self._mac_in_str) raise From 6bdc7205778343e5350494d55161006b4192aa9b Mon Sep 17 00:00:00 2001 From: Marc Dirix Date: Mon, 15 Sep 2025 19:25:12 +0200 Subject: [PATCH 11/92] unifi log message between C+ and Circle --- plugwise_usb/nodes/circle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index adb5761b3..2978434c2 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -886,9 +886,10 @@ async def clock_synchronize(self) -> bool: if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True _LOGGER.info( - "Reset clock of node %s because time has drifted %s sec", + "Reset clock of node %s because time drifted %s seconds (max %s seconds)", self._mac_in_str, str(int(abs(clock_offset.total_seconds()))), + str(MAX_TIME_DRIFT), ) if self._node_protocols is None: raise NodeError( From 65d166a2051986a927c1e76595322374b5c95e04 Mon Sep 17 00:00:00 2001 From: autoruff Date: Mon, 15 Sep 2025 17:27:02 +0000 Subject: [PATCH 12/92] fixup: mdi_clock Python code reformatted using Ruff --- plugwise_usb/nodes/circle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 2978434c2..fe1a6c2a0 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -864,7 +864,9 @@ async def _clock_synchronize_scheduler(self) -> None: try: await self.clock_synchronize() except Exception: - _LOGGER.exception("Clock synchronization failed for %s", self._mac_in_str) + _LOGGER.exception( + "Clock synchronization failed for %s", self._mac_in_str + ) except CancelledError: _LOGGER.debug("Clock sync scheduler cancelled for %s", self._mac_in_str) raise From ffee1f12cab7e37803f0cbe84cef5ca96eef7d79 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 08:09:07 +0200 Subject: [PATCH 13/92] Add CirclePlusRealTimeClockSetRequest with ACK --- tests/stick_test_data.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 4d22e5c64..7f72db58a 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -604,6 +604,11 @@ + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 ), + b'\x05\x05\x03\x030028009876543210123430081800150925FE53\r\n': ( + "Circle+ Realtime set clock for 0098765432101234", + b"000000C1", # Success ack + b"0000" + b"00DF" + b"0098765432101234" # msg_id, clock_ack, mac + ), b"\x05\x05\x03\x03003E11111111111111111B8A\r\n": ( "clock for 0011111111111111", b"000000C1", # Success ack From 220e94d59ad02ef4fc97c0a577c32ab9a49b3cc6 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 08:15:44 +0200 Subject: [PATCH 14/92] Try with 00D7 --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 7f72db58a..6ba72da35 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -607,7 +607,7 @@ b'\x05\x05\x03\x030028009876543210123430081800150925FE53\r\n': ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack - b"0000" + b"00DF" + b"0098765432101234" # msg_id, clock_ack, mac + b"0000" + b"00D7" + b"0098765432101234" # msg_id, clock_ack, mac ), b"\x05\x05\x03\x03003E11111111111111111B8A\r\n": ( "clock for 0011111111111111", From 64afae644fb371086afdecaa7dd224b72fa95b7c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 08:21:14 +0200 Subject: [PATCH 15/92] Add missing comma --- tests/stick_test_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 6ba72da35..2d6a8f1e6 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -604,10 +604,10 @@ + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 ), - b'\x05\x05\x03\x030028009876543210123430081800150925FE53\r\n': ( + b"\x05\x05\x03\x030028009876543210123430081800150925FE53\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack - b"0000" + b"00D7" + b"0098765432101234" # msg_id, clock_ack, mac + b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac ), b"\x05\x05\x03\x03003E11111111111111111B8A\r\n": ( "clock for 0011111111111111", From af29876c2b6e29d0ee58a973ca9c3073e17f0d80 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 08:27:55 +0200 Subject: [PATCH 16/92] Freeze-time for test_node_relay_and_power --- tests/test_usb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_usb.py b/tests/test_usb.py index 59ce895f3..b1b316cb5 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -780,6 +780,7 @@ async def node_init_relay_state( ) ) + @freeze_time("2025-04-03 22:00:00") @pytest.mark.asyncio async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing discovery of nodes.""" From 9f0b17e0e09d945db02d874ec6ca359f52f62227 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 08:28:40 +0200 Subject: [PATCH 17/92] Update 0028-data from test-output --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 2d6a8f1e6..0c915a457 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -604,7 +604,7 @@ + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 ), - b"\x05\x05\x03\x030028009876543210123430081800150925FE53\r\n": ( + b"\x05\x05\x03\x030028009876543210123429210601160925FDA7\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac From e8ececc595b6722d8e3674b09b900d550c7b4140 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 18:03:23 +0200 Subject: [PATCH 18/92] Use freeze_time to fix the test-datetime --- tests/test_usb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index b1b316cb5..2fd779cb9 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -780,7 +780,7 @@ async def node_init_relay_state( ) ) - @freeze_time("2025-04-03 22:00:00") + @freeze_time("2025-04-03 22:00:00", real_asyncio=True) @pytest.mark.asyncio async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing discovery of nodes.""" From 5a08281e2744281a34dcb8c1af05cbf7b09feb33 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 18:45:49 +0200 Subject: [PATCH 19/92] Try --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 0c915a457..968fad42e 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -604,7 +604,7 @@ + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 ), - b"\x05\x05\x03\x030028009876543210123429210601160925FDA7\r\n": ( + b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac From 207157094568b0bf0311fddd02239798cde6bc4e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 18:49:10 +0200 Subject: [PATCH 20/92] Fix datetime returned by CirclePlus --- tests/stick_test_data.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 968fad42e..580e1635e 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -596,13 +596,13 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 + + bytes(("%%0%dd" % 2) % 0, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 0, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 18, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 20, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 1, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 6, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 24, pw_constants.UTF8), # noqa: UP031 ), b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", From 9d8d9e7ce2bee000d8c2a73ea67c1551cb5528d3 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 18:50:33 +0200 Subject: [PATCH 21/92] Try 2 --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 580e1635e..ae56b503d 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -604,7 +604,7 @@ + bytes(("%%0%dd" % 2) % 6, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % 24, pw_constants.UTF8), # noqa: UP031 ), - b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( + b"\x05\x05\x03\x0300280098765432101234354916011609254EC4\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac From ca738ed1ec7dcbd46ad9cdab02d5ba1d7c09a887 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 18:53:58 +0200 Subject: [PATCH 22/92] Try 3 --- tests/stick_test_data.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index ae56b503d..a7c5c2cde 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -596,15 +596,16 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac + # 2025-04-03 22:00:00 + bytes(("%%0%dd" % 2) % 0, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % 0, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 18, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 20, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 1, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 6, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 24, pw_constants.UTF8), # noqa: UP031 + + bytes(("%%0%dd" % 2) % 22, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 5, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 3, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 4, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % 25, pw_constants.UTF8), # noqa: UP031 ), - b"\x05\x05\x03\x0300280098765432101234354916011609254EC4\r\n": ( + b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac From f1ccb0a9da54d685eb0833352ef08f79004e3e4f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 19:09:25 +0200 Subject: [PATCH 23/92] Debug datetimes --- 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 672afb61d..e9572936c 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -85,6 +85,8 @@ async def clock_synchronize(self) -> bool: microsecond=0, tzinfo=UTC, ) + _LOGGER.debug("HOI circle+ clock=%s", clock_response.timestamp.replace(microsecond=0)) + _LOGGER.debug("HOI _dt_of_circle=%s", _dt_of_circle) clock_offset = clock_response.timestamp.replace(microsecond=0) - _dt_of_circle if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True From 0e032f81acab0db040677c0586fe72e318eaca22 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 19:14:09 +0200 Subject: [PATCH 24/92] Move freeze_time to test_energy_circle() --- tests/test_usb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_usb.py b/tests/test_usb.py index 2fd779cb9..23a2058b4 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -909,6 +909,7 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No await stick.disconnect() + @freeze_time("2025-04-03 22:00:00", real_asyncio=True) @pytest.mark.asyncio async def test_energy_circle(self, monkeypatch: pytest.MonkeyPatch) -> None: """Testing energy retrieval.""" From 1bc98096c909064c41127259cdef00449b34b9da Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 19:19:26 +0200 Subject: [PATCH 25/92] Add missing import --- plugwise_usb/nodes/circle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index fe1a6c2a0..a876e366c 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -9,6 +9,7 @@ from functools import wraps import logging from math import ceil +import random from typing import Any, Final, TypeVar, cast from ..api import ( From a208f0ff1f3e264123592213deacc1a84afc9ede Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 19:22:18 +0200 Subject: [PATCH 26/92] Try 4 --- tests/test_usb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index 23a2058b4..e20fb76c1 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -909,7 +909,7 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No await stick.disconnect() - @freeze_time("2025-04-03 22:00:00", real_asyncio=True) + # @freeze_time("2025-04-03 22:00:00", real_asyncio=True) @pytest.mark.asyncio async def test_energy_circle(self, monkeypatch: pytest.MonkeyPatch) -> None: """Testing energy retrieval.""" From 5242ce5be4afcd4c2bb17fc708f38cd7893d2e04 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 19:25:06 +0200 Subject: [PATCH 27/92] Change time bij 30 secs --- tests/test_usb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index e20fb76c1..550d1a2db 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -780,7 +780,7 @@ async def node_init_relay_state( ) ) - @freeze_time("2025-04-03 22:00:00", real_asyncio=True) + @freeze_time("2025-04-03 22:00:30", real_asyncio=True) @pytest.mark.asyncio async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing discovery of nodes.""" From 9ee3c0eb873b84fb2153cf08dddcd131ab0ed9fe Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Sep 2025 19:26:37 +0200 Subject: [PATCH 28/92] Fix 0028 datetime --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index a7c5c2cde..d4cbc3edb 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -605,7 +605,7 @@ + bytes(("%%0%dd" % 2) % 4, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % 25, pw_constants.UTF8), # noqa: UP031 ), - b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( + b"\x05\x05\x03\x030028009876543210123430002203030425107C\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac From 59a5afc331b0c6f286dc6cd71b9ce2a6b9115fe2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 17 Sep 2025 07:57:44 +0200 Subject: [PATCH 29/92] Get proper datetime to compare the received datetime with --- plugwise_usb/nodes/circle_plus.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index e9572936c..070ff66ed 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -67,6 +67,7 @@ async def load(self) -> bool: async def clock_synchronize(self) -> bool: """Synchronize realtime clock. Returns true if successful.""" + _dt_req_to_circle: datetime = datetime.now(tz=UTC) clock_request = CirclePlusRealTimeClockGetRequest( self._send, self._mac_in_bytes ) @@ -78,16 +79,16 @@ async def clock_synchronize(self) -> bool: return False await self._available_update_state(True, clock_response.timestamp) - _dt_of_circle: datetime = datetime.now(tz=UTC).replace( - hour=clock_response.time.value.hour, - minute=clock_response.time.value.minute, - second=clock_response.time.value.second, - microsecond=0, - tzinfo=UTC, - ) - _LOGGER.debug("HOI circle+ clock=%s", clock_response.timestamp.replace(microsecond=0)) - _LOGGER.debug("HOI _dt_of_circle=%s", _dt_of_circle) - clock_offset = clock_response.timestamp.replace(microsecond=0) - _dt_of_circle + # _dt_req_to_circle: datetime = datetime.now(tz=UTC).replace( + # hour=clock_response.time.value.hour, + # minute=clock_response.time.value.minute, + # second=clock_response.time.value.second, + # microsecond=0, + # tzinfo=UTC, + # ) + _LOGGER.debug("HOI circle+ clock=%s", clock_response.timestamp) + _LOGGER.debug("HOI _dt_req_to_circle=%s", _dt_req_to_circle) + clock_offset = clock_response.timestamp.replace(microsecond=0) - _dt_req_to_circle if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True _LOGGER.info( From b4592754d5bc46be011ffd4b992c03f8ed26d9a4 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 17 Sep 2025 08:09:48 +0200 Subject: [PATCH 30/92] Ruffed --- plugwise_usb/nodes/circle_plus.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 070ff66ed..79fdb0a9c 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -88,7 +88,9 @@ async def clock_synchronize(self) -> bool: # ) _LOGGER.debug("HOI circle+ clock=%s", clock_response.timestamp) _LOGGER.debug("HOI _dt_req_to_circle=%s", _dt_req_to_circle) - clock_offset = clock_response.timestamp.replace(microsecond=0) - _dt_req_to_circle + clock_offset = ( + clock_response.timestamp.replace(microsecond=0) - _dt_req_to_circle + ) if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True _LOGGER.info( From c750b975eadcbf8f6aae40a2caefa962a44d8044 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 17 Sep 2025 08:10:29 +0200 Subject: [PATCH 31/92] Try --- tests/stick_test_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index d4cbc3edb..6d7011c78 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -596,8 +596,8 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - # 2025-04-03 22:00:00 - + bytes(("%%0%dd" % 2) % 0, pw_constants.UTF8) # noqa: UP031 + # 2025-04-03 22:00:30 + + bytes(("%%0%dd" % 2) % 30, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % 0, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % 22, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % 5, pw_constants.UTF8) # noqa: UP031 From d9dede4ac46b04f863cfe1e3133f25fb6f1b0745 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 17 Sep 2025 08:26:08 +0200 Subject: [PATCH 32/92] Freeze time a the full hour --- tests/test_usb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index 550d1a2db..e20fb76c1 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -780,7 +780,7 @@ async def node_init_relay_state( ) ) - @freeze_time("2025-04-03 22:00:30", real_asyncio=True) + @freeze_time("2025-04-03 22:00:00", real_asyncio=True) @pytest.mark.asyncio async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing discovery of nodes.""" From 385a76a693d5733d7f6c476f82fd4b84f2812b61 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 17 Sep 2025 18:39:12 +0200 Subject: [PATCH 33/92] Revert back --- plugwise_usb/nodes/circle_plus.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 79fdb0a9c..f54e02feb 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -67,7 +67,7 @@ async def load(self) -> bool: async def clock_synchronize(self) -> bool: """Synchronize realtime clock. Returns true if successful.""" - _dt_req_to_circle: datetime = datetime.now(tz=UTC) + #_dt_req_to_circle: datetime = datetime.now(tz=UTC) clock_request = CirclePlusRealTimeClockGetRequest( self._send, self._mac_in_bytes ) @@ -79,13 +79,13 @@ async def clock_synchronize(self) -> bool: return False await self._available_update_state(True, clock_response.timestamp) - # _dt_req_to_circle: datetime = datetime.now(tz=UTC).replace( - # hour=clock_response.time.value.hour, - # minute=clock_response.time.value.minute, - # second=clock_response.time.value.second, - # microsecond=0, - # tzinfo=UTC, - # ) + _dt_req_to_circle: datetime = datetime.now(tz=UTC).replace( + hour=clock_response.time.value.hour, + minute=clock_response.time.value.minute, + second=clock_response.time.value.second, + microsecond=0, + tzinfo=UTC, + ) _LOGGER.debug("HOI circle+ clock=%s", clock_response.timestamp) _LOGGER.debug("HOI _dt_req_to_circle=%s", _dt_req_to_circle) clock_offset = ( From add25dc9aa61e57c07a7abca67bd6f7a1f2e6b88 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 17 Sep 2025 18:41:17 +0200 Subject: [PATCH 34/92] Update 0028-response to match change --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 6d7011c78..eabbd47aa 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -605,7 +605,7 @@ + bytes(("%%0%dd" % 2) % 4, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % 25, pw_constants.UTF8), # noqa: UP031 ), - b"\x05\x05\x03\x030028009876543210123430002203030425107C\r\n": ( + b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac From 332f27dbe503d7075dfe490145628ae5913a406f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 17 Sep 2025 19:55:10 +0200 Subject: [PATCH 35/92] Shorten, improve var names --- plugwise_usb/nodes/circle_plus.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index f54e02feb..0625d5bf4 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -67,29 +67,28 @@ async def load(self) -> bool: async def clock_synchronize(self) -> bool: """Synchronize realtime clock. Returns true if successful.""" - #_dt_req_to_circle: datetime = datetime.now(tz=UTC) - clock_request = CirclePlusRealTimeClockGetRequest( + request = CirclePlusRealTimeClockGetRequest( self._send, self._mac_in_bytes ) - if (clock_response := await clock_request.send()) is None: + if (response := await request.send()) is None: _LOGGER.debug( "No response for async_realtime_clock_synchronize() for %s", self.mac ) await self._available_update_state(False) return False - await self._available_update_state(True, clock_response.timestamp) + await self._available_update_state(True, response.timestamp) - _dt_req_to_circle: datetime = datetime.now(tz=UTC).replace( - hour=clock_response.time.value.hour, - minute=clock_response.time.value.minute, - second=clock_response.time.value.second, + circle_plus_dt: datetime = datetime.now(tz=UTC).replace( + hour=response.time.value.hour, + minute=response.time.value.minute, + second=response.time.value.second, microsecond=0, tzinfo=UTC, ) - _LOGGER.debug("HOI circle+ clock=%s", clock_response.timestamp) - _LOGGER.debug("HOI _dt_req_to_circle=%s", _dt_req_to_circle) + _LOGGER.debug("HOI circle+ clock=%s", circle_plus_dt) + _LOGGER.debug("HOI response timestamp=%s", response.timestamp) clock_offset = ( - clock_response.timestamp.replace(microsecond=0) - _dt_req_to_circle + response.timestamp.replace(microsecond=0) - circle_plus_dt ) if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True @@ -99,10 +98,10 @@ async def clock_synchronize(self) -> bool: str(int(abs(clock_offset.total_seconds()))), str(MAX_TIME_DRIFT), ) - clock_set_request = CirclePlusRealTimeClockSetRequest( + set_request = CirclePlusRealTimeClockSetRequest( self._send, self._mac_in_bytes, datetime.now(tz=UTC) ) - if (node_response := await clock_set_request.send()) is not None: + if (node_response := await set_request.send()) is not None: return node_response.ack_id == NodeResponseType.CLOCK_ACCEPTED _LOGGER.warning( "Failed to (re)set the internal realtime clock of %s", From b31e15b4f739a4d1ed47a12199e8a2e088868435 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 18 Sep 2025 08:09:01 +0200 Subject: [PATCH 36/92] Try adding a 2nd optional response --- tests/stick_test_data.py | 29 ++++++++++++++++++++--------- tests/test_usb.py | 16 ++++++++-------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index eabbd47aa..7669f1495 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -596,15 +596,26 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - # 2025-04-03 22:00:30 - + bytes(("%%0%dd" % 2) % 30, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 0, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 22, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 5, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 3, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 4, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % 25, pw_constants.UTF8), # noqa: UP031 - ), + # datetime.now() + + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 + ), + # b"003A" # msg_id + # + b"0098765432101234" # mac + # # 2025-04-03 22:00:30 + # + bytes(("%%0%dd" % 2) % 30, pw_constants.UTF8) # noqa: UP031 + # + bytes(("%%0%dd" % 2) % 0, pw_constants.UTF8) # noqa: UP031 + # + bytes(("%%0%dd" % 2) % 22, pw_constants.UTF8) # noqa: UP031 + # + bytes(("%%0%dd" % 2) % 5, pw_constants.UTF8) # noqa: UP031 + # + bytes(("%%0%dd" % 2) % 3, pw_constants.UTF8) # noqa: UP031 + # + bytes(("%%0%dd" % 2) % 4, pw_constants.UTF8) # noqa: UP031 + # + bytes(("%%0%dd" % 2) % 25, pw_constants.UTF8), # noqa: UP031 + #), b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack diff --git a/tests/test_usb.py b/tests/test_usb.py index e20fb76c1..5759256f0 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -101,11 +101,11 @@ def write(self, data: bytes) -> None: """Write data back to system.""" log = None ack = None - response = None + response_1 = None if data in self._processed and self._second_response is not None: - log, ack, response = self._second_response.get(data, (None, None, None)) + log, ack, response_1 = self._second_response.get(data, (None, None, None)) if log is None and self._first_response is not None: - log, ack, response = self._first_response.get(data, (None, None, None)) + log, ack, response_1 = self._first_response.get(data, (None, None, None)) if log is None: resp = pw_userdata.PARTLY_RESPONSE_MESSAGES.get( data[:24], (None, None, None) @@ -113,21 +113,21 @@ def write(self, data: bytes) -> None: if resp is None: _LOGGER.debug("No msg response for %s", str(data)) return - log, ack, response = resp + log, ack, response_1 = resp if ack is None: _LOGGER.debug("No ack response for %s", str(data)) return self._seq_id = inc_seq_id(self._seq_id) - if response and self._msg == 0: - self.message_response_at_once(ack, response, self._seq_id) + if response_1 and self._msg == 0: + self.message_response_at_once(ack, response_1, self._seq_id) self._processed.append(data) else: self.message_response(ack, self._seq_id) self._processed.append(data) - if response is None or self._closing: + if response_1 is None or self._closing: return - self._loop.create_task(self._delayed_response(response, self._seq_id)) + self._loop.create_task(self._delayed_response(response_1, self._seq_id)) self._msg += 1 async def _delayed_response(self, data: bytes, seq_id: bytes) -> None: From bf4454291c6b95fc7528ea1e3cb64d98afc40dea Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 18 Sep 2025 18:04:59 +0200 Subject: [PATCH 37/92] Try 2 --- tests/test_usb.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index 5759256f0..3bdf09907 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -101,11 +101,11 @@ def write(self, data: bytes) -> None: """Write data back to system.""" log = None ack = None - response_1 = None + response = None if data in self._processed and self._second_response is not None: - log, ack, response_1 = self._second_response.get(data, (None, None, None)) + log, ack, response = self._second_response.get(data, (None, None, None)) if log is None and self._first_response is not None: - log, ack, response_1 = self._first_response.get(data, (None, None, None)) + log, ack, response = self._first_response.get(data, (None, None, None)) if log is None: resp = pw_userdata.PARTLY_RESPONSE_MESSAGES.get( data[:24], (None, None, None) @@ -113,21 +113,25 @@ def write(self, data: bytes) -> None: if resp is None: _LOGGER.debug("No msg response for %s", str(data)) return - log, ack, response_1 = resp + log, ack, response = resp + if log is not None: + _LOGGER.debug("HOI log=%s", log) + _LOGGER.debug("HOI ack=%s", ack) + _LOGGER.debug("HOI response=%s", response) if ack is None: _LOGGER.debug("No ack response for %s", str(data)) return self._seq_id = inc_seq_id(self._seq_id) - if response_1 and self._msg == 0: - self.message_response_at_once(ack, response_1, self._seq_id) + if response and self._msg == 0: + self.message_response_at_once(ack, response, self._seq_id) self._processed.append(data) else: self.message_response(ack, self._seq_id) self._processed.append(data) - if response_1 is None or self._closing: + if response is None or self._closing: return - self._loop.create_task(self._delayed_response(response_1, self._seq_id)) + self._loop.create_task(self._delayed_response(response, self._seq_id)) self._msg += 1 async def _delayed_response(self, data: bytes, seq_id: bytes) -> None: From a970073e513ef92d31ab37ed77d0f6de6c5fec0d Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Sep 2025 08:26:00 +0200 Subject: [PATCH 38/92] Try 3 --- tests/stick_test_data.py | 38 ++++++++++++++++++-------------------- tests/test_usb.py | 2 +- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 7669f1495..5b5f7ad83 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -7,6 +7,7 @@ # test using utc timezone utc_now = datetime.now(tz=UTC).replace(tzinfo=UTC) +utc_now_offset = datetime.now(tz=UTC).replace(tzinfo=UTC) + timedelta(seconds=30) # generate energy log timestamps with fixed hour timestamp used in tests @@ -597,26 +598,23 @@ b"003A" # msg_id + b"0098765432101234" # mac # datetime.now() - + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 - ), - # b"003A" # msg_id - # + b"0098765432101234" # mac - # # 2025-04-03 22:00:30 - # + bytes(("%%0%dd" % 2) % 30, pw_constants.UTF8) # noqa: UP031 - # + bytes(("%%0%dd" % 2) % 0, pw_constants.UTF8) # noqa: UP031 - # + bytes(("%%0%dd" % 2) % 22, pw_constants.UTF8) # noqa: UP031 - # + bytes(("%%0%dd" % 2) % 5, pw_constants.UTF8) # noqa: UP031 - # + bytes(("%%0%dd" % 2) % 3, pw_constants.UTF8) # noqa: UP031 - # + bytes(("%%0%dd" % 2) % 4, pw_constants.UTF8) # noqa: UP031 - # + bytes(("%%0%dd" % 2) % 25, pw_constants.UTF8), # noqa: UP031 - #), - b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( + + bytes(("%%0%dd" % 2) % utc_now_offset.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.day, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.month, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % (utc_now_offset.year - 2000), pw_constants.UTF8), # noqa: UP031 + ), + b"\x05\x05\x03\x0300280098765432101234" + + bytes(("%%0%dd" % 2) % utc_now_offset.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.day, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now_offset.month, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % (utc_now_offset.year - 2000), pw_constants.UTF8) # noqa: UP031 + + b"\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac diff --git a/tests/test_usb.py b/tests/test_usb.py index 3bdf09907..aa4746e14 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -784,7 +784,7 @@ async def node_init_relay_state( ) ) - @freeze_time("2025-04-03 22:00:00", real_asyncio=True) + # @freeze_time("2025-04-03 22:00:00", real_asyncio=True) @pytest.mark.asyncio async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing discovery of nodes.""" From 2ac6626a1276f70e1a17e874afa46edfbe0745df Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 11:36:00 +0200 Subject: [PATCH 39/92] CirclePlus: handle time-diff larger than 24hrs --- plugwise_usb/nodes/circle_plus.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 0625d5bf4..e4e9a862d 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -78,17 +78,19 @@ async def clock_synchronize(self) -> bool: return False await self._available_update_state(True, response.timestamp) - circle_plus_dt: datetime = datetime.now(tz=UTC).replace( - hour=response.time.value.hour, + dt_now = datetime.now(tz=UTC) + days_diff = response.day_of_week - dt_now.weekday() + circle_plus_ts: datetime = dt_now.replace( + hour=response.time.value.hour + (24 * days_diff), minute=response.time.value.minute, second=response.time.value.second, microsecond=0, tzinfo=UTC, ) - _LOGGER.debug("HOI circle+ clock=%s", circle_plus_dt) + _LOGGER.debug("HOI circle+ clock=%s", circle_plus_ts) _LOGGER.debug("HOI response timestamp=%s", response.timestamp) clock_offset = ( - response.timestamp.replace(microsecond=0) - circle_plus_dt + response.timestamp.replace(microsecond=0) - circle_plus_ts ) if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True From 37f4c5ca08e723f958b1a048c6d34f24e593d763 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 11:41:26 +0200 Subject: [PATCH 40/92] Force int via value property --- plugwise_usb/nodes/circle_plus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index e4e9a862d..dfcee3d69 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -79,7 +79,7 @@ async def clock_synchronize(self) -> bool: await self._available_update_state(True, response.timestamp) dt_now = datetime.now(tz=UTC) - days_diff = response.day_of_week - dt_now.weekday() + days_diff = response.day_of_week.value - dt_now.weekday() circle_plus_ts: datetime = dt_now.replace( hour=response.time.value.hour + (24 * days_diff), minute=response.time.value.minute, From ea89f5640f5fb2a13c6ac8e0c14c132cc68551b9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 11:45:15 +0200 Subject: [PATCH 41/92] Test larger offset --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 5b5f7ad83..e031e09ce 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -7,7 +7,7 @@ # test using utc timezone utc_now = datetime.now(tz=UTC).replace(tzinfo=UTC) -utc_now_offset = datetime.now(tz=UTC).replace(tzinfo=UTC) + timedelta(seconds=30) +utc_now_offset = datetime.now(tz=UTC).replace(tzinfo=UTC) + timedelta(days=1, hours=4, seconds=30) # generate energy log timestamps with fixed hour timestamp used in tests From 68090b25f07e945e7c54d05c17e1408252fa4c2f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 11:50:20 +0200 Subject: [PATCH 42/92] Fix adding/deleting day(s) --- plugwise_usb/nodes/circle_plus.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index dfcee3d69..d7e3f7f87 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -81,7 +81,8 @@ async def clock_synchronize(self) -> bool: dt_now = datetime.now(tz=UTC) days_diff = response.day_of_week.value - dt_now.weekday() circle_plus_ts: datetime = dt_now.replace( - hour=response.time.value.hour + (24 * days_diff), + day=dt_now.day - days_diff, + hour=response.time.value.hour, minute=response.time.value.minute, second=response.time.value.second, microsecond=0, From b5b31de93d28313cdefde8e9414a51b0c1ef30fd Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 11:53:59 +0200 Subject: [PATCH 43/92] Full naming --- 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 d7e3f7f87..09987d807 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -80,7 +80,7 @@ async def clock_synchronize(self) -> bool: dt_now = datetime.now(tz=UTC) days_diff = response.day_of_week.value - dt_now.weekday() - circle_plus_ts: datetime = dt_now.replace( + circle_plus_timestamp: datetime = dt_now.replace( day=dt_now.day - days_diff, hour=response.time.value.hour, minute=response.time.value.minute, @@ -88,10 +88,10 @@ async def clock_synchronize(self) -> bool: microsecond=0, tzinfo=UTC, ) - _LOGGER.debug("HOI circle+ clock=%s", circle_plus_ts) + _LOGGER.debug("HOI circle+ clock=%s", circle_plus_timestamp) _LOGGER.debug("HOI response timestamp=%s", response.timestamp) clock_offset = ( - response.timestamp.replace(microsecond=0) - circle_plus_ts + response.timestamp.replace(microsecond=0) - circle_plus_timestamp ) if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True From 58af3f47ec398adb4c9f0a1065b46593c9c03dbf Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 12:01:14 +0200 Subject: [PATCH 44/92] Line up circle-clock-sync code --- plugwise_usb/nodes/circle.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index a876e366c..8ca9a1be5 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -874,18 +874,22 @@ async def _clock_synchronize_scheduler(self) -> None: async def clock_synchronize(self) -> bool: """Synchronize clock. Returns true if successful.""" - get_clock_request = CircleClockGetRequest(self._send, self._mac_in_bytes) - clock_response = await get_clock_request.send() - if clock_response is None or clock_response.timestamp is None: + request = CircleClockGetRequest(self._send, self._mac_in_bytes) + response = await request.send() + if response is None or response.timestamp is None: return False - _dt_of_circle = datetime.now(tz=UTC).replace( - hour=clock_response.time.hour.value, - minute=clock_response.time.minute.value, - second=clock_response.time.second.value, + + dt_now = datetime.now(tz=UTC) + days_diff = response.day_of_week.value - dt_now.weekday() + circle_timestamp = dt_now.replace( + day=dt_now.day - days_diff, + hour=response.time.value.hour, + minute=response.time.value.minute, + second=response.time.value.second, microsecond=0, tzinfo=UTC, ) - clock_offset = clock_response.timestamp.replace(microsecond=0) - _dt_of_circle + clock_offset = response.timestamp.replace(microsecond=0) - circle_timestamp if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True _LOGGER.info( @@ -898,13 +902,14 @@ async def clock_synchronize(self) -> bool: raise NodeError( "Unable to synchronize clock en when protocol version is unknown" ) - set_clock_request = CircleClockSetRequest( + + set_request = CircleClockSetRequest( self._send, self._mac_in_bytes, datetime.now(tz=UTC), self._node_protocols.max, ) - if (node_response := await set_clock_request.send()) is None: + if (node_response := await set_request.send()) is None: _LOGGER.warning( "Failed to (re)set the internal clock of %s", self.name, From f8833b81ad6dbc76d796b20e44b7a804796a77dd Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 12:04:35 +0200 Subject: [PATCH 45/92] Debug weekday to large difference --- 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 09987d807..90fee04d3 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -79,6 +79,8 @@ async def clock_synchronize(self) -> bool: await self._available_update_state(True, response.timestamp) dt_now = datetime.now(tz=UTC) + _LOGGER.debug("HOI dt_now weekday=%s", dt_now.weekday()) + _LOGGER.debug("HOI circle+ day_of_week=%s", response.day_of_week.value) days_diff = response.day_of_week.value - dt_now.weekday() circle_plus_timestamp: datetime = dt_now.replace( day=dt_now.day - days_diff, From 66ea7c14b38a156f1bb21b474a1915f5ad2b9aaa Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 16:08:54 +0200 Subject: [PATCH 46/92] Properly calc resulting day value --- plugwise_usb/nodes/circle_plus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 90fee04d3..fe2e7be92 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -81,9 +81,9 @@ async def clock_synchronize(self) -> bool: dt_now = datetime.now(tz=UTC) _LOGGER.debug("HOI dt_now weekday=%s", dt_now.weekday()) _LOGGER.debug("HOI circle+ day_of_week=%s", response.day_of_week.value) - days_diff = response.day_of_week.value - dt_now.weekday() + days_diff = (response.day_of_week.value - dt_now.weekday()) % 7 circle_plus_timestamp: datetime = dt_now.replace( - day=dt_now.day - days_diff, + day=(dt_now.day + days_diff), hour=response.time.value.hour, minute=response.time.value.minute, second=response.time.value.second, From 466d008d28b6da3394fbaf261b7bcade7a91db8c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 16:33:32 +0200 Subject: [PATCH 47/92] Update circle too --- plugwise_usb/nodes/circle.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 8ca9a1be5..a33090ac3 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -880,9 +880,9 @@ async def clock_synchronize(self) -> bool: return False dt_now = datetime.now(tz=UTC) - days_diff = response.day_of_week.value - dt_now.weekday() - circle_timestamp = dt_now.replace( - day=dt_now.day - days_diff, + days_diff = (response.day_of_week.value - dt_now.weekday()) % 7 + circle_plus_timestamp: datetime = dt_now.replace( + day=dt_now.day + days_diff, hour=response.time.value.hour, minute=response.time.value.minute, second=response.time.value.second, @@ -892,6 +892,7 @@ async def clock_synchronize(self) -> bool: clock_offset = response.timestamp.replace(microsecond=0) - circle_timestamp if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True + _LOGGER.info( "Reset clock of node %s because time drifted %s seconds (max %s seconds)", self._mac_in_str, From 1100f32a032ff1c396f4e57d953da7659070bee5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 16:35:56 +0200 Subject: [PATCH 48/92] Clean up extra debug-logging --- plugwise_usb/nodes/circle_plus.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index fe2e7be92..4e3745fb1 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -79,8 +79,6 @@ async def clock_synchronize(self) -> bool: await self._available_update_state(True, response.timestamp) dt_now = datetime.now(tz=UTC) - _LOGGER.debug("HOI dt_now weekday=%s", dt_now.weekday()) - _LOGGER.debug("HOI circle+ day_of_week=%s", response.day_of_week.value) days_diff = (response.day_of_week.value - dt_now.weekday()) % 7 circle_plus_timestamp: datetime = dt_now.replace( day=(dt_now.day + days_diff), @@ -90,13 +88,12 @@ async def clock_synchronize(self) -> bool: microsecond=0, tzinfo=UTC, ) - _LOGGER.debug("HOI circle+ clock=%s", circle_plus_timestamp) - _LOGGER.debug("HOI response timestamp=%s", response.timestamp) clock_offset = ( response.timestamp.replace(microsecond=0) - circle_plus_timestamp ) if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True + _LOGGER.info( "Reset realtime clock of node %s because time has drifted %s seconds while max drift is set to %s seconds)", self._node_info.mac, From 00d8ab2d96b37f954616775cf172c02642b2171e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 16:38:42 +0200 Subject: [PATCH 49/92] Line up circle code in detail --- plugwise_usb/nodes/circle.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index a33090ac3..d4e338278 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -910,14 +910,12 @@ async def clock_synchronize(self) -> bool: datetime.now(tz=UTC), self._node_protocols.max, ) - if (node_response := await set_request.send()) is None: - _LOGGER.warning( - "Failed to (re)set the internal clock of %s", - self.name, - ) - return False - if node_response.ack_id == NodeResponseType.CLOCK_ACCEPTED: - return True + if (node_response := await set_request.send()) is not None: + return node_response.ack_id == NodeResponseType.CLOCK_ACCEPTED + _LOGGER.warning( + "Failed to (re)set the internal realtime clock of %s", + self.name, + ) return False async def load(self) -> None: From 698b531d77c8ba8e31c078d4142cd43cfa1ab3c2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 16:41:39 +0200 Subject: [PATCH 50/92] Set debug-level to warning, line up messages --- plugwise_usb/nodes/circle.py | 6 +++--- plugwise_usb/nodes/circle_plus.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index d4e338278..cf208ab14 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -893,9 +893,9 @@ async def clock_synchronize(self) -> bool: if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True - _LOGGER.info( - "Reset clock of node %s because time drifted %s seconds (max %s seconds)", - self._mac_in_str, + _LOGGER.warning( + "Reset realtime clock of node %s because time drifted %s seconds (max %s seconds)", + self.name, str(int(abs(clock_offset.total_seconds()))), str(MAX_TIME_DRIFT), ) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 4e3745fb1..70cd3e0a9 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -94,9 +94,9 @@ async def clock_synchronize(self) -> bool: if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True - _LOGGER.info( + _LOGGER.warning( "Reset realtime clock of node %s because time has drifted %s seconds while max drift is set to %s seconds)", - self._node_info.mac, + self.name, str(int(abs(clock_offset.total_seconds()))), str(MAX_TIME_DRIFT), ) From 903425949e50cf6bcd15f8ac83f61c312b135fcb Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 16:44:37 +0200 Subject: [PATCH 51/92] Revert node id in warning logs --- plugwise_usb/nodes/circle.py | 2 +- plugwise_usb/nodes/circle_plus.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index cf208ab14..f1a643f63 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -895,7 +895,7 @@ async def clock_synchronize(self) -> bool: _LOGGER.warning( "Reset realtime clock of node %s because time drifted %s seconds (max %s seconds)", - self.name, + self._mac_in_str, str(int(abs(clock_offset.total_seconds()))), str(MAX_TIME_DRIFT), ) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 70cd3e0a9..dfeb74af3 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -96,7 +96,7 @@ async def clock_synchronize(self) -> bool: _LOGGER.warning( "Reset realtime clock of node %s because time has drifted %s seconds while max drift is set to %s seconds)", - self.name, + self._mac_in_str, str(int(abs(clock_offset.total_seconds()))), str(MAX_TIME_DRIFT), ) From b8e3ecc5ed47d2faa0bc9952af06c0bccbe12055 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 16:48:30 +0200 Subject: [PATCH 52/92] Revert test-updates --- tests/stick_test_data.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index e031e09ce..69123b00f 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -598,23 +598,15 @@ b"003A" # msg_id + b"0098765432101234" # mac # datetime.now() - + bytes(("%%0%dd" % 2) % utc_now_offset.second, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.weekday(), pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.day, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.month, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % (utc_now_offset.year - 2000), pw_constants.UTF8), # noqa: UP031 - ), - b"\x05\x05\x03\x0300280098765432101234" - + bytes(("%%0%dd" % 2) % utc_now_offset.second, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.weekday(), pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.day, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now_offset.month, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % (utc_now_offset.year - 2000), pw_constants.UTF8) # noqa: UP031 - + b"\r\n": ( + + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 + ), + b"\x05\x05\x03\x030028009876543210123452441406210925D23C\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac From e11ecd6f378e9f7c4d611b5573d394cebcd4e728 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 16:50:28 +0200 Subject: [PATCH 53/92] More logging clean up --- tests/test_usb.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_usb.py b/tests/test_usb.py index aa4746e14..94cdde966 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -114,10 +114,6 @@ def write(self, data: bytes) -> None: _LOGGER.debug("No msg response for %s", str(data)) return log, ack, response = resp - if log is not None: - _LOGGER.debug("HOI log=%s", log) - _LOGGER.debug("HOI ack=%s", ack) - _LOGGER.debug("HOI response=%s", response) if ack is None: _LOGGER.debug("No ack response for %s", str(data)) return From 439878b5b31f61265e1883e8c41858ee97ba2887 Mon Sep 17 00:00:00 2001 From: autoruff Date: Sun, 21 Sep 2025 14:52:04 +0000 Subject: [PATCH 54/92] fixup: test_bouwew Python code reformatted using Ruff --- plugwise_usb/nodes/circle.py | 2 +- plugwise_usb/nodes/circle_plus.py | 10 +++------- tests/stick_test_data.py | 4 +++- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index f1a643f63..e1b62ac92 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -882,7 +882,7 @@ async def clock_synchronize(self) -> bool: dt_now = datetime.now(tz=UTC) days_diff = (response.day_of_week.value - dt_now.weekday()) % 7 circle_plus_timestamp: datetime = dt_now.replace( - day=dt_now.day + days_diff, + day=dt_now.day + days_diff, hour=response.time.value.hour, minute=response.time.value.minute, second=response.time.value.second, diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index dfeb74af3..13a0fda9a 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -67,9 +67,7 @@ async def load(self) -> bool: async def clock_synchronize(self) -> bool: """Synchronize realtime clock. Returns true if successful.""" - request = CirclePlusRealTimeClockGetRequest( - self._send, self._mac_in_bytes - ) + request = CirclePlusRealTimeClockGetRequest(self._send, self._mac_in_bytes) if (response := await request.send()) is None: _LOGGER.debug( "No response for async_realtime_clock_synchronize() for %s", self.mac @@ -81,16 +79,14 @@ async def clock_synchronize(self) -> bool: dt_now = datetime.now(tz=UTC) days_diff = (response.day_of_week.value - dt_now.weekday()) % 7 circle_plus_timestamp: datetime = dt_now.replace( - day=(dt_now.day + days_diff), + day=(dt_now.day + days_diff), hour=response.time.value.hour, minute=response.time.value.minute, second=response.time.value.second, microsecond=0, tzinfo=UTC, ) - clock_offset = ( - response.timestamp.replace(microsecond=0) - circle_plus_timestamp - ) + clock_offset = response.timestamp.replace(microsecond=0) - circle_plus_timestamp if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 69123b00f..cf8d610d0 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -7,7 +7,9 @@ # test using utc timezone utc_now = datetime.now(tz=UTC).replace(tzinfo=UTC) -utc_now_offset = datetime.now(tz=UTC).replace(tzinfo=UTC) + timedelta(days=1, hours=4, seconds=30) +utc_now_offset = datetime.now(tz=UTC).replace(tzinfo=UTC) + timedelta( + days=1, hours=4, seconds=30 +) # generate energy log timestamps with fixed hour timestamp used in tests From af313a7e846131e82716cce66ef7cd4f79385b37 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 16:56:45 +0200 Subject: [PATCH 55/92] More clean up --- tests/stick_test_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index cf8d610d0..8578a0469 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -599,7 +599,6 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - # datetime.now() + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 From 1e91943a7811586694f44a901970662d355983bb Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 17:05:34 +0200 Subject: [PATCH 56/92] Revert "Clean up extra debug-logging" This reverts commit 444ed47603395344a9962793a4c8bc8242570bae. --- plugwise_usb/nodes/circle_plus.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 13a0fda9a..970b6134b 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -77,6 +77,8 @@ async def clock_synchronize(self) -> bool: await self._available_update_state(True, response.timestamp) dt_now = datetime.now(tz=UTC) + _LOGGER.debug("HOI dt_now weekday=%s", dt_now.weekday()) + _LOGGER.debug("HOI circle+ day_of_week=%s", response.day_of_week.value) days_diff = (response.day_of_week.value - dt_now.weekday()) % 7 circle_plus_timestamp: datetime = dt_now.replace( day=(dt_now.day + days_diff), @@ -86,6 +88,8 @@ async def clock_synchronize(self) -> bool: microsecond=0, tzinfo=UTC, ) + _LOGGER.debug("HOI circle+ clock=%s", circle_plus_timestamp) + _LOGGER.debug("HOI response timestamp=%s", response.timestamp) clock_offset = response.timestamp.replace(microsecond=0) - circle_plus_timestamp if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True From bbb598d448c5f6778d60d0ae24b4229aa071996c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 17:06:36 +0200 Subject: [PATCH 57/92] Full test-output --- scripts/tests_and_coverage.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/tests_and_coverage.sh b/scripts/tests_and_coverage.sh index 749dec0f5..4900304eb 100755 --- a/scripts/tests_and_coverage.sh +++ b/scripts/tests_and_coverage.sh @@ -57,7 +57,8 @@ 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 af54eb3a470d58133c0df0a90e8c1c6ae854f142 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 19:38:18 +0200 Subject: [PATCH 58/92] Add pytest settings --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index a46a3a4ae..512babea4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -540,3 +540,5 @@ testpaths = [ ] asyncio_default_fixture_loop_scope = "session" asyncio_mode = "strict" +log_format = "%(asctime)s %(levelname)s %(message)s" +log_date_format = "%Y-%m-%d %H:%M:%S" From e40792adda458f2eb09be3b3b86badc904163726 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 21 Sep 2025 19:47:24 +0200 Subject: [PATCH 59/92] Try --- tests/stick_test_data.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 8578a0469..5ddb80058 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -599,13 +599,13 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 + + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).day, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).month, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % (datetime.now(tz=UTC).replace(tzinfo=UTC).year - 2000), pw_constants.UTF8), # noqa: UP031 ), b"\x05\x05\x03\x030028009876543210123452441406210925D23C\r\n": ( "Circle+ Realtime set clock for 0098765432101234", From c615c9264c77b155b3796ec9c0dea54c4e748612 Mon Sep 17 00:00:00 2001 From: autoruff Date: Mon, 22 Sep 2025 10:21:15 +0000 Subject: [PATCH 60/92] fixup: test_bouwew Python code reformatted using Ruff --- tests/stick_test_data.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 5ddb80058..0144ac7c2 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -599,13 +599,34 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).second, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).weekday(), pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).day, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).month, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % (datetime.now(tz=UTC).replace(tzinfo=UTC).year - 2000), pw_constants.UTF8), # noqa: UP031 + + bytes( + ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).second, + pw_constants.UTF8, + ) # noqa: UP031 + + bytes( + ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).minute, + pw_constants.UTF8, + ) # noqa: UP031 + + bytes( + ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).hour, + pw_constants.UTF8, + ) # noqa: UP031 + + bytes( + ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).weekday(), + pw_constants.UTF8, + ) # noqa: UP031 + + bytes( + ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).day, + pw_constants.UTF8, + ) # noqa: UP031 + + bytes( + ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).month, + pw_constants.UTF8, + ) # noqa: UP031 + + bytes( + ("%%0%dd" % 2) % (datetime.now(tz=UTC).replace(tzinfo=UTC).year - 2000), + pw_constants.UTF8, + ), # noqa: UP031 ), b"\x05\x05\x03\x030028009876543210123452441406210925D23C\r\n": ( "Circle+ Realtime set clock for 0098765432101234", From e2984509b2183be273930b09efb288e8aef27b4e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 13:34:40 +0200 Subject: [PATCH 61/92] Back to using freeze_time for both related testcases --- 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 94cdde966..23a2058b4 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -780,7 +780,7 @@ async def node_init_relay_state( ) ) - # @freeze_time("2025-04-03 22:00:00", real_asyncio=True) + @freeze_time("2025-04-03 22:00:00", real_asyncio=True) @pytest.mark.asyncio async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> None: # noqa: PLR0915 """Testing discovery of nodes.""" @@ -909,7 +909,7 @@ async def test_node_relay_and_power(self, monkeypatch: pytest.MonkeyPatch) -> No await stick.disconnect() - # @freeze_time("2025-04-03 22:00:00", real_asyncio=True) + @freeze_time("2025-04-03 22:00:00", real_asyncio=True) @pytest.mark.asyncio async def test_energy_circle(self, monkeypatch: pytest.MonkeyPatch) -> None: """Testing energy retrieval.""" From df1e9e96b110b12ffd187d4f555ea25501d8c077 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 13:37:20 +0200 Subject: [PATCH 62/92] Fix time for CircleEnergyLogsResponses --- tests/stick_test_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 0144ac7c2..79bf7c070 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -13,7 +13,8 @@ # generate energy log timestamps with fixed hour timestamp used in tests -hour_timestamp = utc_now.replace(minute=0, second=0, microsecond=0) +# 2025-04-03 22:00:00 +hour_timestamp = datetime(25, 4, 3, 22, 0, 0) # utc_now.replace(minute=0, second=0, microsecond=0) LOG_TIMESTAMPS = {} _one_hour = timedelta(hours=1) From 49bdb23502cecd2a35e71b07ec2be0ccdf1dd085 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 13:41:10 +0200 Subject: [PATCH 63/92] Update 0028 response --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 79bf7c070..1fd7285ac 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -629,7 +629,7 @@ pw_constants.UTF8, ), # noqa: UP031 ), - b"\x05\x05\x03\x030028009876543210123452441406210925D23C\r\n": ( + b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", b"000000C1", # Success ack b"0000" + b"00D7" + b"0098765432101234", # msg_id, clock_ack, mac From 63bc7f989e2412a4fec208b36bb8c509bb3da7ac Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 13:51:48 +0200 Subject: [PATCH 64/92] Correct var name --- 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 e1b62ac92..1cb93e754 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -881,7 +881,7 @@ async def clock_synchronize(self) -> bool: dt_now = datetime.now(tz=UTC) days_diff = (response.day_of_week.value - dt_now.weekday()) % 7 - circle_plus_timestamp: datetime = dt_now.replace( + circle_timestamp: datetime = dt_now.replace( day=dt_now.day + days_diff, hour=response.time.value.hour, minute=response.time.value.minute, From 85b0ce6e77716a527a8686426d021b78be909c38 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 16:31:38 +0200 Subject: [PATCH 65/92] Revert back to utc_now --- tests/stick_test_data.py | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 1fd7285ac..288cee770 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -600,34 +600,13 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - + bytes( - ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).second, - pw_constants.UTF8, - ) # noqa: UP031 - + bytes( - ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).minute, - pw_constants.UTF8, - ) # noqa: UP031 - + bytes( - ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).hour, - pw_constants.UTF8, - ) # noqa: UP031 - + bytes( - ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).weekday(), - pw_constants.UTF8, - ) # noqa: UP031 - + bytes( - ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).day, - pw_constants.UTF8, - ) # noqa: UP031 - + bytes( - ("%%0%dd" % 2) % datetime.now(tz=UTC).replace(tzinfo=UTC).month, - pw_constants.UTF8, - ) # noqa: UP031 - + bytes( - ("%%0%dd" % 2) % (datetime.now(tz=UTC).replace(tzinfo=UTC).year - 2000), - pw_constants.UTF8, - ), # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8) # noqa: UP031 ), b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", From 71e6f5722fe8a859d29a9d21c2578de795f9b554 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 17:20:59 +0200 Subject: [PATCH 66/92] Fix year error --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 288cee770..515595a09 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -14,7 +14,7 @@ # generate energy log timestamps with fixed hour timestamp used in tests # 2025-04-03 22:00:00 -hour_timestamp = datetime(25, 4, 3, 22, 0, 0) # utc_now.replace(minute=0, second=0, microsecond=0) +hour_timestamp = datetime(2025, 4, 3, 22, 0, 0) # utc_now.replace(minute=0, second=0, microsecond=0) LOG_TIMESTAMPS = {} _one_hour = timedelta(hours=1) From bb658d1bde650dd2333902576e875267a433b217 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 17:25:23 +0200 Subject: [PATCH 67/92] Also freeze_time test_node_discovery_and_load --- tests/stick_test_data.py | 6 ++++-- tests/test_usb.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 515595a09..7167f75b0 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -14,7 +14,9 @@ # generate energy log timestamps with fixed hour timestamp used in tests # 2025-04-03 22:00:00 -hour_timestamp = datetime(2025, 4, 3, 22, 0, 0) # utc_now.replace(minute=0, second=0, microsecond=0) +hour_timestamp = datetime( + 2025, 4, 3, 22, 0, 0 +) # utc_now.replace(minute=0, second=0, microsecond=0) LOG_TIMESTAMPS = {} _one_hour = timedelta(hours=1) @@ -606,7 +608,7 @@ + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 ), b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", diff --git a/tests/test_usb.py b/tests/test_usb.py index 23a2058b4..ad4979fb0 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -2724,6 +2724,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign ) assert not state[pw_api.NodeFeature.AVAILABLE].state + @freeze_time("2025-04-03 22:00:00", real_asyncio=True) @pytest.mark.asyncio async def test_node_discovery_and_load( # noqa: PLR0915 self, monkeypatch: pytest.MonkeyPatch From 61374d0e2661f53d6bb9d4e7c391f19a237fea1c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 17:29:38 +0200 Subject: [PATCH 68/92] Introduce clock offset of 10s --- tests/stick_test_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 7167f75b0..26debc215 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -602,7 +602,9 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - + bytes(("%%0%dd" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes( + ("%%0%dd" % 2) % (utc_now + timedelta(seconds=10)).second, pw_constants.UTF8 + ) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 From 6d4dfb667d44d42315bb1000b44e30bc087cfb16 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 17:39:42 +0200 Subject: [PATCH 69/92] Try --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 26debc215..daaab4996 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -603,7 +603,7 @@ b"003A" # msg_id + b"0098765432101234" # mac + bytes( - ("%%0%dd" % 2) % (utc_now + timedelta(seconds=10)).second, pw_constants.UTF8 + ("%%0%dd" % 2) % (utc_now + timedelta(seconds=0)).second, pw_constants.UTF8 ) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 From 16efa76db4413d54c4b5460c0f7e2c4efd11e3e8 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 18:16:31 +0200 Subject: [PATCH 70/92] Try 2 --- tests/stick_test_data.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index daaab4996..16c341323 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -5,18 +5,17 @@ pw_constants = importlib.import_module("plugwise_usb.constants") -# test using utc timezone -utc_now = datetime.now(tz=UTC).replace(tzinfo=UTC) -utc_now_offset = datetime.now(tz=UTC).replace(tzinfo=UTC) + timedelta( - days=1, hours=4, seconds=30 -) +# test using utc timezone - 2025-04-03 22:00:00 +utc_now = datetime( + 2025, 4, 3, 22, 0, 0 +) # datetime.now(tz=UTC).replace(tzinfo=UTC) +# utc_now_offset = datetime.now(tz=UTC).replace(tzinfo=UTC) + timedelta( +# days=1, hours=4, seconds=30 +# ) # generate energy log timestamps with fixed hour timestamp used in tests -# 2025-04-03 22:00:00 -hour_timestamp = datetime( - 2025, 4, 3, 22, 0, 0 -) # utc_now.replace(minute=0, second=0, microsecond=0) +hour_timestamp = utc_now.replace(minute=0, second=0, microsecond=0) LOG_TIMESTAMPS = {} _one_hour = timedelta(hours=1) From ae1cc420fed73a354cd9f74a71e2d42e64f426c7 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 18:17:50 +0200 Subject: [PATCH 71/92] 10s offset Circle+ --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 16c341323..1bb8c7463 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -602,7 +602,7 @@ b"003A" # msg_id + b"0098765432101234" # mac + bytes( - ("%%0%dd" % 2) % (utc_now + timedelta(seconds=0)).second, pw_constants.UTF8 + ("%%0%dd" % 2) % (utc_now + timedelta(seconds=10)).second, pw_constants.UTF8 ) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 From c211e7f746e8610416f074d95fa15d73e6390346 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 18:24:37 +0200 Subject: [PATCH 72/92] 10s offset Circle --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 1bb8c7463..7e4d33ac4 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -623,7 +623,7 @@ + b"1111111111111111" # mac + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 + + bytes(("%%0%dX" % 2) % (utc_now + timedelta(seconds=10)).second, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 From 5e005ffcc8a80a34b23b58c5870d98bcf85a5ec5 Mon Sep 17 00:00:00 2001 From: autoruff Date: Mon, 22 Sep 2025 16:31:23 +0000 Subject: [PATCH 73/92] fixup: test_bouwew Python code reformatted using Ruff --- tests/stick_test_data.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 7e4d33ac4..3289e64df 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -6,9 +6,7 @@ pw_constants = importlib.import_module("plugwise_usb.constants") # test using utc timezone - 2025-04-03 22:00:00 -utc_now = datetime( - 2025, 4, 3, 22, 0, 0 -) # datetime.now(tz=UTC).replace(tzinfo=UTC) +utc_now = datetime(2025, 4, 3, 22, 0, 0) # datetime.now(tz=UTC).replace(tzinfo=UTC) # utc_now_offset = datetime.now(tz=UTC).replace(tzinfo=UTC) + timedelta( # days=1, hours=4, seconds=30 # ) @@ -623,7 +621,9 @@ + b"1111111111111111" # mac + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % (utc_now + timedelta(seconds=10)).second, pw_constants.UTF8) # noqa: UP031 + + bytes( + ("%%0%dX" % 2) % (utc_now + timedelta(seconds=10)).second, pw_constants.UTF8 + ) # noqa: UP031 + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 From 6c89d6045b500c85cef5b953c9a6f0774601e03b Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 18:37:28 +0200 Subject: [PATCH 74/92] Clean up --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 3289e64df..c5706d3d9 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -1,6 +1,6 @@ """Stick Test Program.""" -from datetime import UTC, datetime, timedelta +from datetime import datetime, timedelta import importlib pw_constants = importlib.import_module("plugwise_usb.constants") From f5722814a74081b3d913ebda580f950b7ca88294 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 18:51:58 +0200 Subject: [PATCH 75/92] Modernize %-operator formatting --- tests/stick_test_data.py | 57 ++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index c5706d3d9..a29c13b47 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -20,11 +20,10 @@ for x in range(168): delta_month = hour_timestamp - hour_timestamp.replace(day=1, hour=0) LOG_TIMESTAMPS[x] = ( - bytes(("%%0%dX" % 2) % (hour_timestamp.year - 2000), pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % hour_timestamp.month, pw_constants.UTF8) # noqa: UP031 + bytes(f"{(hour_timestamp.year - 2000):02x}", pw_constants.UTF8) + + bytes(f"{hour_timestamp.month:02x}", pw_constants.UTF8) + bytes( - ("%%0%dX" % 4) # noqa: UP031 - % int((delta_month.days * 1440) + (delta_month.seconds / 60)), + f"{int((delta_month.days * 1440) + (delta_month.seconds / 60)):04x}", pw_constants.UTF8, ) ) @@ -599,15 +598,13 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - + bytes( - ("%%0%dd" % 2) % (utc_now + timedelta(seconds=10)).second, pw_constants.UTF8 - ) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.day, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % utc_now.month, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dd" % 2) % (utc_now.year - 2000), pw_constants.UTF8), # noqa: UP031 + + bytes(f"{(utc_now + timedelta(seconds=10)).second:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.minute:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.hour:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.weekday():02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.day:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.month:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{(utc_now.year - 2000):02d}", pw_constants.UTF8), # noqa: UP031 ), b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", @@ -619,12 +616,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"1111111111111111" # mac - + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes( - ("%%0%dX" % 2) % (utc_now + timedelta(seconds=10)).second, pw_constants.UTF8 - ) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.hour:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.minute:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{(utc_now + timedelta(seconds=10)).second:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.weekday():02d}", pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), @@ -633,10 +628,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"2222222222222222" # mac - + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.hour:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.minute:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.second:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), @@ -645,10 +640,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"3333333333333333" # mac - + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.hour:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.minute:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.second:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), @@ -657,10 +652,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"4444444444444444" # mac - + bytes(("%%0%dX" % 2) % utc_now.hour, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.minute, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.second, pw_constants.UTF8) # noqa: UP031 - + bytes(("%%0%dX" % 2) % utc_now.weekday(), pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.hour:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.minute:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.second:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), From ae4e3e4337d62423cf93bb95c48e5f9d9577174e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 19:03:36 +0200 Subject: [PATCH 76/92] Fix typo --- tests/stick_test_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index a29c13b47..230f8170c 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -619,7 +619,7 @@ + bytes(f"{utc_now.hour:02x}", pw_constants.UTF8) # noqa: UP031 + bytes(f"{utc_now.minute:02x}", pw_constants.UTF8) # noqa: UP031 + bytes(f"{(utc_now + timedelta(seconds=10)).second:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.weekday():02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{utc_now.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), From 6a96ea286fccde2dd9b836faa63771d66697a96c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 22 Sep 2025 19:13:34 +0200 Subject: [PATCH 77/92] Back to normal 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 4900304eb..749dec0f5 100755 --- a/scripts/tests_and_coverage.sh +++ b/scripts/tests_and_coverage.sh @@ -57,8 +57,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 73b5cb3e47643655845131afc6cb028b62111983 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 23 Sep 2025 09:16:40 +0200 Subject: [PATCH 78/92] Remove test-debugging --- plugwise_usb/nodes/circle_plus.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 970b6134b..13a0fda9a 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -77,8 +77,6 @@ async def clock_synchronize(self) -> bool: await self._available_update_state(True, response.timestamp) dt_now = datetime.now(tz=UTC) - _LOGGER.debug("HOI dt_now weekday=%s", dt_now.weekday()) - _LOGGER.debug("HOI circle+ day_of_week=%s", response.day_of_week.value) days_diff = (response.day_of_week.value - dt_now.weekday()) % 7 circle_plus_timestamp: datetime = dt_now.replace( day=(dt_now.day + days_diff), @@ -88,8 +86,6 @@ async def clock_synchronize(self) -> bool: microsecond=0, tzinfo=UTC, ) - _LOGGER.debug("HOI circle+ clock=%s", circle_plus_timestamp) - _LOGGER.debug("HOI response timestamp=%s", response.timestamp) clock_offset = response.timestamp.replace(microsecond=0) - circle_plus_timestamp if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True From dbfdd30cf261d2dc9f7875f81bce8224cbfa14a6 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 23 Sep 2025 09:26:23 +0200 Subject: [PATCH 79/92] Calculate also difference in days, next to difference within a day --- plugwise_usb/nodes/circle_plus.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 13a0fda9a..ccbd98297 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -77,9 +77,26 @@ async def clock_synchronize(self) -> bool: await self._available_update_state(True, response.timestamp) dt_now = datetime.now(tz=UTC) - days_diff = (response.day_of_week.value - dt_now.weekday()) % 7 + dt_now_date = dt_now.replace(hour=0, minute=0, second=0, microsecond=0) + response_date = datetime( + response.date.value.year, + response.date.value.month, + response.date.value.day, + hour=0, + minute=0, + second=0, + microsecond=0, + tzinfo=UTC, + ) + if dt_now_date != response_date: + _LOGGER.warning( + "Reset realtime clock of node %s because time has drifted %s days", + self._mac_in_str, + int(abs((dt_now_date - response_date).days)), + ) + return await self._send_clock_set_req() + circle_plus_timestamp: datetime = dt_now.replace( - day=(dt_now.day + days_diff), hour=response.time.value.hour, minute=response.time.value.minute, second=response.time.value.second, @@ -93,9 +110,13 @@ async def clock_synchronize(self) -> bool: _LOGGER.warning( "Reset realtime clock of node %s because time has drifted %s seconds while max drift is set to %s seconds)", self._mac_in_str, - str(int(abs(clock_offset.total_seconds()))), - str(MAX_TIME_DRIFT), + int(abs(clock_offset.total_seconds())), + MAX_TIME_DRIFT, ) + return await self._send_clock_set_req() + + async def _send_clock_set_req(self) -> bool: + """Send CirclePlusRealTimeClockSetRequest.""" set_request = CirclePlusRealTimeClockSetRequest( self._send, self._mac_in_bytes, datetime.now(tz=UTC) ) From d1f05727a25f8c877a6bd8139ce86c42f3f5a45c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 24 Sep 2025 18:11:56 +0200 Subject: [PATCH 80/92] Update CHANGELOG --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e29a4dee..d41bc8ad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ # Changelog +## Ongoing + +- PR [345](https://github.com/plugwise/python-plugwise-usb/pull/345): New Feature: schedule clock synchronization every 3600 seconds + ## v0.46.1 - 2025-09-25 - PR [337](https://github.com/plugwise/python-plugwise-usb/pull/337): Improve node removal, remove and reset the node as executed by Source, and remove the cache-file. -- PR [341](https://github.com/plugwise/python-plugwise-usb/pull/341): Schedule clock synchronization every 3600 seconds - PR [342](https://github.com/plugwise/python-plugwise-usb/pull/342): Improve node_type caching. - PR [343](https://github.com/plugwise/python-plugwise-usb/pull/343): Improve writing of cache-files. - PR [344](https://github.com/plugwise/python-plugwise-usb/pull/344): Don't store plus-device in nodetypes cache. From 87434a5fb23787311ed913fa74cd99a564a902f2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 24 Sep 2025 18:12:49 +0200 Subject: [PATCH 81/92] v0.47.0a0 test-version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 512babea4..288f938ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.46.1" +version = "0.47.0a0" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ From da3ae6643f584efd3dc9707f2202226147259736 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 24 Sep 2025 20:11:22 +0200 Subject: [PATCH 82/92] Line up debug messages --- plugwise_usb/nodes/circle_plus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index ccbd98297..37559104a 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -108,7 +108,7 @@ async def clock_synchronize(self) -> bool: return True _LOGGER.warning( - "Reset realtime clock of node %s because time has drifted %s seconds while max drift is set to %s seconds)", + "Reset realtime clock of node %s because time drifted %s seconds (max %s seconds)", self._mac_in_str, int(abs(clock_offset.total_seconds())), MAX_TIME_DRIFT, From f50af682df6945a57dbf23b871badb6c8a331089 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 24 Sep 2025 20:14:11 +0200 Subject: [PATCH 83/92] Replave utc_now by fixed_time --- tests/stick_test_data.py | 54 +++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 230f8170c..7b0d1fd04 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -6,14 +6,10 @@ pw_constants = importlib.import_module("plugwise_usb.constants") # test using utc timezone - 2025-04-03 22:00:00 -utc_now = datetime(2025, 4, 3, 22, 0, 0) # datetime.now(tz=UTC).replace(tzinfo=UTC) -# utc_now_offset = datetime.now(tz=UTC).replace(tzinfo=UTC) + timedelta( -# days=1, hours=4, seconds=30 -# ) - +fixed_time = datetime(2025, 4, 3, 22, 0, 0) # changed from datetime.now(tz=UTC).replace(tzinfo=UTC) # generate energy log timestamps with fixed hour timestamp used in tests -hour_timestamp = utc_now.replace(minute=0, second=0, microsecond=0) +hour_timestamp = fixed_time.replace(minute=0, second=0, microsecond=0) LOG_TIMESTAMPS = {} _one_hour = timedelta(hours=1) @@ -598,13 +594,13 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - + bytes(f"{(utc_now + timedelta(seconds=10)).second:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.minute:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.hour:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.weekday():02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.day:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.month:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{(utc_now.year - 2000):02d}", pw_constants.UTF8), # noqa: UP031 + + bytes(f"{(fixed_time + timedelta(seconds=10)).second:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.minute:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.hour:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.weekday():02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.day:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.month:02d}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{(fixed_time.year - 2000):02d}", pw_constants.UTF8), # noqa: UP031 ), b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", @@ -616,10 +612,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"1111111111111111" # mac - + bytes(f"{utc_now.hour:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.minute:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{(utc_now + timedelta(seconds=10)).second:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{(fixed_time + timedelta(seconds=10)).second:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), @@ -628,10 +624,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"2222222222222222" # mac - + bytes(f"{utc_now.hour:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.minute:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.second:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.second:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), @@ -640,10 +636,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"3333333333333333" # mac - + bytes(f"{utc_now.hour:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.minute:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.second:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.second:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), @@ -652,10 +648,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"4444444444444444" # mac - + bytes(f"{utc_now.hour:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.minute:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.second:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{utc_now.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.second:02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + b"00" # unknown + b"0000", # unknown2 ), From d81c485dd0cc1a1b242e133e7b22d8e3b6141e54 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 24 Sep 2025 20:17:11 +0200 Subject: [PATCH 84/92] Clean up --- tests/stick_test_data.py | 46 ++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 7b0d1fd04..9ad44e505 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -594,13 +594,13 @@ b"000000C1", # Success ack b"003A" # msg_id + b"0098765432101234" # mac - + bytes(f"{(fixed_time + timedelta(seconds=10)).second:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.minute:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.hour:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.weekday():02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.day:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.month:02d}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{(fixed_time.year - 2000):02d}", pw_constants.UTF8), # noqa: UP031 + + bytes(f"{(fixed_time + timedelta(seconds=10)).second:02d}", pw_constants.UTF8) + + bytes(f"{fixed_time.minute:02d}", pw_constants.UTF8) + + bytes(f"{fixed_time.hour:02d}", pw_constants.UTF8) + + bytes(f"{fixed_time.weekday():02d}", pw_constants.UTF8) + + bytes(f"{fixed_time.day:02d}", pw_constants.UTF8) + + bytes(f"{fixed_time.month:02d}", pw_constants.UTF8) + + bytes(f"{(fixed_time.year - 2000):02d}", pw_constants.UTF8), ), b"\x05\x05\x03\x0300280098765432101234000022030304259DDF\r\n": ( "Circle+ Realtime set clock for 0098765432101234", @@ -612,10 +612,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"1111111111111111" # mac - + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{(fixed_time + timedelta(seconds=10)).second:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) + + bytes(f"{(fixed_time + timedelta(seconds=10)).second:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) + b"00" # unknown + b"0000", # unknown2 ), @@ -624,10 +624,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"2222222222222222" # mac - + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.second:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.second:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) + b"00" # unknown + b"0000", # unknown2 ), @@ -636,10 +636,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"3333333333333333" # mac - + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.second:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.second:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) + b"00" # unknown + b"0000", # unknown2 ), @@ -648,10 +648,10 @@ b"000000C1", # Success ack b"003F" # msg_id + b"4444444444444444" # mac - + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.second:02x}", pw_constants.UTF8) # noqa: UP031 - + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) # noqa: UP031 + + bytes(f"{fixed_time.hour:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.minute:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.second:02x}", pw_constants.UTF8) + + bytes(f"{fixed_time.weekday():02x}", pw_constants.UTF8) + b"00" # unknown + b"0000", # unknown2 ), From 2ba67e59332b35af6e572203d33a5d9f1790d579 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 24 Sep 2025 20:24:36 +0200 Subject: [PATCH 85/92] Improve pytest settings --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 288f938ad..db6573d91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -540,5 +540,7 @@ testpaths = [ ] asyncio_default_fixture_loop_scope = "session" asyncio_mode = "strict" +log_cli = true +log_cli_level = "DEBUG" log_format = "%(asctime)s %(levelname)s %(message)s" log_date_format = "%Y-%m-%d %H:%M:%S" From 1a687f98ccff3ac460b8ffc1bd31b42dd3c5192d Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 24 Sep 2025 20:26:03 +0200 Subject: [PATCH 86/92] Ruffed --- tests/stick_test_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/stick_test_data.py b/tests/stick_test_data.py index 9ad44e505..b70ca5239 100644 --- a/tests/stick_test_data.py +++ b/tests/stick_test_data.py @@ -6,7 +6,9 @@ pw_constants = importlib.import_module("plugwise_usb.constants") # test using utc timezone - 2025-04-03 22:00:00 -fixed_time = datetime(2025, 4, 3, 22, 0, 0) # changed from datetime.now(tz=UTC).replace(tzinfo=UTC) +fixed_time = datetime( + 2025, 4, 3, 22, 0, 0 +) # changed from datetime.now(tz=UTC).replace(tzinfo=UTC) # generate energy log timestamps with fixed hour timestamp used in tests hour_timestamp = fixed_time.replace(minute=0, second=0, microsecond=0) From e12211a121039f0466b56bb408d81cdbf5baf081 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 24 Sep 2025 20:26:45 +0200 Subject: [PATCH 87/92] Disable pytest logging --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index db6573d91..2d41e7e96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -540,7 +540,7 @@ testpaths = [ ] asyncio_default_fixture_loop_scope = "session" asyncio_mode = "strict" -log_cli = true -log_cli_level = "DEBUG" -log_format = "%(asctime)s %(levelname)s %(message)s" -log_date_format = "%Y-%m-%d %H:%M:%S" +# log_cli = true +# log_cli_level = "DEBUG" +# log_format = "%(asctime)s %(levelname)s %(message)s" +# log_date_format = "%Y-%m-%d %H:%M:%S" From f379b026840b46fe4f20be197bf19ab5f7e5ee1e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 25 Sep 2025 10:04:51 +0200 Subject: [PATCH 88/92] Improve clock-sync related log messages --- plugwise_usb/nodes/circle.py | 8 ++------ plugwise_usb/nodes/circle_plus.py | 12 ++++-------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index 1cb93e754..f02a7f932 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -894,10 +894,9 @@ async def clock_synchronize(self) -> bool: return True _LOGGER.warning( - "Reset realtime clock of node %s because time drifted %s seconds (max %s seconds)", + "Sync clock of node %s because time drifted %s seconds", self._mac_in_str, str(int(abs(clock_offset.total_seconds()))), - str(MAX_TIME_DRIFT), ) if self._node_protocols is None: raise NodeError( @@ -912,10 +911,7 @@ async def clock_synchronize(self) -> bool: ) if (node_response := await set_request.send()) is not None: return node_response.ack_id == NodeResponseType.CLOCK_ACCEPTED - _LOGGER.warning( - "Failed to (re)set the internal realtime clock of %s", - self.name, - ) + _LOGGER.debug("Failed to sync the clock of %s", self.name) return False async def load(self) -> None: diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 37559104a..55dcb48cb 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -70,7 +70,7 @@ async def clock_synchronize(self) -> bool: request = CirclePlusRealTimeClockGetRequest(self._send, self._mac_in_bytes) if (response := await request.send()) is None: _LOGGER.debug( - "No response for async_realtime_clock_synchronize() for %s", self.mac + "No response for clock_synchronize() for %s", self._mac_in_str ) await self._available_update_state(False) return False @@ -90,7 +90,7 @@ async def clock_synchronize(self) -> bool: ) if dt_now_date != response_date: _LOGGER.warning( - "Reset realtime clock of node %s because time has drifted %s days", + "Sync clock of node %s because time has drifted %s days", self._mac_in_str, int(abs((dt_now_date - response_date).days)), ) @@ -108,10 +108,9 @@ async def clock_synchronize(self) -> bool: return True _LOGGER.warning( - "Reset realtime clock of node %s because time drifted %s seconds (max %s seconds)", + "Sync clock of node %s because time drifted %s seconds", self._mac_in_str, int(abs(clock_offset.total_seconds())), - MAX_TIME_DRIFT, ) return await self._send_clock_set_req() @@ -122,10 +121,7 @@ async def _send_clock_set_req(self) -> bool: ) if (node_response := await set_request.send()) is not None: return node_response.ack_id == NodeResponseType.CLOCK_ACCEPTED - _LOGGER.warning( - "Failed to (re)set the internal realtime clock of %s", - self.name, - ) + _LOGGER.debug("Failed to sync the clock of %s", self.name) return False @raise_not_loaded From 51ec2b70c652b10bbcb9f01a258f236f8adb2657 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 25 Sep 2025 10:43:36 +0200 Subject: [PATCH 89/92] CRAI suggestions --- plugwise_usb/nodes/circle.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index f02a7f932..fac1ba986 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -896,11 +896,11 @@ async def clock_synchronize(self) -> bool: _LOGGER.warning( "Sync clock of node %s because time drifted %s seconds", self._mac_in_str, - str(int(abs(clock_offset.total_seconds()))), + int(abs(clock_offset.total_seconds())), ) if self._node_protocols is None: raise NodeError( - "Unable to synchronize clock en when protocol version is unknown" + "Unable to synchronize clock when protocol version is unknown" ) set_request = CircleClockSetRequest( @@ -1011,10 +1011,6 @@ async def initialize(self) -> bool: ) self._initialized = False return False - if self._clock_synchronize_task is None or self._clock_synchronize_task.done(): - self._clock_synchronize_task = create_task( - self._clock_synchronize_scheduler() - ) if not self._calibration and not await self.calibration_update(): _LOGGER.debug( @@ -1039,6 +1035,10 @@ async def initialize(self) -> bool: return False await super().initialize() + if self._clock_synchronize_task is None or self._clock_synchronize_task.done(): + self._clock_synchronize_task = create_task( + self._clock_synchronize_scheduler() + ) return True async def node_info_update( From 226008afbd021d76dfc557ce9df0287ecee6af2f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 25 Sep 2025 19:01:54 +0200 Subject: [PATCH 90/92] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d41bc8ad4..2dd6d43b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Ongoing +## v0.47.0 - 2025-09-26 - PR [345](https://github.com/plugwise/python-plugwise-usb/pull/345): New Feature: schedule clock synchronization every 3600 seconds From d70082271740664de18b94ac40a46c8d29604738 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 26 Sep 2025 12:26:53 +0200 Subject: [PATCH 91/92] Change debug levels --- plugwise_usb/nodes/circle.py | 14 +++++++------- plugwise_usb/nodes/circle_plus.py | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugwise_usb/nodes/circle.py b/plugwise_usb/nodes/circle.py index fac1ba986..1ff06fb92 100644 --- a/plugwise_usb/nodes/circle.py +++ b/plugwise_usb/nodes/circle.py @@ -893,7 +893,7 @@ async def clock_synchronize(self) -> bool: if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True - _LOGGER.warning( + _LOGGER.info( "Sync clock of node %s because time drifted %s seconds", self._mac_in_str, int(abs(clock_offset.total_seconds())), @@ -911,7 +911,7 @@ async def clock_synchronize(self) -> bool: ) if (node_response := await set_request.send()) is not None: return node_response.ack_id == NodeResponseType.CLOCK_ACCEPTED - _LOGGER.debug("Failed to sync the clock of %s", self.name) + _LOGGER.warning("Failed to sync the clock of %s", self.name) return False async def load(self) -> None: @@ -1346,7 +1346,7 @@ async def energy_reset_request(self) -> None: f"Unexpected NodeResponseType {response.ack_id!r} received as response to CircleClockSetRequest" ) - _LOGGER.warning("Energy reset for Node %s successful", self._mac_in_str) + _LOGGER.info("Energy reset for Node %s successful", self._mac_in_str) # Follow up by an energy-intervals (re)set interval_request = CircleMeasureIntervalRequest( @@ -1365,12 +1365,12 @@ async def energy_reset_request(self) -> None: raise MessageError( f"Unknown NodeResponseType '{interval_response.response_type.name}' received" ) - _LOGGER.warning("Resetting energy intervals to default (= consumption only)") + _LOGGER.info("Resetting energy intervals to default (= consumption only)") # Clear the cached energy_collection if self._cache_enabled: self._set_cache(CACHE_ENERGY_COLLECTION, "") - _LOGGER.warning( + _LOGGER.info( "Energy-collection cache cleared successfully, updating cache for %s", self._mac_in_str, ) @@ -1378,7 +1378,7 @@ async def energy_reset_request(self) -> None: # Clear PulseCollection._logs self._energy_counters.reset_pulse_collection() - _LOGGER.warning("Resetting pulse-collection") + _LOGGER.info("Resetting pulse-collection") # Request a NodeInfo update if await self.node_info_update() is None: @@ -1387,7 +1387,7 @@ async def energy_reset_request(self) -> None: self._mac_in_str, ) else: - _LOGGER.warning( + _LOGGER.info( "Node info update after energy-reset successful for %s", self._mac_in_str, ) diff --git a/plugwise_usb/nodes/circle_plus.py b/plugwise_usb/nodes/circle_plus.py index 55dcb48cb..5760bea92 100644 --- a/plugwise_usb/nodes/circle_plus.py +++ b/plugwise_usb/nodes/circle_plus.py @@ -69,7 +69,7 @@ async def clock_synchronize(self) -> bool: """Synchronize realtime clock. Returns true if successful.""" request = CirclePlusRealTimeClockGetRequest(self._send, self._mac_in_bytes) if (response := await request.send()) is None: - _LOGGER.debug( + _LOGGER.warning( "No response for clock_synchronize() for %s", self._mac_in_str ) await self._available_update_state(False) @@ -89,7 +89,7 @@ async def clock_synchronize(self) -> bool: tzinfo=UTC, ) if dt_now_date != response_date: - _LOGGER.warning( + _LOGGER.info( "Sync clock of node %s because time has drifted %s days", self._mac_in_str, int(abs((dt_now_date - response_date).days)), @@ -107,7 +107,7 @@ async def clock_synchronize(self) -> bool: if abs(clock_offset.total_seconds()) < MAX_TIME_DRIFT: return True - _LOGGER.warning( + _LOGGER.info( "Sync clock of node %s because time drifted %s seconds", self._mac_in_str, int(abs(clock_offset.total_seconds())), @@ -121,7 +121,7 @@ async def _send_clock_set_req(self) -> bool: ) if (node_response := await set_request.send()) is not None: return node_response.ack_id == NodeResponseType.CLOCK_ACCEPTED - _LOGGER.debug("Failed to sync the clock of %s", self.name) + _LOGGER.warning("Failed to sync the clock of %s", self.name) return False @raise_not_loaded From df5ae007b8cfd222d0e8b436e086bd56445c22ea Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 26 Sep 2025 12:30:36 +0200 Subject: [PATCH 92/92] Set release-version 0.47.0 in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2d41e7e96..df84cf0fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.47.0a0" +version = "0.47.0" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [