AceTimeClock  1.2.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)
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  // convert four bytes starting at location 40 to a long integer
136  uint32_t secsSince1900 = (uint32_t) mPacketBuffer[40] << 24;
137  secsSince1900 |= (uint32_t) mPacketBuffer[41] << 16;
138  secsSince1900 |= (uint32_t) mPacketBuffer[42] << 8;
139  secsSince1900 |= (uint32_t) mPacketBuffer[43];
140 
141  acetime_t epochSeconds = (secsSince1900 == 0)
143  : secsSince1900 - kSecondsSinceNtpEpoch;
144  #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
145  SERIAL_PORT_MONITOR.print(F("NtpClock::readResponse(): epoch="));
146  SERIAL_PORT_MONITOR.println(epochSeconds);
147  #endif
148  return epochSeconds;
149 }
150 
151 void NtpClock::sendNtpPacket(const IPAddress& address) const {
152 #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
153  uint16_t startTime = millis();
154 #endif
155 
156  // set all bytes in the buffer to 0
157  memset(mPacketBuffer, 0, kNtpPacketSize);
158  // Initialize values needed to form NTP request
159  // (see URL above for details on the packets)
160  mPacketBuffer[0] = 0b11100011; // LI, Version, Mode
161  mPacketBuffer[1] = 0; // Stratum, or type of clock
162  mPacketBuffer[2] = 6; // Polling Interval
163  mPacketBuffer[3] = 0xEC; // Peer Clock Precision
164  // 8 bytes of zero for Root Delay & Root Dispersion
165  mPacketBuffer[12] = 49;
166  mPacketBuffer[13] = 0x4E;
167  mPacketBuffer[14] = 49;
168  mPacketBuffer[15] = 52;
169  // all NTP fields have been given values, now
170  // you can send a packet requesting a timestamp:
171  mUdp.beginPacket(address, 123); //NTP requests are to port 123
172  mUdp.write(mPacketBuffer, kNtpPacketSize);
173  mUdp.endPacket();
174 
175 #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
176  SERIAL_PORT_MONITOR.print(F("NtpClock::sendNtpPacket(): "));
177  SERIAL_PORT_MONITOR.print((unsigned) ((uint16_t) millis() - startTime));
178  SERIAL_PORT_MONITOR.println(" ms");
179 #endif
180 }
181 
182 } // clock
183 } // ace_time
184 
185 #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
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:51
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