AceTimeClock  1.0.3
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 is a blocking call. So if the DNS resolver goes flaky,
91  // everything stops.
92  //
93  // TODO: Change to a non-blocking NTP library.
94  // TODO: check return value of hostByName() for errors
95  // When there is an error, the ntpServerIP seems to become "0.0.0.0".
96  IPAddress ntpServerIP;
97  WiFi.hostByName(mServer, ntpServerIP);
98  sendNtpPacket(ntpServerIP);
99 }
100 
102 #if ACE_TIME_NTP_CLOCK_DEBUG >= 3
103  static uint8_t rateLimiter;
104 #endif
105 
106  if (!mIsSetUp) return false;
107  if (WiFi.status() != WL_CONNECTED) {
108  #if ACE_TIME_NTP_CLOCK_DEBUG >= 3
109  if (++rateLimiter == 0) {
110  SERIAL_PORT_MONITOR.print("F[256]");
111  }
112  #endif
113  return false;
114  }
115  #if ACE_TIME_NTP_CLOCK_DEBUG >= 3
116  if (++rateLimiter == 0) {
117  SERIAL_PORT_MONITOR.print(".[256]");
118  }
119  #endif
120 
121  return mUdp.parsePacket() >= kNtpPacketSize;
122 }
123 
124 acetime_t NtpClock::readResponse() const {
125  if (!mIsSetUp) return kInvalidSeconds;
126  if (WiFi.status() != WL_CONNECTED) {
127  #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
128  SERIAL_PORT_MONITOR.println("NtpClock::readResponse(): not connected");
129  #endif
130  return kInvalidSeconds;
131  }
132 
133  // read packet into the buffer
134  mUdp.read(mPacketBuffer, kNtpPacketSize);
135 
136  // convert four bytes starting at location 40 to a long integer
137  uint32_t secsSince1900 = (uint32_t) mPacketBuffer[40] << 24;
138  secsSince1900 |= (uint32_t) mPacketBuffer[41] << 16;
139  secsSince1900 |= (uint32_t) mPacketBuffer[42] << 8;
140  secsSince1900 |= (uint32_t) mPacketBuffer[43];
141 
142  acetime_t epochSeconds = (secsSince1900 == 0)
144  : secsSince1900 - kSecondsSinceNtpEpoch;
145  #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
146  SERIAL_PORT_MONITOR.print(F("NtpClock::readResponse(): epoch="));
147  SERIAL_PORT_MONITOR.println(epochSeconds);
148  #endif
149  return epochSeconds;
150 }
151 
152 void NtpClock::sendNtpPacket(const IPAddress& address) const {
153 #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
154  uint16_t startTime = millis();
155 #endif
156 
157  // set all bytes in the buffer to 0
158  memset(mPacketBuffer, 0, kNtpPacketSize);
159  // Initialize values needed to form NTP request
160  // (see URL above for details on the packets)
161  mPacketBuffer[0] = 0b11100011; // LI, Version, Mode
162  mPacketBuffer[1] = 0; // Stratum, or type of clock
163  mPacketBuffer[2] = 6; // Polling Interval
164  mPacketBuffer[3] = 0xEC; // Peer Clock Precision
165  // 8 bytes of zero for Root Delay & Root Dispersion
166  mPacketBuffer[12] = 49;
167  mPacketBuffer[13] = 0x4E;
168  mPacketBuffer[14] = 49;
169  mPacketBuffer[15] = 52;
170  // all NTP fields have been given values, now
171  // you can send a packet requesting a timestamp:
172  mUdp.beginPacket(address, 123); //NTP requests are to port 123
173  mUdp.write(mPacketBuffer, kNtpPacketSize);
174  mUdp.endPacket();
175 
176 #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
177  SERIAL_PORT_MONITOR.print(F("NtpClock::sendNtpPacket(): "));
178  SERIAL_PORT_MONITOR.print((unsigned) ((uint16_t) millis() - startTime));
179  SERIAL_PORT_MONITOR.println(" ms");
180 #endif
181 }
182 
183 } // clock
184 } // ace_time
185 
186 #endif
ace_time::clock::NtpClock::sendRequest
void sendRequest() const override
Send a time request asynchronously.
Definition: NtpClock.cpp:72
ace_time::clock::NtpClock::kNtpServerName
static const char kNtpServerName[]
Default NTP Server.
Definition: NtpClock.h:51
ace_time::clock::NtpClock::isResponseReady
bool isResponseReady() const override
Return true if a response is ready.
Definition: NtpClock.cpp:101
ace_time::clock::NtpClock::getNow
acetime_t getNow() const override
Return the number of seconds since the AceTime epoch (2000-01-01T00:00:00Z).
Definition: NtpClock.cpp:58
ace_time::clock::NtpClock::readResponse
acetime_t readResponse() const override
Returns number of seconds since AceTime epoch (2000-01-01).
Definition: NtpClock.cpp:124
ace_time::clock::Clock::kInvalidSeconds
static const acetime_t kInvalidSeconds
Error value returned by getNow() and other methods when this object is not yet initialized.
Definition: Clock.h:25
ace_time::clock::NtpClock::setup
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