AceTimeClock  1.3.0
Clock classes for Arduino that can synchronize from an NTP server or an RTC chip
NtpClock.cpp
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
6 #include <Arduino.h>
7 #include "NtpClock.h"
8 
9 #if defined(ESP8266) || defined(ESP32) || defined(EPOXY_CORE_ESP8266)
10 
11 // ESP32 does not define SERIAL_PORT_MONITOR
12 #ifndef SERIAL_PORT_MONITOR
13 #define SERIAL_PORT_MONITOR Serial
14 #endif
15 
16 namespace ace_time {
17 namespace clock {
18 
19 const char NtpClock::kNtpServerName[] = "us.pool.ntp.org";
20 
22  const char* ssid,
23  const char* password,
24  uint16_t connectTimeoutMillis
25 ) {
26  if (ssid) {
27  WiFi.mode(WIFI_STA);
28  WiFi.begin(ssid, password);
29  uint16_t startMillis = millis();
30  while (WiFi.status() != WL_CONNECTED) {
31  uint16_t elapsedMillis = millis() - startMillis;
32  if (elapsedMillis >= connectTimeoutMillis) {
33  #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
34  SERIAL_PORT_MONITOR.println(F("NtpClock::setup(): failed"));
35  #endif
36  mIsSetUp = false;
37  return;
38  }
39 
40  delay(500);
41  }
42  }
43 
44  mUdp.begin(mLocalPort);
45 
46 #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
47  SERIAL_PORT_MONITOR.print(F("NtpClock::setup(): connected to"));
48  SERIAL_PORT_MONITOR.println(WiFi.localIP());
49  #if defined(ESP8266)
50  SERIAL_PORT_MONITOR.print(F("Local port: "));
51  SERIAL_PORT_MONITOR.println(mUdp.localPort());
52  #endif
53 #endif
54 
55  mIsSetUp = true;
56 }
57 
58 acetime_t NtpClock::getNow() const {
59  if (!mIsSetUp || WiFi.status() != WL_CONNECTED) return kInvalidSeconds;
60 
61  sendRequest();
62 
63  uint16_t startTime = millis();
64  while ((uint16_t) (millis() - startTime) < mRequestTimeout) {
65  if (isResponseReady()) {
66  return readResponse();
67  }
68  }
69  return kInvalidSeconds;
70 }
71 
72 void NtpClock::sendRequest() const {
73  if (!mIsSetUp) return;
74  if (WiFi.status() != WL_CONNECTED) {
75  #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
76  SERIAL_PORT_MONITOR.println(
77  F("NtpClock::sendRequest(): not connected"));
78  #endif
79  return;
80  }
81 
82  // discard any previously received packets
83  while (mUdp.parsePacket() > 0) {}
84 
85  #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
86  SERIAL_PORT_MONITOR.println(F("NtpClock::sendRequest(): sending request"));
87  #endif
88 
89  // Get a random server from the pool. Unfortunately, hostByName() is a
90  // blocking call. So if the DNS resolver goes flaky, everything stops.
91  //
92  // TODO: Change to a non-blocking NTP library.
93  // TODO: check return value of hostByName() for errors
94  // When there is an error, the ntpServerIP seems to become "0.0.0.0".
95  IPAddress ntpServerIP;
96  WiFi.hostByName(mServer, ntpServerIP);
97  sendNtpPacket(ntpServerIP);
98 }
99 
101 #if ACE_TIME_NTP_CLOCK_DEBUG >= 3
102  static uint8_t rateLimiter;
103 #endif
104 
105  if (!mIsSetUp) return false;
106  if (WiFi.status() != WL_CONNECTED) {
107  #if ACE_TIME_NTP_CLOCK_DEBUG >= 3
108  if (++rateLimiter == 0) {
109  SERIAL_PORT_MONITOR.print("F[256]");
110  }
111  #endif
112  return false;
113  }
114  #if ACE_TIME_NTP_CLOCK_DEBUG >= 3
115  if (++rateLimiter == 0) {
116  SERIAL_PORT_MONITOR.print(".[256]");
117  }
118  #endif
119 
120  return mUdp.parsePacket() >= kNtpPacketSize;
121 }
122 
123 acetime_t NtpClock::readResponse() const {
124  if (!mIsSetUp) return kInvalidSeconds;
125  if (WiFi.status() != WL_CONNECTED) {
126  #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
127  SERIAL_PORT_MONITOR.println("NtpClock::readResponse(): not connected");
128  #endif
129  return kInvalidSeconds;
130  }
131 
132  // read packet into the buffer
133  mUdp.read(mPacketBuffer, kNtpPacketSize);
134 
135  // Extract the NTP seconds, an unsigned number of seconds since the NTP epoch
136  // of 1900-01-01. The NTP timestamp is a 32:32 fixed point number (64-bits
137  // total) starting at byte 40. The location is determined from the following
138  // headers (see https://en.wikipedia.org/wiki/User_Datagram_Protocol):
139  //
140  // * IP header - 12 bytes
141  // * UDP header - 8 bytes
142  // * NTP message packet
143  // (https://www.meinbergglobal.com/english/info/ntp-packet.htm):
144  // * flags - 4 bytes
145  // * Root delay - 4 bytes
146  // * Root dispersion - 4 bytes
147  // * Reference identifier - 4 bytes
148  // * Reference timestamp - 8 bytes (40th byte here)
149  // * whole seconds (unsigned 32 bits)
150  // * fractional seconds (unsigned 32 bits, in units of 1/2^32 seconds)
151  //
152  // The timestamp is stored in big-endian order.
153  uint32_t ntpSeconds = (uint32_t) mPacketBuffer[40] << 24;
154  ntpSeconds |= (uint32_t) mPacketBuffer[41] << 16;
155  ntpSeconds |= (uint32_t) mPacketBuffer[42] << 8;
156  ntpSeconds |= (uint32_t) mPacketBuffer[43];
157 
158  // Convert to AceTime epoch (as defined by Epoch::currentEpochYear()).
159  acetime_t epochSeconds = convertNtpSecondsToAceTimeSeconds(ntpSeconds);
160  #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
161  SERIAL_PORT_MONITOR.print(F("NtpClock::readResponse(): ntpSeconds="));
162  SERIAL_PORT_MONITOR.print(ntpSeconds);
163  SERIAL_PORT_MONITOR.print(F("; epochSeconds="));
164  SERIAL_PORT_MONITOR.println(epochSeconds);
165  #endif
166 
167  return epochSeconds;
168 }
169 
170 // NTP epoch is 1900-01-01. Unix epoch is 1970-01-01. GPS epoch is 1980-01-06.
171 // AceTime v2 epoch is 2050-01-01 by default but is adjustable at runtime.
172 acetime_t NtpClock::convertNtpSecondsToAceTimeSeconds(uint32_t ntpSeconds) {
173  // Sometimes the NTP packet is garbage and contains 0. Mark that as invalid.
174  // NOTE: Is this necessary? Let's comment it out for now.
175  //if (ntpSeconds == 0) return kInvalidSeconds;
176 
177  // Shift the NTP seconds to AceTime seconds, using uint32_t operations,
178  // which performs a shift using modulo 2^32 arithmetic. This maps the entire
179  // 32-bit range of NTP seconds to the 32-bit AceTime seconds, automatically
180  // accounting for NTP rollovers, for any AceTime currentEpochYear().
181  int32_t daysToCurrentEpochFromNtpEpoch =
182  Epoch::daysToCurrentEpochFromInternalEpoch()
183  + kDaysToInternalEpochFromNtpEpoch;
184  uint32_t secondsToCurrentEpochFromNtpEpoch = (uint32_t) 86400
185  * (uint32_t) daysToCurrentEpochFromNtpEpoch;
186  uint32_t epochSeconds = ntpSeconds - secondsToCurrentEpochFromNtpEpoch;
187 
188  // Cast the uint32_t to an int32_t, which has the effect of mapping the upper
189  // half of the AceTime seconds 32-bit range to its lower half. In other words,
190  // `if epochSeconds > INT32_MAX: epochSeconds -= 2^32`.
191  return (int32_t) epochSeconds;
192 }
193 
194 void NtpClock::sendNtpPacket(const IPAddress& address) const {
195 #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
196  uint16_t startTime = millis();
197 #endif
198 
199  // set all bytes in the buffer to 0
200  memset(mPacketBuffer, 0, kNtpPacketSize);
201  // Initialize values needed to form NTP request
202  // (see URL above for details on the packets)
203  mPacketBuffer[0] = 0b11100011; // LI, Version, Mode
204  mPacketBuffer[1] = 0; // Stratum, or type of clock
205  mPacketBuffer[2] = 6; // Polling Interval
206  mPacketBuffer[3] = 0xEC; // Peer Clock Precision
207  // 8 bytes of zero for Root Delay & Root Dispersion
208  mPacketBuffer[12] = 49;
209  mPacketBuffer[13] = 0x4E;
210  mPacketBuffer[14] = 49;
211  mPacketBuffer[15] = 52;
212  // all NTP fields have been given values, now
213  // you can send a packet requesting a timestamp:
214  mUdp.beginPacket(address, 123); //NTP requests are to port 123
215  mUdp.write(mPacketBuffer, kNtpPacketSize);
216  mUdp.endPacket();
217 
218 #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
219  SERIAL_PORT_MONITOR.print(F("NtpClock::sendNtpPacket(): "));
220  SERIAL_PORT_MONITOR.print((unsigned) ((uint16_t) millis() - startTime));
221  SERIAL_PORT_MONITOR.println(" ms");
222 #endif
223 }
224 
225 } // clock
226 } // ace_time
227 
228 #endif
static const acetime_t kInvalidSeconds
Error value returned by getNow() and other methods when this object is not yet initialized.
Definition: Clock.h:25
static acetime_t convertNtpSecondsToAceTimeSeconds(uint32_t ntpSeconds)
Convert an NTP seconds to AceTime seconds relative to the current AceTime epoch defined by Epoch::cur...
Definition: NtpClock.cpp:172
acetime_t readResponse() const override
Returns number of seconds since AceTime epoch (2000-01-01).
Definition: NtpClock.cpp:123
void setup(const char *ssid=nullptr, const char *password=nullptr, uint16_t connectTimeoutMillis=kConnectTimeoutMillis)
Set up the WiFi connection using the given ssid and password, and prepare the UDP connection.
Definition: NtpClock.cpp:21
static const char kNtpServerName[]
Default NTP Server.
Definition: NtpClock.h:71
bool isResponseReady() const override
Return true if a response is ready.
Definition: NtpClock.cpp:100
acetime_t getNow() const override
Return the number of seconds since the AceTime epoch (2000-01-01T00:00:00Z).
Definition: NtpClock.cpp:58
void sendRequest() const override
Send a time request asynchronously.
Definition: NtpClock.cpp:72