AceTime  0.5.1
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 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_NTP_TIME_PROVIDER_H
7 #define ACE_TIME_NTP_TIME_PROVIDER_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 "TimeKeeper.h"
20 
21 #ifndef ACE_TIME_NTP_TIME_PROVIDER_DEBUG
22 #define ACE_TIME_NTP_TIME_PROVIDER_DEBUG 0
23 #endif
24 
25 extern "C" unsigned long millis();
26 
27 namespace ace_time {
28 namespace clock {
29 
43 class NtpTimeProvider: public TimeProvider {
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 NtpTimeProvider(
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_TIME_PROVIDER_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_TIME_PROVIDER_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_TIME_PROVIDER_DEBUG == 1
191  logging::println("NtpTimeProvider::sendNtpPacket(): %u ms",
192  (uint16_t) (millis() - startTime));
193 #endif
194  }
195 
196  const char* const mServer; // TODO: make this configurable
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