Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Ongoing

- 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 chaching.

## 0.46.0 - 2025-09-12
Expand Down
36 changes: 30 additions & 6 deletions plugwise_usb/nodes/circle.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from asyncio import Task, create_task, gather
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
Expand Down Expand Up @@ -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__)
Expand Down Expand Up @@ -141,6 +143,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()
Expand Down Expand Up @@ -852,6 +856,19 @@ async def _relay_update_lock(
)
await self.save_cache()

async def _clock_synchronize_scheduler(self) -> None:
"""Background task: periodically synchronize the clock until cancelled."""
try:
while True:
await sleep(CLOCK_SYNC_PERIOD)
try:
await self.clock_synchronize()
except Exception:
_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)
raise

Comment thread
dirixmjm marked this conversation as resolved.
async def clock_synchronize(self) -> bool:
"""Synchronize clock. Returns true if successful."""
get_clock_request = CircleClockGetRequest(self._send, self._mac_in_bytes)
Expand All @@ -866,14 +883,12 @@ 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",
self._mac_in_str,
str(clock_offset.seconds),
str(int(abs(clock_offset.total_seconds()))),
)
if self._node_protocols is None:
raise NodeError(
Expand Down Expand Up @@ -992,6 +1007,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(
Expand Down Expand Up @@ -1082,6 +1101,11 @@ async def unload(self) -> None:
if self._cache_enabled:
await self._energy_log_records_save_to_cache()

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()

@raise_not_loaded
Expand Down
6 changes: 2 additions & 4 deletions plugwise_usb/nodes/circle_plus.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,12 @@ 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)",
self._node_info.mac,
str(clock_offset.seconds),
str(int(abs(clock_offset.total_seconds()))),
str(MAX_TIME_DRIFT),
)
clock_set_request = CirclePlusRealTimeClockSetRequest(
Expand Down
Loading