AceTime  1.7.3
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock 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 "../common/compat.h"
8 #include "../common/logging.h"
9 #include "NtpClock.h"
10 
11 #if defined(ESP8266) || defined(ESP32)
12 
13 namespace ace_time {
14 namespace clock {
15 
16 const char NtpClock::kNtpServerName[] = "us.pool.ntp.org";
17 
19  const char* ssid,
20  const char* password,
21  uint16_t connectTimeoutMillis
22 ) {
23  if (ssid) {
24  WiFi.mode(WIFI_STA);
25  WiFi.begin(ssid, password);
26  uint16_t startMillis = millis();
27  while (WiFi.status() != WL_CONNECTED) {
28  uint16_t elapsedMillis = millis() - startMillis;
29  if (elapsedMillis >= connectTimeoutMillis) {
30  #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
31  SERIAL_PORT_MONITOR.println(F("NtpClock::setup(): failed"));
32  #endif
33  mIsSetUp = false;
34  return;
35  }
36 
37  delay(500);
38  }
39  }
40 
41  mUdp.begin(mLocalPort);
42 
43 #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
44  SERIAL_PORT_MONITOR.print(F("NtpClock::setup(): connected to"));
45  SERIAL_PORT_MONITOR.println(WiFi.localIP());
46  #if defined(ESP8266)
47  SERIAL_PORT_MONITOR.print(F("Local port: "));
48  SERIAL_PORT_MONITOR.println(mUdp.localPort());
49  #endif
50 #endif
51 
52  mIsSetUp = true;
53 }
54 
55 acetime_t NtpClock::getNow() const {
56  if (!mIsSetUp || WiFi.status() != WL_CONNECTED) return kInvalidSeconds;
57 
58  sendRequest();
59 
60  uint16_t startTime = millis();
61  while ((uint16_t) (millis() - startTime) < mRequestTimeout) {
62  if (isResponseReady()) {
63  return readResponse();
64  }
65  }
66  return kInvalidSeconds;
67 }
68 
69 void NtpClock::sendRequest() const {
70  if (!mIsSetUp) return;
71  if (WiFi.status() != WL_CONNECTED) {
72  #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
73  SERIAL_PORT_MONITOR.println(
74  F("NtpClock::sendRequest(): not connected"));
75  #endif
76  return;
77  }
78 
79  // discard any previously received packets
80  while (mUdp.parsePacket() > 0) {}
81 
82  #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
83  SERIAL_PORT_MONITOR.println(F("NtpClock::sendRequest(): sending request"));
84  #endif
85 
86  // Get a random server from the pool. Unfortunately, hostByName() is a
87  // blocking is a blocking call. So if the DNS resolver goes flaky,
88  // everything stops.
89  //
90  // TODO: Change to a non-blocking NTP library.
91  // TODO: check return value of hostByName() for errors
92  // When there is an error, the ntpServerIP seems to become "0.0.0.0".
93  IPAddress ntpServerIP;
94  WiFi.hostByName(mServer, ntpServerIP);
95  sendNtpPacket(ntpServerIP);
96 }
97 
99 #if ACE_TIME_NTP_CLOCK_DEBUG >= 3
100  static uint8_t rateLimiter;
101 #endif
102 
103  if (!mIsSetUp) return false;
104  if (WiFi.status() != WL_CONNECTED) {
105  #if ACE_TIME_NTP_CLOCK_DEBUG >= 3
106  if (++rateLimiter == 0) {
107  SERIAL_PORT_MONITOR.print("F[256]");
108  }
109  #endif
110  return false;
111  }
112  #if ACE_TIME_NTP_CLOCK_DEBUG >= 3
113  if (++rateLimiter == 0) {
114  SERIAL_PORT_MONITOR.print(".[256]");
115  }
116  #endif
117 
118  return mUdp.parsePacket() >= kNtpPacketSize;
119 }
120 
121 acetime_t NtpClock::readResponse() const {
122  if (!mIsSetUp) return kInvalidSeconds;
123  if (WiFi.status() != WL_CONNECTED) {
124  #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
125  SERIAL_PORT_MONITOR.println("NtpClock::readResponse(): not connected");
126  #endif
127  return kInvalidSeconds;
128  }
129 
130  // read packet into the buffer
131  mUdp.read(mPacketBuffer, kNtpPacketSize);
132 
133  // convert four bytes starting at location 40 to a long integer
134  uint32_t secsSince1900 = (uint32_t) mPacketBuffer[40] << 24;
135  secsSince1900 |= (uint32_t) mPacketBuffer[41] << 16;
136  secsSince1900 |= (uint32_t) mPacketBuffer[42] << 8;
137  secsSince1900 |= (uint32_t) mPacketBuffer[43];
138 
139  acetime_t epochSeconds = (secsSince1900 == 0)
141  : secsSince1900 - kSecondsSinceNtpEpoch;
142  #if ACE_TIME_NTP_CLOCK_DEBUG >= 1
143  SERIAL_PORT_MONITOR.print(F("NtpClock::readResponse(): epoch="));
144  SERIAL_PORT_MONITOR.println(epochSeconds);
145  #endif
146  return epochSeconds;
147 }
148 
149 void NtpClock::sendNtpPacket(const IPAddress& address) const {
150 #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
151  uint16_t startTime = millis();
152 #endif
153 
154  // set all bytes in the buffer to 0
155  memset(mPacketBuffer, 0, kNtpPacketSize);
156  // Initialize values needed to form NTP request
157  // (see URL above for details on the packets)
158  mPacketBuffer[0] = 0b11100011; // LI, Version, Mode
159  mPacketBuffer[1] = 0; // Stratum, or type of clock
160  mPacketBuffer[2] = 6; // Polling Interval
161  mPacketBuffer[3] = 0xEC; // Peer Clock Precision
162  // 8 bytes of zero for Root Delay & Root Dispersion
163  mPacketBuffer[12] = 49;
164  mPacketBuffer[13] = 0x4E;
165  mPacketBuffer[14] = 49;
166  mPacketBuffer[15] = 52;
167  // all NTP fields have been given values, now
168  // you can send a packet requesting a timestamp:
169  mUdp.beginPacket(address, 123); //NTP requests are to port 123
170  mUdp.write(mPacketBuffer, kNtpPacketSize);
171  mUdp.endPacket();
172 
173 #if ACE_TIME_NTP_CLOCK_DEBUG >= 2
174  SERIAL_PORT_MONITOR.print(F("NtpClock::sendNtpPacket(): "));
175  SERIAL_PORT_MONITOR.print((unsigned) ((uint16_t) millis() - startTime));
176  SERIAL_PORT_MONITOR.println(" ms");
177 #endif
178 }
179 
180 } // clock
181 } // ace_time
182 
183 #endif
ace_time::clock::NtpClock::sendRequest
void sendRequest() const override
Send a time request asynchronously.
Definition: NtpClock.cpp:69
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:98
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:55
ace_time::clock::NtpClock::readResponse
acetime_t readResponse() const override
Returns number of seconds since AceTime epoch (2000-01-01).
Definition: NtpClock.cpp:121
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:26
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:18