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