AceTime  1.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.h
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_NTP_CLOCK_H
7 #define ACE_TIME_NTP_CLOCK_H
8 
9 #if defined(ESP8266) || defined(ESP32)
10 
11 #include <stdint.h>
12 #if defined(ESP8266)
13  #include <ESP8266WiFi.h>
14 #else
15  #include <WiFi.h>
16 #endif
17 #include <WiFiUdp.h>
18 #include "../common/logging.h"
19 #include "Clock.h"
20 
21 extern "C" unsigned long millis();
22 
23 #ifndef ACE_TIME_NTP_CLOCK_DEBUG
24 #define ACE_TIME_NTP_CLOCK_DEBUG 0
25 #endif
26 
27 namespace ace_time {
28 namespace clock {
29 
43 class NtpClock: public Clock {
44  public:
46  static const char kNtpServerName[];
47 
49  static const uint16_t kLocalPort = 8888;
50 
52  static const uint16_t kRequestTimeout = 1000;
53 
60  explicit NtpClock(
61  const char* server = kNtpServerName,
62  uint16_t localPort = kLocalPort,
63  uint16_t requestTimeout = kRequestTimeout):
64  mServer(server),
65  mLocalPort(localPort),
66  mRequestTimeout(requestTimeout) {}
67 
78  void setup(const char* ssid = nullptr, const char* password = nullptr,
79  uint16_t connectTimeoutMillis = kConnectTimeoutMillis) {
80  if (ssid) {
81  WiFi.begin(ssid, password);
82  uint16_t startMillis = millis();
83  while (WiFi.status() != WL_CONNECTED) {
84  uint16_t elapsedMillis = millis() - startMillis;
85  if (elapsedMillis >= connectTimeoutMillis) {
86  mIsSetUp = false;
87  return;
88  }
89 
90  delay(500);
91  }
92  }
93 
94  mUdp.begin(mLocalPort);
95 
96  #if ACE_TIME_NTP_CLOCK_DEBUG == 1
97  #if defined(ESP8266)
98  SERIAL_PORT_MONITOR.print(F("Local port: "));
99  SERIAL_PORT_MONITOR.println(mUdp.localPort());
100  #endif
101  #endif
102 
103  mIsSetUp = true;
104  }
105 
106  const char* getServer() const { return mServer; }
107 
108  bool isSetup() const { return mIsSetUp; }
109 
110  acetime_t getNow() const override {
111  if (!mIsSetUp || WiFi.status() != WL_CONNECTED) return kInvalidSeconds;
112 
113  sendRequest();
114 
115  uint16_t startTime = millis();
116  while ((uint16_t) (millis() - startTime) < mRequestTimeout) {
117  if (isResponseReady()) {
118  return readResponse();
119  }
120  }
121  return kInvalidSeconds;
122  }
123 
124  void sendRequest() const override {
125  if (!mIsSetUp || WiFi.status() != WL_CONNECTED) return;
126 
127  // discard any previously received packets
128  while (mUdp.parsePacket() > 0) {}
129 
130  // Get a random server from the pool. Unfortunately, hostByName() is a
131  // blocking is a blocking call. So if the DNS resolver goes flaky,
132  // everything stops.
133  //
134  // TODO: Change to a non-blocking NTP library.
135  // TODO: check return value of hostByName() for errors
136  // When there is an error, the ntpServerIP seems to become "0.0.0.0".
137  IPAddress ntpServerIP;
138  WiFi.hostByName(mServer, ntpServerIP);
139  sendNtpPacket(ntpServerIP);
140  }
141 
142  bool isResponseReady() const override {
143  if (!mIsSetUp || WiFi.status() != WL_CONNECTED) return false;
144  return mUdp.parsePacket() >= kNtpPacketSize;
145  }
146 
147  acetime_t readResponse() const override {
148  if (!mIsSetUp || WiFi.status() != WL_CONNECTED) return kInvalidSeconds;
149 
150  // read packet into the buffer
151  mUdp.read(mPacketBuffer, kNtpPacketSize);
152 
153  // convert four bytes starting at location 40 to a long integer
154  uint32_t secsSince1900 = (uint32_t) mPacketBuffer[40] << 24;
155  secsSince1900 |= (uint32_t) mPacketBuffer[41] << 16;
156  secsSince1900 |= (uint32_t) mPacketBuffer[42] << 8;
157  secsSince1900 |= (uint32_t) mPacketBuffer[43];
158 
159  return (secsSince1900 == 0)
160  ? kInvalidSeconds
161  : secsSince1900 - kSecondsSinceNtpEpoch;
162  }
163 
164  private:
166  static const uint8_t kNtpPacketSize = 48;
167 
172  static const uint32_t kSecondsSinceNtpEpoch = 3155673600;
173 
175  static const uint16_t kConnectTimeoutMillis = 10000;
176 
178  void sendNtpPacket(const IPAddress& address) const {
179 #if ACE_TIME_NTP_CLOCK_DEBUG == 1
180  uint16_t startTime = millis();
181 #endif
182  // set all bytes in the buffer to 0
183  memset(mPacketBuffer, 0, kNtpPacketSize);
184  // Initialize values needed to form NTP request
185  // (see URL above for details on the packets)
186  mPacketBuffer[0] = 0b11100011; // LI, Version, Mode
187  mPacketBuffer[1] = 0; // Stratum, or type of clock
188  mPacketBuffer[2] = 6; // Polling Interval
189  mPacketBuffer[3] = 0xEC; // Peer Clock Precision
190  // 8 bytes of zero for Root Delay & Root Dispersion
191  mPacketBuffer[12] = 49;
192  mPacketBuffer[13] = 0x4E;
193  mPacketBuffer[14] = 49;
194  mPacketBuffer[15] = 52;
195  // all NTP fields have been given values, now
196  // you can send a packet requesting a timestamp:
197  mUdp.beginPacket(address, 123); //NTP requests are to port 123
198  mUdp.write(mPacketBuffer, kNtpPacketSize);
199  mUdp.endPacket();
200 #if ACE_TIME_NTP_CLOCK_DEBUG == 1
201  logging::printf("NtpClock::sendNtpPacket(): %u ms\n",
202  (unsigned) ((uint16_t) millis() - startTime));
203 #endif
204  }
205 
206  const char* const mServer;
207  uint16_t const mLocalPort;
208  uint16_t const mRequestTimeout;
209 
210  mutable WiFiUDP mUdp;
211  // buffer to hold incoming & outgoing packets
212  mutable uint8_t mPacketBuffer[kNtpPacketSize];
213  bool mIsSetUp = false;
214 };
215 
216 }
217 }
218 
219 #endif // defined(ESP8266) || defined(ESP32)
220 
221 #endif
ace_time::clock::NtpClock::sendRequest
void sendRequest() const override
Send a time request asynchronously.
Definition: NtpClock.h:124
ace_time::clock::NtpClock::kRequestTimeout
static const uint16_t kRequestTimeout
Request time out milliseconds.
Definition: NtpClock.h:52
ace_time::clock::NtpClock::kNtpServerName
static const char kNtpServerName[]
Default NTP Server.
Definition: NtpClock.h:46
ace_time::clock::NtpClock::isResponseReady
bool isResponseReady() const override
Return true if a response is ready.
Definition: NtpClock.h:142
ace_time::clock::NtpClock::NtpClock
NtpClock(const char *server=kNtpServerName, uint16_t localPort=kLocalPort, uint16_t requestTimeout=kRequestTimeout)
Constructor.
Definition: NtpClock.h:60
ace_time::clock::NtpClock
A Clock that retrieves the time from an NTP server.
Definition: NtpClock.h:43
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.h:110
ace_time::clock::Clock
Base class for objects that provide and store time.
Definition: Clock.h:20
ace_time::clock::NtpClock::readResponse
acetime_t readResponse() const override
Returns number of seconds since AceTime epoch (2000-01-01).
Definition: NtpClock.h:147
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.h:78
ace_time::clock::NtpClock::kLocalPort
static const uint16_t kLocalPort
Default port used for UDP packets.
Definition: NtpClock.h:49