I've been re-purposing old cell phones as clocks for several years.
Android 2.2.2 - Froyo. This one still going, its sibling has failed. And more recent phones have failed.
Two have just failed. That's the last one running...good hardware. Really old OS, but still gets the time off the network and still updates when we switch to daylight time and back.
So I need to replace them, and I started thinking about Arduinos and Raspberries and stumbled into ESP32.
Quarter inch squares, this is about 25cm x 50cm
This is from espressif and I got this one on amazon, 3 for $17. So far I've played with blinking an LED and using the built-in WIFI hardware. Yes, WIFI, and blue tooth, and lots of I/O pins, 4M rom and 512K ram (I think. have not explored far enough to know for sure.) In the process, I ran the NTP (Network Time Protocol) example and started wondering how NTP works.
At the bottom, NTP is really simple. A computer that wants to know the time sends a short message to another computer that hosts an NTP server, and that computer sends a short message back that includes the GMT time. Done.
(It is much more complicated; apparently the NTP servers may answer the question 1000 times a second! And the mechanisms for synchronizing the servers use statistics. And David L Mills invented the NTP stuff around 1980.)
The internet mostly uses TCP (Transmission Control Protocol), a robust protocol built on top of IP. TCP retries, etc, to make sure data is delivered, in order, and intact. It is a heavy-weight protocol. NTP servers use a light weight protocol, UDP (also on top of IP) which guarantees nothing. Your messages might get lost, never arrive, arrive out of order. But the load on the server is minimal; the server doesn't need to do any extra hand-shaking to make sure you get an answer. The U in UDP (User Datagram Protocol) means the user is responsible for handling errors.
This JSL creates a socket to use port 123 (the NTP port) and the UDP protocol to send a message to a pool of NTP servers and get the response. It does some really simple statistics math that assumes the travel time to and from the server are equal so the server's time can be synced with the local computer better. The results are pretty good! Here's a slightly better than average example that found a server in the pool half-way around the world:
pool.ntp.org(rn5.quickhost.hk)
TotalTime = 0.143846988677979
FlightTime = 0.143799781799316
Difference = 0:00:00.01689
Assuming .hk is really in Hong Kong, that's 8,000 miles from North Carolina. 16,000 round trip. The Flight Time is the Total Time less the processing time in Hong Kong, which was negligible. If I did my math right, that's more than 100,000 miles/second, a significant fraction of the speed of light.
The last number, difference, is the difference between my CPU clock and the NTP server's clock. That's less than 1/50 second. And I get results from most servers within 1/10 second, unless the flight time is large, in which case it is asymmetrical and the correction technique breaks down.
pool.ntp.org(LAX.CALTICK.NET)
TotalTime = 0.101963043212891
FlightTime = 0.101903915405273
Difference = 0:00:00.08646
pool.ntp.org(ns2.nuso.cloud)
TotalTime = 0.103617668151855
FlightTime = 0.103596687316895
Difference = 0:00:00.04034
pool.ntp.org(50-205-57-38-static.hfc.comcastbusiness.net)
TotalTime = 0.0763883590698242
FlightTime = 0.0763206481933594
Difference = 0:00:00.00965
pool.ntp.org(13.86.101.172)
TotalTime = 0.077089786529541
FlightTime = 0.07708740234375
Difference = 0:00:00.00663
timezone = -4;
xPacket = Hex To Blob( "1b" || Repeat( "00", 47 ) );
sock = Socket( DGRAM );
addr = "pool.ntp.org";
rc = sock << bind( addr, "123" );
If( rc[2] != "ok",
sock << close;
Throw( "bad bind" );
);
rc = sock << Ioctl( FIONBIO, 1 );
If( rc[2] != "ok",
sock << close;
Throw( "bad ioctl" );
);
sendHPtime = HP Time();
rc = sock << sendto( addr, "123", xPacket );
If( rc[2] != "ok",
sock << close;
Throw( "bad send" );
);
timeOut = Tick Seconds() + 5;
While( Tick Seconds() < timeOut,
Wait( 0 );
rc = sock << recvfrom( 48 );
recvHPtime = HP Time();
If( !Starts With( rc[2], "WOULDBLOCK" ),
Break()
);
);
If( rc[2] != "ok" | Length( rc[3] ) != 48,
sock << close;
Throw( Char( rc ) || " time out" );
);
sock << close;
serverName = rc[4];
rPacket = rc[3];
rIntMatrix = Blob To Matrix( rPacket, "uint", 4, "big" );
rxTm_s = rIntMatrix[9];
rxTm_f = rIntMatrix[10];
txTm_s = rIntMatrix[11];
txTm_f = rIntMatrix[12];
rxTm = As Date( rxTm_s + rxTm_f / (2 ^ 32) + 1jan1900 );
txTm = As Date( txTm_s + txTm_f / (2 ^ 32) + 1jan1900 );
oldTd = Today();
While( (nowHp = HP Time() ; newTd = Today()) == oldTd, 0 );
addToHpSeconds = newTd - nowHp / 1e6;
origTm = sendHPtime / 1e6 + addToHpSeconds;
destTm = recvHPtime / 1e6 + addToHpSeconds;
flightTime = (destTm - origTm) - (txTm - rxTm);
returnTime = flightTime / 2;
localTimeWhenServerSent = destTm - returnTime;
lagtime = (txTm + In Hours( timezone )) - (localTimeWhenServerSent);
Write( "\!n", addr, "(", serverName, ")",
"\!n TotalTime =\!t", destTm - origTm,
"\!n FlightTime =\!t", flightTime,
"\!n Difference =\!t", Format( lagtime, "hr:m:s", 15, 5 )
);