From 1e71d3f4ca91306279b3c800cb9e0832840be67c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 Dec 2023 21:34:38 -1000 Subject: [PATCH] Sync time daily once ESPHome device has requested time once If the ESPHome device requests time once we know it has Home Assistant time enabled. Since the clock drifts over time, we will send time again daily to ensure it keeps in sync. fixes https://github.com/esphome/issues/issues/4424 --- aioesphomeapi/connection.pxd | 9 ++++++++ aioesphomeapi/connection.py | 42 +++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/aioesphomeapi/connection.pxd b/aioesphomeapi/connection.pxd index 941c799..3c3db48 100644 --- a/aioesphomeapi/connection.pxd +++ b/aioesphomeapi/connection.pxd @@ -109,6 +109,7 @@ cdef class APIConnection: cdef bint _debug_enabled cdef public str received_name cdef public str connected_address + cdef object _time_sync_timer cpdef void send_message(self, object msg) @@ -154,3 +155,11 @@ cdef class APIConnection: cdef void _register_internal_message_handlers(self) cdef void _increase_recv_buffer_size(self) + + cdef void _cancel_next_time_sync(self) + + cdef void _send_time_response(self) + + cdef void _schedule_next_time_sync(self) + + cpdef void _send_time_response_schedule_next(self) diff --git a/aioesphomeapi/connection.py b/aioesphomeapi/connection.py index 14403b7..18d8a2f 100644 --- a/aioesphomeapi/connection.py +++ b/aioesphomeapi/connection.py @@ -98,6 +98,12 @@ CONNECT_REQUEST_TIMEOUT = 30.0 # to reboot and connect to the network/WiFi. TCP_CONNECT_TIMEOUT = 60.0 +# How often to sync the time with the ESPHome device +# to ensure the ESPHome device has the correct time +# after the initial sync. +TIME_SYNC_INTERVAL_SECONDS = 86400.0 + + WRITE_EXCEPTIONS = (RuntimeError, ConnectionResetError, OSError) @@ -209,6 +215,7 @@ class APIConnection: "_debug_enabled", "received_name", "connected_address", + "_time_sync_timer", ) def __init__( @@ -253,6 +260,7 @@ class APIConnection: self._debug_enabled = debug_enabled self.received_name: str = "" self.connected_address: str | None = None + self._time_sync_timer: asyncio.TimerHandle | None = None def set_log_name(self, name: str) -> None: """Set the friendly log name for this connection.""" @@ -313,6 +321,8 @@ class APIConnection: self._ping_timer.cancel() self._ping_timer = None + self._cancel_next_time_sync() + if (on_stop := self.on_stop) is not None and was_connected: self.on_stop = None on_stop(self._expected_disconnect) @@ -962,7 +972,37 @@ class APIConnection: def _handle_get_time_request_internal( # pylint: disable=unused-argument self, _msg: GetTimeRequest ) -> None: - """Handle a GetTimeRequest.""" + """Handle a GetTimeRequest. + + Once the ESPHome device has asked for time, we will + send a time response and schedule the next periodic sync + to ensure the ESPHome device has the correct time as the + ESPHome device clock is not very accurate and will drift + over time. + """ + self._send_time_response_schedule_next() + + def _send_time_response_schedule_next(self) -> None: + """Send a time response and schedule the next periodic sync.""" + self._send_time_response() + self._schedule_next_time_sync() + + def _schedule_next_time_sync(self) -> None: + """Schedule the next time sync.""" + self._cancel_next_time_sync() + self._time_sync_timer = self._loop.call_at( + self._loop.time() + TIME_SYNC_INTERVAL_SECONDS, + self._send_time_response_schedule_next, + ) + + def _cancel_next_time_sync(self) -> None: + """Cancel the next time sync.""" + if self._time_sync_timer is not None: + self._time_sync_timer.cancel() + self._time_sync_timer = None + + def _send_time_response(self) -> None: + """Send a time response.""" resp = GetTimeResponse() resp.epoch_seconds = int(time.time()) self.send_messages((resp,))