Skip to content

Instantly share code, notes, and snippets.

@anecdata
Last active February 23, 2023 19:22
Show Gist options
  • Save anecdata/1b09c992df194b9fa6033ad58c6e1e49 to your computer and use it in GitHub Desktop.
Save anecdata/1b09c992df194b9fa6033ad58c6e1e49 to your computer and use it in GitHub Desktop.
NTP: manual or automatic (for CircuitPython ports with native wifi) & alternative(s)
import time
import rtc
import wifi
import socketpool
import adafruit_ntp
from secrets import secrets
pool = socketpool.SocketPool(wifi.radio)
ntp = adafruit_ntp.NTP(pool, tz_offset=0)
wifi.radio.connect(secrets["ssid"], secrets["password"])
# manual - sync ntp to the internal rtc when it needs adjustment
rtci = rtc.RTC()
rtci.datetime = ntp.datetime
print(time.localtime())
# automatic - use ntp as the source for all time calls
rtc.set_time_source(ntp)
print(time.localtime())
@anecdata
Copy link
Author

anecdata commented Sep 25, 2022

It's unfortunate that datetime means two different things in different libraries.

In the datetime library, class datetime.datetime has year, month, day, hour, minute, second, microsecond, and tzinfo.

But ntp.datetime and rtc.datetime and the .datetime property of the libraries for the extertnal RTC modules return a struct_time with some subset of year, month, mday, hour, min, sec, wday, yday, isdst, zone, and gmtoff (no fractional seconds).

The NTP protocol, used by the adafruit_NTP library, potentially has extremely precise fractional seconds, but the library currently ignores the fractional part. The fraction can be obtained using:

struct.unpack_from("!I", self._packet, offset=44)[0] / 2**32

or

struct.unpack_from(“!B”, self._packet, offset=44)[0] / 256

The latter gives enough precision for rough milliseconds, which is probably more than enough precision given the accuracy of NTP in a networked CircuitPython environment. It could be exposed with an additional property.

@anecdata
Copy link
Author

anecdata commented Sep 25, 2022

An alternative process for sub-second timestamps could be:

import time
import rtc
import random

# track rtc timestamps with monotonic (but not accurate) fractional seconds)
# rtc can be fed from any source of struct_time

current_s = 0
current_monotonic_ns = time.monotonic_ns()
while True:
    event = True  # some loggable activity happens
    if event:
        rtc_s = time.mktime(rtc.RTC().datetime)
        if rtc_s != current_s:
            print()
            current_s = rtc_s
            current_monotonic_ns = time.monotonic_ns()
            new_s_indicator = "*"
        else:
            new_s_indicator = "-"
        interval_ns = time.monotonic_ns() - current_monotonic_ns
        bodged_float_str = f"{rtc_s}" + "." + f"{(interval_ns / 1_000_000_000):0.3f}".split("0.")[-1]
        print(f"{new_s_indicator} {rtc_s} {bodged_float_str: 18}")  # , end="\r")
    time.sleep(random.random())  # simulate random event intervals

The fractional seconds are not accurate relative to real clock time, but the intervals between are reasonably accurate. The fractional seconds are strictly monotonic, so preserve the sequence of timestamps.

Sample output:

* 1664226131 1664226131.000    
- 1664226131 1664226131.157    

* 1664226132 1664226132.000    
- 1664226132 1664226132.062    
- 1664226132 1664226132.669    

* 1664226133 1664226133.000    
- 1664226133 1664226133.100    
- 1664226133 1664226133.110    
- 1664226133 1664226133.639    

* 1664226134 1664226134.000    

* 1664226135 1664226135.000    
- 1664226135 1664226135.162    
- 1664226135 1664226135.400    

* 1664226136 1664226136.000    

* 1664226137 1664226137.000    
- 1664226137 1664226137.512    

* 1664226138 1664226138.000    
- 1664226138 1664226138.549    

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment