""" ntptime2 ntptime rewritten to improve the APIs and the communication with (pools of) NTP server. At 2023-05-06: https://github.com/micropython/micropython-lib/blob/master/micropython/net/ntptime/ntptime.py Note: time.time() is platform dependant. See time_to_posix(). Some micros use the epoch of 2000-01-01 00:00:00 UTC There's currently no timezone support in MicroPython; the RTC is set in UTC time. Stable API : settime() New API : fetch_ntp_time(), ntp_to_time(), posix_to_time(), rfc2822_datetime(), time_to_posix() Broken API : time() is no more exposed, renamed to _time() The correct method to set the RTC and get the current time() is: - call once ntptime2.settime() - use time.time() Platforms: python, micropython Author: massimo.sala.71@gmail.com """ #----------------------------------------------------------------- # set this parameters before calling fetch_ntp_time() / settime() host = "pool.ntp.org" timeout = 2 #----------------------------------------------------------------- from compatibility import * from time import gmtime import socket from select import select gc_collect(__name__) MONTHS = const(b"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec") WEEK_DAYS = const(b"Mon Tue Wed Thu Fri Sat Sun") RFC2822_UTC_FORMAT = const(b"%s, %02d %s %d %02d:%02d:%02d GMT") NTP_TO_POSIX_DELTA = const(2208988800) NTP_TO_Y2K_DELTA = const(3155673600) Y2K_TO_POSIX_DELTA = const(946684800) # yes, it isn't NTP_TO_Y2K_DELTA - NTP_TO_POSIX_DELTA # NTP protocol _pkt_size = const(48) _V3_CLIENT_query = const(b"\x1b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") _V3_SERVER = const(0x1c) _V4_SERVER = const(0x24) _LI_NOT_SYNC = const(3) _LOCALTIME_BASE_YEAR = gmtime(0)[0] def fetch_ntp_time() : # Output: seconds from 1900-01-01 # Note: DO NOT use in tight loops: you flood the servers, they will ban your requests. try : addrs = socket.getaddrinfo(host, 123) except Exception as ex : printf("fetch_ntp_time: getaddrinfo error", host, ex) return for _host in addrs : _host = _host[-1] client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try : client.sendto(_V3_CLIENT_query, _host) resp = None rx, _, err = select([client], [], [client], timeout) if err : raise RuntimeError("socket.select: error") if rx : resp = client.recv(_pkt_size) if not resp : raise RuntimeError("timed out") if len(resp) == _pkt_size : LI = resp[0] >> 6 mode = resp[0] & 0x3f stratum = resp[1] if mode in [_V3_SERVER, _V4_SERVER] and LI != _LI_NOT_SYNC and stratum >= 1 and stratum <= 15 : i = int.from_bytes(resp[40 : 44], "big") if i : # RFC: 0 is invalid return i raise RuntimeError("invalid reply") except Exception as ex : # keep going with the next server printf("fetch_ntp_time", _host, ex) finally : client.close() def ntp_to_time(ntp) : # Convert NTP time to local time.time() if _LOCALTIME_BASE_YEAR == 1970 : return ntp - NTP_TO_POSIX_DELTA elif _LOCALTIME_BASE_YEAR == 2000 : return ntp - NTP_TO_Y2K_DELTA raise OSError("Unsupported python epoch: base year is %d" % _LOCALTIME_BASE_YEAR) def posix_to_time(tim) : # Convert POSIX time to local time.time() if _LOCALTIME_BASE_YEAR == 1970 : return tim elif _LOCALTIME_BASE_YEAR == 2000 : return tim - Y2K_TO_POSIX_DELTA raise OSError("Unsupported python epoch: base year is %d" % _LOCALTIME_BASE_YEAR) def time_to_posix(tim) : # Convert local time.time() to POSIX time if _LOCALTIME_BASE_YEAR == 1970 : return tim elif _LOCALTIME_BASE_YEAR == 2000 : return tim + Y2K_TO_POSIX_DELTA raise OSError("Unsupported python epoch: base year is %d" % _LOCALTIME_BASE_YEAR) def _time() : # Output: local time.time() retrieved from Internet. # Notes: internal function, used by settime(); see fetch_ntp_time() notes. ntp = fetch_ntp_time() if not ntp : raise RuntimeError("Cannot acquire NTP time from %s" % host) return ntp_to_time(ntp) def settime(tim = None) : # Set the board RTC. # Input: local time.time(); without an argument it calls _time(), see its notes. if tim is None : tim = _time() tm = gmtime(tim) if not MICRO : printf("Unsupported platform, no RTC") return from machine import RTC RTC().datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0)) def rfc2822_datetime(tim) : # Input: local time.time() # Output: used in HTTP headers, so its in bytes year, month, mday, hour, minute, second, wday = gmtime(tim)[: 7] wday *= 4 month = (month - 1) * 4 return RFC2822_UTC_FORMAT % (WEEK_DAYS[wday : wday + 3], mday, MONTHS[month : month + 3], year, hour, minute, second) #ISO8601_UTC_FORMAT = const("%Y-%m-%dT%H:%M:%SZ") # return ISO8601_UTC_FORMAT % (year, month, mday, hour, minute, second)