From eb34cfa39d180797cc91aa840fb50ba877ecdfb1 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 30 Oct 2020 16:02:20 -0700 Subject: [PATCH 1/3] Use UDP to talk to NTP servers --- adafruit_ntp.py | 60 ++++++++++++++++++++++---------------- examples/ntp_esp32spi.py | 52 +++++++++++++++++++++++++++++++++ examples/ntp_simpletest.py | 55 ++++------------------------------ 3 files changed, 93 insertions(+), 74 deletions(-) create mode 100644 examples/ntp_esp32spi.py diff --git a/adafruit_ntp.py b/adafruit_ntp.py index 6297ebf..307a78f 100644 --- a/adafruit_ntp.py +++ b/adafruit_ntp.py @@ -36,42 +36,52 @@ https://github.com/adafruit/circuitpython/releases """ +import struct import time -import rtc __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_NTP.git" +NTP_TO_UNIX_EPOCH = 2208988800 # 1970-01-01 00:00:00 class NTP: """Network Time Protocol (NTP) helper module for CircuitPython. - This module does not handle daylight savings or local time. + This module does not handle daylight savings or local time. It simply requests + UTC from a NTP server. - :param adafruit_esp32spi esp: ESP32SPI object. + :param object socketpool: A socket provider such as CPython's `socket` module. """ - def __init__(self, esp): - # Verify ESP32SPI module - if "ESP_SPIcontrol" in str(type(esp)): - self._esp = esp - else: - raise TypeError("Provided object is not an ESP_SPIcontrol object.") - self.valid_time = False + def __init__(self, socketpool, *, server="pool.ntp.org", port=123): + self._pool = socketpool + self._server = server + self._port = port + self._packet = bytearray(48) - def set_time(self, tz_offset=0): - """Fetches and sets the microcontroller's current time - in seconds since since Jan 1, 1970. + if time.gmtime(0).tm_year != 1970: + raise OSError("Epoch must be 1970") - :param int tz_offset: The offset of the local timezone, - in seconds west of UTC (negative in most of Western Europe, - positive in the US, zero in the UK). - """ + # This is our estimated start time for the monotonic clock. We adjust it based on the ntp + # responses. + self._monotonic_start = 0 - try: - now = self._esp.get_time() - now = time.localtime(now[0] + tz_offset) - rtc.RTC().datetime = now - self.valid_time = True - except ValueError as error: - print(str(error)) - return + self.next_sync = 0 + + @property + def datetime(self): + if time.monotonic_ns() > self.next_sync: + self._packet[0] = 0b00100011 # Not leap second, NTP version 4, Client mode + for i in range(1, len(self._packet)): + self._packet[i] = 0 + with self._pool.socket(self._pool.AF_INET, self._pool.SOCK_DGRAM) as sock: + sock.sendto(self._packet, (self._server, self._port)) + size, address = sock.recvfrom_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] + self.next_sync = destination + (2 ** poll) * 1_000_000_000 + seconds = struct.unpack_from("!I", self._packet, offset=len(self._packet) - 8)[0] + self._monotonic_start = seconds - NTP_TO_UNIX_EPOCH - (destination // 1_000_000_000) + + return time.gmtime(time.monotonic_ns() // 1_000_000_000 + self._monotonic_start) diff --git a/examples/ntp_esp32spi.py b/examples/ntp_esp32spi.py new file mode 100644 index 0000000..7b0598f --- /dev/null +++ b/examples/ntp_esp32spi.py @@ -0,0 +1,52 @@ +import time +import board +import busio +from digitalio import DigitalInOut +from adafruit_esp32spi import adafruit_esp32spi +from adafruit_ntp import NTP + +# If you are using a board with pre-defined ESP32 Pins: +esp32_cs = DigitalInOut(board.ESP_CS) +esp32_ready = DigitalInOut(board.ESP_BUSY) +esp32_reset = DigitalInOut(board.ESP_RESET) + +# If you have an externally connected ESP32: +# esp32_cs = DigitalInOut(board.D9) +# esp32_ready = DigitalInOut(board.D10) +# esp32_reset = DigitalInOut(board.D5) + +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) + +print("Connecting to AP...") +while not esp.is_connected: + try: + esp.connect_AP(b"WIFI_SSID", b"WIFI_PASS") + except RuntimeError as e: + print("could not connect to AP, retrying: ", e) + continue + +# Initialize the NTP object +ntp = NTP(esp) + +# Fetch and set the microcontroller's current UTC time +# keep retrying until a valid time is returned +while not ntp.valid_time: + ntp.set_time() + print("Failed to obtain time, retrying in 5 seconds...") + time.sleep(5) + +# Get the current time in seconds since Jan 1, 1970 +current_time = time.time() +print("Seconds since Jan 1, 1970: {} seconds".format(current_time)) + +# Convert the current time in seconds since Jan 1, 1970 to a struct_time +now = time.localtime(current_time) +print(now) + +# Pretty-parse the struct_time +print( + "It is currently {}/{}/{} at {}:{}:{} UTC".format( + now.tm_mon, now.tm_mday, now.tm_year, now.tm_hour, now.tm_min, now.tm_sec + ) +) diff --git a/examples/ntp_simpletest.py b/examples/ntp_simpletest.py index 7b0598f..23557d8 100644 --- a/examples/ntp_simpletest.py +++ b/examples/ntp_simpletest.py @@ -1,52 +1,9 @@ +import adafruit_ntp +import socket import time -import board -import busio -from digitalio import DigitalInOut -from adafruit_esp32spi import adafruit_esp32spi -from adafruit_ntp import NTP -# If you are using a board with pre-defined ESP32 Pins: -esp32_cs = DigitalInOut(board.ESP_CS) -esp32_ready = DigitalInOut(board.ESP_BUSY) -esp32_reset = DigitalInOut(board.ESP_RESET) +ntp = adafruit_ntp.NTP(socket) -# If you have an externally connected ESP32: -# esp32_cs = DigitalInOut(board.D9) -# esp32_ready = DigitalInOut(board.D10) -# esp32_reset = DigitalInOut(board.D5) - -spi = busio.SPI(board.SCK, board.MOSI, board.MISO) -esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) - -print("Connecting to AP...") -while not esp.is_connected: - try: - esp.connect_AP(b"WIFI_SSID", b"WIFI_PASS") - except RuntimeError as e: - print("could not connect to AP, retrying: ", e) - continue - -# Initialize the NTP object -ntp = NTP(esp) - -# Fetch and set the microcontroller's current UTC time -# keep retrying until a valid time is returned -while not ntp.valid_time: - ntp.set_time() - print("Failed to obtain time, retrying in 5 seconds...") - time.sleep(5) - -# Get the current time in seconds since Jan 1, 1970 -current_time = time.time() -print("Seconds since Jan 1, 1970: {} seconds".format(current_time)) - -# Convert the current time in seconds since Jan 1, 1970 to a struct_time -now = time.localtime(current_time) -print(now) - -# Pretty-parse the struct_time -print( - "It is currently {}/{}/{} at {}:{}:{} UTC".format( - now.tm_mon, now.tm_mday, now.tm_year, now.tm_hour, now.tm_min, now.tm_sec - ) -) +while True: + print(ntp.datetime) + time.sleep(1) From 22df8a35fce80702d131400b690e45bd40a4c17a Mon Sep 17 00:00:00 2001 From: askpatricw <4002194+askpatrickw@users.noreply.github.com> Date: Tue, 22 Dec 2020 10:09:22 -0800 Subject: [PATCH 2/3] ntp for cpython and circuitpython sockets --- adafruit_ntp.py | 8 +++----- examples/ntp_simpletest.py | 23 ++++++++++++++++++++--- examples/ntp_simpletest_cpython.py | 9 +++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 examples/ntp_simpletest_cpython.py diff --git a/adafruit_ntp.py b/adafruit_ntp.py index 307a78f..a8755c2 100644 --- a/adafruit_ntp.py +++ b/adafruit_ntp.py @@ -58,11 +58,9 @@ def __init__(self, socketpool, *, server="pool.ntp.org", port=123): self._port = port self._packet = bytearray(48) - if time.gmtime(0).tm_year != 1970: - raise OSError("Epoch must be 1970") - # This is our estimated start time for the monotonic clock. We adjust it based on the ntp - # responses. + # This is our estimated start time for the monotonic clock. + # We adjust it based on the ntp responses. self._monotonic_start = 0 self.next_sync = 0 @@ -84,4 +82,4 @@ def datetime(self): seconds = struct.unpack_from("!I", self._packet, offset=len(self._packet) - 8)[0] self._monotonic_start = seconds - NTP_TO_UNIX_EPOCH - (destination // 1_000_000_000) - return time.gmtime(time.monotonic_ns() // 1_000_000_000 + self._monotonic_start) + return time.localtime(time.monotonic_ns() // 1_000_000_000 + self._monotonic_start) diff --git a/examples/ntp_simpletest.py b/examples/ntp_simpletest.py index 23557d8..74b2200 100644 --- a/examples/ntp_simpletest.py +++ b/examples/ntp_simpletest.py @@ -1,9 +1,26 @@ -import adafruit_ntp -import socket +import socketpool import time +import wifi + +import adafruit_ntp + + +# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and +# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other +# source control. +# pylint: disable=no-name-in-module,wrong-import-order +try: + from secrets import secrets +except ImportError: + print("WiFi secrets are kept in secrets.py, please add them there!") + raise + +wifi.radio.connect(secrets["ssid"], secrets["wifi_password"]) -ntp = adafruit_ntp.NTP(socket) +pool = socketpool.SocketPool(wifi.radio) +ntp = adafruit_ntp.NTP(pool) while True: print(ntp.datetime) time.sleep(1) + \ No newline at end of file diff --git a/examples/ntp_simpletest_cpython.py b/examples/ntp_simpletest_cpython.py new file mode 100644 index 0000000..23557d8 --- /dev/null +++ b/examples/ntp_simpletest_cpython.py @@ -0,0 +1,9 @@ +import adafruit_ntp +import socket +import time + +ntp = adafruit_ntp.NTP(socket) + +while True: + print(ntp.datetime) + time.sleep(1) From 94c93f2cb4c65e5104dbb771b1fb3764ee01937d Mon Sep 17 00:00:00 2001 From: askpatricw <4002194+askpatrickw@users.noreply.github.com> Date: Tue, 22 Dec 2020 10:19:24 -0800 Subject: [PATCH 3/3] correct secrets to adafruit styls --- examples/ntp_simpletest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ntp_simpletest.py b/examples/ntp_simpletest.py index 74b2200..a3b3d2f 100644 --- a/examples/ntp_simpletest.py +++ b/examples/ntp_simpletest.py @@ -15,7 +15,7 @@ print("WiFi secrets are kept in secrets.py, please add them there!") raise -wifi.radio.connect(secrets["ssid"], secrets["wifi_password"]) +wifi.radio.connect(secrets["ssid"], secrets["password"]) pool = socketpool.SocketPool(wifi.radio) ntp = adafruit_ntp.NTP(pool)