AceTime  0.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.
NtpTimeProvider.h
1 #ifndef ACE_TIME_NTP_TIME_PROVIDER_H
2 #define ACE_TIME_NTP_TIME_PROVIDER_H
3 
4 #if defined(ESP8266) || defined(ESP32)
5 
6 #include <stdint.h>
7 #if defined(ESP8266)
8  #include <ESP8266WiFi.h>
9 #else
10  #include <WiFi.h>
11 #endif
12 #include <WiFiUdp.h>
13 #include "../common/logger.h"
14 #include "TimeKeeper.h"
15 
16 #ifndef ACE_TIME_NTP_TIME_PROVIDER_DEBUG
17 #define ACE_TIME_NTP_TIME_PROVIDER_DEBUG 0
18 #endif
19 
20 namespace ace_time {
21 namespace clock {
22 
36 class NtpTimeProvider: public TimeProvider {
37  public:
39  static const char kNtpServerName[];
40 
42  static const uint16_t kLocalPort = 8888;
43 
45  static const uint16_t kRequestTimeout = 1000;
46 
55  explicit NtpTimeProvider(
56  const char* server = kNtpServerName,
57  uint16_t localPort = kLocalPort,
58  uint16_t requestTimeout = kRequestTimeout):
59  mServer(server),
60  mLocalPort(localPort),
61  mRequestTimeout(requestTimeout) {}
62 
64  void setup(const char* ssid, const char* password);
65 
66  const char* getServer() const { return mServer; }
67 
68  bool isSetup() const { return mIsSetUp; }
69 
70  acetime_t getNow() const override {
71  if (!mIsSetUp) return kInvalidSeconds;
72 
73  sendRequest();
74 
75  uint16_t startTime = millis();
76  while ((uint16_t) (millis() - startTime) < mRequestTimeout) {
77  if (isResponseReady()) {
78  return readResponse();
79  }
80  }
81  return kInvalidSeconds;
82  }
83 
84  void sendRequest() const override {
85  if (!mIsSetUp) return;
86 
87  // discard any previously received packets
88  while (mUdp.parsePacket() > 0) {}
89 
90  // Get a random server from the pool. Unfortunately, hostByName() is a
91  // blocking is a blocking call. So if the DNS resolver goes flaky,
92  // everything stops.
93  //
94  // TODO: Change to a non-blocking NTP library.
95  // TODO: check return value of hostByName() for errors
96  // When there is an error, the ntpServerIP seems to become "0.0.0.0".
97  IPAddress ntpServerIP;
98  WiFi.hostByName(mServer, ntpServerIP);
99  sendNtpPacket(ntpServerIP);
100  }
101 
102  bool isResponseReady() const override {
103  if (!mIsSetUp) return false;
104  return mUdp.parsePacket() >= kNtpPacketSize;
105  }
106 
107  acetime_t readResponse() const override {
108  if (!mIsSetUp) return kInvalidSeconds;
109 
110  // read packet into the buffer
111  mUdp.read(mPacketBuffer, kNtpPacketSize);
112 
113  // convert four bytes starting at location 40 to a long integer
114  uint32_t secsSince1900 = (uint32_t) mPacketBuffer[40] << 24;
115  secsSince1900 |= (uint32_t) mPacketBuffer[41] << 16;
116  secsSince1900 |= (uint32_t) mPacketBuffer[42] << 8;
117  secsSince1900 |= (uint32_t) mPacketBuffer[43];
118 
119  return (secsSince1900 == 0)
120  ? kInvalidSeconds
121  : secsSince1900 - kSecondsSinceNtpEpoch;
122  }
123 
124  private:
126  static const uint8_t kNtpPacketSize = 48;
127 
132  static const uint32_t kSecondsSinceNtpEpoch = 3155673600;
133 
135  static const uint16_t kConnectTimeoutMillis = 5000;
136 
138  void sendNtpPacket(const IPAddress& address) const {
139 #if ACE_TIME_NTP_TIME_PROVIDER_DEBUG == 1
140  uint16_t startTime = millis();
141 #endif
142  // set all bytes in the buffer to 0
143  memset(mPacketBuffer, 0, kNtpPacketSize);
144  // Initialize values needed to form NTP request
145  // (see URL above for details on the packets)
146  mPacketBuffer[0] = 0b11100011; // LI, Version, Mode
147  mPacketBuffer[1] = 0; // Stratum, or type of clock
148  mPacketBuffer[2] = 6; // Polling Interval
149  mPacketBuffer[3] = 0xEC; // Peer Clock Precision
150  // 8 bytes of zero for Root Delay & Root Dispersion
151  mPacketBuffer[12] = 49;
152  mPacketBuffer[13] = 0x4E;
153  mPacketBuffer[14] = 49;
154  mPacketBuffer[15] = 52;
155  // all NTP fields have been given values, now
156  // you can send a packet requesting a timestamp:
157  mUdp.beginPacket(address, 123); //NTP requests are to port 123
158  mUdp.write(mPacketBuffer, kNtpPacketSize);
159  mUdp.endPacket();
160 #if ACE_TIME_NTP_TIME_PROVIDER_DEBUG == 1
161  logging::println("NtpTimeProvider::sendNtpPacket(): %u ms",
162  (uint16_t) (millis() - startTime));
163 #endif
164  }
165 
166  const char* const mServer; // TODO: make this configurable
167  uint16_t const mLocalPort;
168  uint16_t const mRequestTimeout;
169 
170  mutable WiFiUDP mUdp;
171  // buffer to hold incoming & outgoing packets
172  mutable uint8_t mPacketBuffer[kNtpPacketSize];
173  bool mIsSetUp = false;
174 };
175 
176 }
177 }
178 
179 #endif // defined(ESP8266) || defined(ESP32)
180 
181 #endif