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