async def _poller(self): self._status = 0 while True: # print("\nperforming NTP query") try: self.status = (self._status << 1) & 0xFFFF (delay_us, step_us) = await self._poll() if step_us > self._max_step or -step_us > self._max_step: # large adjustment: step the clock, don't slew # print(time.localtime()) (tgt_s, tgt_us) = divmod(time.time_us() + step_us, 1000000) log.warning("stepping to %s", time.localtime(tgt_s)) settime(tgt_s, tgt_us) # print(time.localtime()) # if the adjustment is too small WRT round-trip delay, it's likely not to be # very good and result in lots of noise, so skip elif step_us > 2 * delay_us or -step_us > 2 * delay_us: lvl = logging.DEBUG if abs( step_us) < 10000 else logging.INFO log.log(lvl, "adjusting by %dus (delay=%dus)", step_us, delay_us) adjtime(step_us) self.status |= 1 await asyncio.sleep(61) except asyncio.TimeoutError: log.warning("%s timed out", self._host) if (self._status & 0x7) == 0: # Three failures in a row, force fresh DNS look-up self.sock = None await asyncio.sleep(11) except OSError as e: # Most likely DNS lookup failure log.warning("%s: %s", self._host, e) self.sock = None await asyncio.sleep(11) except Exception as e: log.error("%s", e) print_exception(e) await asyncio.sleep(121)
async def _poller(self): self._status = 0 while True: # print("\nperforming NTP query") try: self.status = (self._status << 1) & 0xFFFF (delay_us, step_us) = await self._poll() if step_us > self._max_step or -step_us > self._max_step: # print(time.localtime()) (tgt_s, tgt_us) = divmod(time.time_us() + step_us, 1000000) log.warning("stepping to %s", time.localtime(tgt_s)) settime(tgt_s, tgt_us) # print(time.localtime()) else: lvl = logging.DEBUG if abs( step_us) < 10000 else logging.INFO log.log(lvl, "adjusting by %dus (delay=%dus)", step_us, delay_us) adjtime(step_us) self.status |= 1 await asyncio.sleep(61) except asyncio.TimeoutError: log.warning("%s timed out", self._host) if (self._status & 0x7) == 0: # Three failures in a row, force fresh DNS look-up self.sock = None await asyncio.sleep(11) except OSError as e: # Most likely DNS lookup failure log.warning("%s: %s", self._host, e) self.sock = None await asyncio.sleep(11) except Exception as e: log.error("%s", e) print_exception(e) await asyncio.sleep(121)
async def _poll(self): # We try to stay with the same server as long as possible. Only # lookup the address on startup or after errors. if self._sock is None: self._addr = socket.getaddrinfo(self._host, 123)[0][-1] log.debug("server %s->%s", self._host, self._addr) if sys.implementation.name == "micropython": self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._sock.connect(self._addr) stream = asyncio.StreamReader(self._sock) async def write_drain(pkt): stream.write(pkt) await stream.drain() self._send = write_drain self._recv = lambda length: stream.read(length) self._close = lambda: self._sock.close() else: stream = await dgram_connect(self._addr) async def stream_send(pkt): return await stream.send(pkt) self._send = stream_send async def stream_recv(length): return (await stream.recv())[0] self._recv = stream_recv self._close = lambda: stream.close() # Send the NTP v3 request to the server wbuf = bytearray(48) wbuf[0] = 0b00011011 send_us = time_us() send_ntp = mp2ntp(send_us) struct.pack_into("!II", wbuf, OFF_TX, send_ntp[0], send_ntp[1]) # set tx timestamp await self._send(wbuf) # Get server reply while True: # Raises asyncio.TimeoutError on time-out rbuf = await asyncio.wait_for(self._recv(48), timeout=1) recv_us = time_us() # Verify it's truly a response to our request orig_ntp = struct.unpack_from("!II", rbuf, OFF_ORIG) # get originate timestamp if orig_ntp == send_ntp: break # Calculate clock step to apply per RFC4330 rx_us = ntp2mp(*struct.unpack_from( "!II", rbuf, OFF_RX)) # get server recv timestamp tx_us = ntp2mp(*struct.unpack_from( "!II", rbuf, OFF_TX)) # get server transmit timestamp delay = (recv_us - send_us) - (tx_us - rx_us) step = ((rx_us - send_us) + (tx_us - recv_us)) // 2 tup = struct.unpack_from("!IIIIII", rbuf, OFF_ORIG) r = mp2ntp(recv_us) # log.debug( "orig=[%d,%x] rx=[%d,%x] tx=[%d,%x] recv=[%d,%x] -> delay=%fms step=%dus", # tup[0], tup[1], tup[2], tup[3], tup[4], tup[5], r[0], r[1], delay / 1000, step) return (delay, step)
def adjtime(usecs): print("adjtime(%d) - an adjustment of %d" % (usecs, time_us() - (usecs + UNIX_DELTA)))
def settime(usecs): print("settime(%d) - a step of %d" % (usecs, time_us() - (usecs + UNIX_DELTA)))