diff --git a/NTPClient.cpp b/NTPClient.cpp index b435855..095c66f 100755 --- a/NTPClient.cpp +++ b/NTPClient.cpp @@ -81,58 +81,67 @@ void NTPClient::begin(unsigned int port) { this->_udpSetup = true; } -bool NTPClient::forceUpdate() { +bool NTPClient::forceUpdate(unsigned long timeout) { + this->sendNTPPacket(); + #ifdef DEBUG_NTPClient - Serial.println("Update from NTP Server"); + Serial.println("[NTPClient] Waiting for NTP Server response..."); #endif - // flush any existing packets - while(this->_udp->parsePacket() != 0) - this->_udp->flush(); - - this->sendNTPPacket(); - // Wait till data is there or timeout... - byte timeout = 0; - int cb = 0; - do { - delay ( 10 ); - cb = this->_udp->parsePacket(); - if (timeout > 100) return false; // timeout after 1000 ms - timeout++; - } while (cb == 0); - - this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time + if (this->receiveNTPPacket(timeout)) return true; - this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE); - - unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]); - unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]); - // combine the four bytes (two words) into a long integer - // this is NTP time (seconds since Jan 1 1900): - unsigned long secsSince1900 = highWord << 16 | lowWord; + #ifdef DEBUG_NTPClient + Serial.println("[NTPClient] Timeout"); + #endif + return false; // timeout +} - this->_currentEpoc = secsSince1900 - SEVENZYYEARS; +bool NTPClient::needUpdate() { + if (this->_sendTime == 0) return true; // Was never updated before. + if (millis() - this->_sendTime >= this->_updateInterval) return true; // Previous update was more than configured interval ago. - return true; // return true after successful update + return false; } -bool NTPClient::update() { - if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval - || this->_lastUpdate == 0) { // Update if there was no update yet. - if (!this->_udpSetup || this->_port != NTP_DEFAULT_LOCAL_PORT) this->begin(this->_port); // setup the UDP client if needed - return this->forceUpdate(); +bool NTPClient::update(unsigned long timeout) { + if (this->needUpdate()) { + return this->forceUpdate(timeout); } return false; // return false if update does not occur } +int NTPClient::asyncUpdate() { + if (this->needUpdate()) { + this->sendNTPPacket(); + + if (_needUpdate) return -1; // return -1 if timeout + _needUpdate = true; + return 2; // return 2 if update is in progress + } + + if (_needUpdate) { + if (!this->receiveNTPPacket()) return 2; + _needUpdate = false; + return 0; + } + + return 1; // return 1 if update does not occur +} + bool NTPClient::isTimeSet() const { return (this->_lastUpdate != 0); // returns true if the time has been set, else false } +long long NTPClient::getEpochTimeMillis() const { + return (this->_timeOffset + // User offset + this->_currentEpoch) * 1000LL + // Epoch returned by the NTP server + (millis() - this->_lastUpdate); // Time since last update +} + unsigned long NTPClient::getEpochTime() const { return this->_timeOffset + // User offset - this->_currentEpoc + // Epoch returned by the NTP server + this->_currentEpoch + // Epoch returned by the NTP server ((millis() - this->_lastUpdate) / 1000); // Time since last update } @@ -182,6 +191,13 @@ void NTPClient::setPoolServerName(const char* poolServerName) { } void NTPClient::sendNTPPacket() { + if (this->_udpSetup) { + // flush any existing packets + while(this->_udp->parsePacket() != 0) + this->_udp->flush(); + } + + if (!this->_udpSetup) this->begin(this->_port); // setup the UDP client if needed // set all bytes in the buffer to 0 memset(this->_packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request @@ -204,6 +220,45 @@ void NTPClient::sendNTPPacket() { } this->_udp->write(this->_packetBuffer, NTP_PACKET_SIZE); this->_udp->endPacket(); + this->_sendTime = millis(); + + #ifdef DEBUG_NTPClient + Serial.println("[NTPClient] Sent UDP packet"); + #endif +} + +bool NTPClient::receiveNTPPacket(unsigned long timeout) { + int packetSize = this->_udp->parsePacket(); + + if (packetSize < NTP_PACKET_SIZE) { + if (timeout == 0) return false; + + unsigned long start = millis(); + while (millis() - start < timeout) { + delay(1); + packetSize = this->_udp->parsePacket(); + if (packetSize >= NTP_PACKET_SIZE) break; + } + } + + this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE); + + unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]); + unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]); + // combine the four bytes (two words) into a long integer + // this is NTP time (seconds since Jan 1 1900): + unsigned long secsSince1900 = highWord << 16 | lowWord; + + this->_currentEpoch = secsSince1900 - SEVENTY_YEARS; + int latency = (millis() - this->_sendTime); + this->_lastUpdate = millis() - (latency / 2); // Account for latency in reading the time + + #ifdef DEBUG_NTPClient + Serial.print("[NTPClient] Received UDP packet latency: "); + Serial.println(latency); + #endif + + return true; } void NTPClient::setRandomPort(unsigned int minValue, unsigned int maxValue) { diff --git a/NTPClient.h b/NTPClient.h index a31d32f..606d552 100755 --- a/NTPClient.h +++ b/NTPClient.h @@ -4,7 +4,7 @@ #include -#define SEVENZYYEARS 2208988800UL +#define SEVENTY_YEARS 2208988800UL #define NTP_PACKET_SIZE 48 #define NTP_DEFAULT_LOCAL_PORT 1337 @@ -20,12 +20,15 @@ class NTPClient { unsigned long _updateInterval = 60000; // In ms - unsigned long _currentEpoc = 0; // In s + bool _needUpdate = true; + unsigned long _sendTime = 0; + unsigned long _currentEpoch = 0; // In s unsigned long _lastUpdate = 0; // In ms byte _packetBuffer[NTP_PACKET_SIZE]; void sendNTPPacket(); + bool receiveNTPPacket(unsigned long timeout = 0); public: NTPClient(UDP& udp); @@ -59,20 +62,35 @@ class NTPClient { */ void begin(unsigned int port); + /** + * Returns true if an first time or if the last update was more than configured interval ago. + * + * @return true on need, false on "not now" + */ + bool needUpdate(); + /** * This should be called in the main loop of your application. By default an update from the NTP Server is only * made every 60 seconds. This can be configured in the NTPClient constructor. * * @return true on success, false on failure */ - bool update(); + bool update(unsigned long timeout = 1000); /** * This will force the update from the NTP Server. * * @return true on success, false on failure */ - bool forceUpdate(); + bool forceUpdate(unsigned long timeout = 1000); + + /** + * Alternatevly this can be called instead of update() in the main loop of your application. + * AsyncUpdate from the NTP Server is made every _updateInterval milliseconds. + * + * @return 0 on success, -1 on failure, 1 if no update is needed, 2 if update is in progress + */ + int asyncUpdate(); /** * This allows to check if the NTPClient successfully received a NTP packet and set the time. @@ -107,6 +125,12 @@ class NTPClient { */ unsigned long getEpochTime() const; + + /** + * @return time in milliseconds since Jan. 1, 1970 (UTC+0) + */ + long long getEpochTimeMillis() const; + /** * Stops the underlying UDP client */ diff --git a/examples/AsyncUpdate/AsyncUpdate.ino b/examples/AsyncUpdate/AsyncUpdate.ino new file mode 100644 index 0000000..ce9fd86 --- /dev/null +++ b/examples/AsyncUpdate/AsyncUpdate.ino @@ -0,0 +1,50 @@ +#include +// change next line to use with another board/shield +#include +//#include // for WiFi shield +//#include // for WiFi 101 shield or MKR1000 +#include + +#define SSID "" +#define PASSWORD "" + +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP, "0.asia.pool.ntp.org", 0, 1500); + +void setup(){ + Serial.begin(115200); + + WiFi.begin(SSID, PASSWORD); + + while ( WiFi.status() != WL_CONNECTED ) { + delay ( 500 ); + Serial.print ( "." ); + } + + timeClient.begin(); +} + +void loop() { + long int loopMillis = millis(); + static int prevRes = 0; + // with update() method loop will be blocked until time is updated + // timeClient.update(); + int res = timeClient.asyncUpdate(); + + if (res != prevRes) { + Serial.printf("Result: %d\n", res); + prevRes = res; + } + + static long int lm = 0; + + if (millis() - lm > 1000) { + lm = millis(); + Serial.printf("%s.%d\n", timeClient.getFormattedTime().c_str(), int(timeClient.getEpochTimeMillis()%1000)); + } + + long int dd = millis() - loopMillis; + if (dd > 10) { + Serial.printf("Loop took %d ms...\n", dd); + } +} \ No newline at end of file diff --git a/library.properties b/library.properties index abdd80d..5ff7998 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=NTPClient -version=3.2.1 +version=3.3.1 author=Fabrice Weinberg maintainer=Fabrice Weinberg sentence=An NTPClient to connect to a time server