diff --git a/adafruit_ntp.py b/adafruit_ntp.py index bc6db0d..9dc7476 100644 --- a/adafruit_ntp.py +++ b/adafruit_ntp.py @@ -32,13 +32,13 @@ PACKET_SIZE = const(48) -class NTP: +class NTP: # pylint:disable=too-many-instance-attributes """Network Time Protocol (NTP) helper module for CircuitPython. This module does not handle daylight savings or local time. It simply requests UTC from a NTP server. """ - def __init__( + def __init__( # pylint:disable=too-many-arguments self, socketpool, *, @@ -70,45 +70,76 @@ def __init__( # This is our estimated start time for the monotonic clock. We adjust it based on the ntp # responses. - self._monotonic_start = 0 + self._monotonic_start_ns = 0 self.next_sync = 0 + def _update_time_sync(self) -> None: + """Update the time sync value. Raises OSError exception if no response + is received within socket_timeout seconds, ArithmeticError for substantially incorrect + NTP results.""" + if self._socket_address is None: + self._socket_address = self._pool.getaddrinfo(self._server, self._port)[0][ + 4 + ] + + self._packet[0] = 0b00100011 # Not leap second, NTP version 4, Client mode + for i in range(1, PACKET_SIZE): + self._packet[i] = 0 + with self._pool.socket(self._pool.AF_INET, self._pool.SOCK_DGRAM) as sock: + sock.settimeout(self._socket_timeout) + local_send_ns = time.monotonic_ns() # expanded + sock.sendto(self._packet, self._socket_address) + sock.recv_into(self._packet) + # Get the time in the context to minimize the difference between it and receiving + # the packet. + local_recv_ns = time.monotonic_ns() # was destination + + poll = struct.unpack_from("!B", self._packet, offset=2)[0] + + cache_offset_s = max(2**poll, self._cache_seconds) + self.next_sync = local_recv_ns + cache_offset_s * 1_000_000_000 + + srv_recv_s, srv_recv_f = struct.unpack_from("!II", self._packet, offset=32) + srv_send_s, srv_send_f = struct.unpack_from("!II", self._packet, offset=40) + + # Convert the server times from NTP to UTC for local use + srv_recv_ns = (srv_recv_s - NTP_TO_UNIX_EPOCH) * 1_000_000_000 + ( + srv_recv_f * 1_000_000_000 // 2**32 + ) + srv_send_ns = (srv_send_s - NTP_TO_UNIX_EPOCH) * 1_000_000_000 + ( + srv_send_f * 1_000_000_000 // 2**32 + ) + + # _round_trip_delay = (local_recv_ns - local_send_ns) - (srv_send_ns - srv_recv_ns) + # Calculate (best estimate) offset between server UTC and board monotonic_ns time + clock_offset = ( + (srv_recv_ns - local_send_ns) + (srv_send_ns - local_recv_ns) + ) // 2 + + self._monotonic_start_ns = clock_offset + self._tz_offset * 1_000_000_000 + @property def datetime(self) -> time.struct_time: """Current time from NTP server. Accessing this property causes the NTP time request, - unless there has already been a recent request. Raises OSError exception if no response - is received within socket_timeout seconds, ArithmeticError for substantially incorrect - NTP results.""" + unless there has already been a recent request.""" if time.monotonic_ns() > self.next_sync: - if self._socket_address is None: - self._socket_address = self._pool.getaddrinfo(self._server, self._port)[ - 0 - ][4] - - self._packet[0] = 0b00100011 # Not leap second, NTP version 4, Client mode - for i in range(1, PACKET_SIZE): - self._packet[i] = 0 - with self._pool.socket(self._pool.AF_INET, self._pool.SOCK_DGRAM) as sock: - sock.settimeout(self._socket_timeout) - sock.sendto(self._packet, self._socket_address) - sock.recv_into(self._packet) - # Get the time in the context to minimize the difference between it and receiving - # the packet. - destination = time.monotonic_ns() - poll = struct.unpack_from("!B", self._packet, offset=2)[0] - - cache_offset = max(2**poll, self._cache_seconds) - self.next_sync = destination + cache_offset * 1_000_000_000 - seconds = struct.unpack_from("!I", self._packet, offset=PACKET_SIZE - 8)[0] - - self._monotonic_start = ( - seconds - + self._tz_offset - - NTP_TO_UNIX_EPOCH - - (destination // 1_000_000_000) - ) - - return time.localtime( - time.monotonic_ns() // 1_000_000_000 + self._monotonic_start - ) + self._update_time_sync() + + # Calculate the current time based on the current and start monotonic times + current_time_s = ( + time.monotonic_ns() + self._monotonic_start_ns + ) // 1_000_000_000 + + return time.localtime(current_time_s) + + @property + def utc_ns(self) -> int: + """UTC (unix epoch) time in nanoseconds. Accessing this property causes the NTP time + request, unless there has already been a recent request. Raises OSError exception if + no response is received within socket_timeout seconds, ArithmeticError for substantially + incorrect NTP results.""" + if time.monotonic_ns() > self.next_sync: + self._update_time_sync() + + return time.monotonic_ns() + self._monotonic_start_ns diff --git a/examples/ntp_set_rtc.py b/examples/ntp_set_rtc.py index e4f331b..e0be6f9 100644 --- a/examples/ntp_set_rtc.py +++ b/examples/ntp_set_rtc.py @@ -3,6 +3,7 @@ """Example demonstrating how to set the realtime clock (RTC) based on NTP time.""" +import os import time import rtc @@ -11,15 +12,19 @@ import adafruit_ntp -# Get wifi details and more from a secrets.py file +# Get wifi AP credentials from a settings.toml file +wifi_ssid = os.getenv("CIRCUITPY_WIFI_SSID") +wifi_password = os.getenv("CIRCUITPY_WIFI_PASSWORD") +if wifi_ssid is None: + print("WiFi credentials are kept in settings.toml, please add them there!") + raise ValueError("SSID not found in environment variables") + try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") + wifi.radio.connect(wifi_ssid, wifi_password) +except ConnectionError: + print("Failed to connect to WiFi with provided credentials") raise -wifi.radio.connect(secrets["ssid"], secrets["password"]) - pool = socketpool.SocketPool(wifi.radio) ntp = adafruit_ntp.NTP(pool, tz_offset=0, cache_seconds=3600) diff --git a/examples/ntp_simpletest.py b/examples/ntp_simpletest.py index a3f1b70..59b88bb 100644 --- a/examples/ntp_simpletest.py +++ b/examples/ntp_simpletest.py @@ -3,6 +3,7 @@ """Print out time based on NTP.""" +import os import time import socketpool @@ -10,15 +11,19 @@ import adafruit_ntp -# Get wifi details and more from a secrets.py file +# Get wifi AP credentials from a settings.toml file +wifi_ssid = os.getenv("CIRCUITPY_WIFI_SSID") +wifi_password = os.getenv("CIRCUITPY_WIFI_PASSWORD") +if wifi_ssid is None: + print("WiFi credentials are kept in settings.toml, please add them there!") + raise ValueError("SSID not found in environment variables") + try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") + wifi.radio.connect(wifi_ssid, wifi_password) +except ConnectionError: + print("Failed to connect to WiFi with provided credentials") raise -wifi.radio.connect(secrets["ssid"], secrets["password"]) - pool = socketpool.SocketPool(wifi.radio) ntp = adafruit_ntp.NTP(pool, tz_offset=0, cache_seconds=3600) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy