From 0ccc182d7f55d95c8808efc26a8b8d68a8c27995 Mon Sep 17 00:00:00 2001 From: HoustonAsh Date: Tue, 9 Apr 2024 19:22:04 +0800 Subject: [PATCH 1/5] feat: async update --- NTPClient.cpp | 37 +++++++++++++++++++++++++++++++++++++ NTPClient.h | 16 ++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/NTPClient.cpp b/NTPClient.cpp index b435855..760068c 100755 --- a/NTPClient.cpp +++ b/NTPClient.cpp @@ -126,10 +126,47 @@ bool NTPClient::update() { return false; // return false if update does not occur } +int NTPClient::asyncUpdate() { + if ((millis() - this->_sendTime >= this->_updateInterval) // Update after _updateInterval + || this->_sendTime == 0) { // Update if there was no update yet. + _sendTime = millis(); + if (!this->_udpSetup || this->_port != NTP_DEFAULT_LOCAL_PORT) this->begin(this->_port); // setup the UDP client if needed + this->sendNTPPacket(); + + int code = 2; + if (_needUpdate) code = -1; // timeout + _needUpdate = true; + return code; + } + + if (_needUpdate) { + int cb = this->_udp->parsePacket(); + if (cb == 0) return 2; + 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->_currentEpoc = secsSince1900 - SEVENZYYEARS; + this->_lastUpdate = millis(); + _needUpdate = false; + return 0; + } + + return 1; // return false 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->_currentEpoc * 1000LL + millis() - this->_lastUpdate; +} + unsigned long NTPClient::getEpochTime() const { return this->_timeOffset + // User offset this->_currentEpoc + // Epoch returned by the NTP server diff --git a/NTPClient.h b/NTPClient.h index a31d32f..e89dfe3 100755 --- a/NTPClient.h +++ b/NTPClient.h @@ -20,6 +20,8 @@ class NTPClient { unsigned long _updateInterval = 60000; // In ms + bool _needUpdate = true; + unsigned long _sendTime = 0; unsigned long _currentEpoc = 0; // In s unsigned long _lastUpdate = 0; // In ms @@ -74,6 +76,14 @@ class NTPClient { */ bool forceUpdate(); + /** + * 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 +117,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 */ From 36d6a23b56bcb3ee59013939c9e8d4402e2b285b Mon Sep 17 00:00:00 2001 From: HoustonAsh Date: Tue, 9 Apr 2024 19:27:06 +0800 Subject: [PATCH 2/5] feat: new example for async update --- examples/AsyncUpdate/AsyncUpdate.ino | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 examples/AsyncUpdate/AsyncUpdate.ino diff --git a/examples/AsyncUpdate/AsyncUpdate.ino b/examples/AsyncUpdate/AsyncUpdate.ino new file mode 100644 index 0000000..95bd1ec --- /dev/null +++ b/examples/AsyncUpdate/AsyncUpdate.ino @@ -0,0 +1,48 @@ +#include +#include + +#include "NTPClient.h" + +#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 From 671e503c969167a3b33fe0347ce5f9866fdaa0ac Mon Sep 17 00:00:00 2001 From: HoustonAsh Date: Tue, 9 Apr 2024 19:41:24 +0800 Subject: [PATCH 3/5] fix: asyncUpdate example --- examples/AsyncUpdate/AsyncUpdate.ino | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/AsyncUpdate/AsyncUpdate.ino b/examples/AsyncUpdate/AsyncUpdate.ino index 95bd1ec..ce9fd86 100644 --- a/examples/AsyncUpdate/AsyncUpdate.ino +++ b/examples/AsyncUpdate/AsyncUpdate.ino @@ -1,8 +1,10 @@ -#include +#include +// change next line to use with another board/shield +#include +//#include // for WiFi shield +//#include // for WiFi 101 shield or MKR1000 #include -#include "NTPClient.h" - #define SSID "" #define PASSWORD "" From e370ab622d55d1f97aabccbeb66c7b3f51d69c75 Mon Sep 17 00:00:00 2001 From: HoustonAsh Date: Tue, 9 Apr 2024 19:48:42 +0800 Subject: [PATCH 4/5] cfg: pkg version --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b31b66f7c6656f7d66389e8d075a32069448966a Mon Sep 17 00:00:00 2001 From: Israel Lins Albuquerque Date: Fri, 4 Oct 2024 14:26:57 +0200 Subject: [PATCH 5/5] centralized repeated code, improvements on asyncUpdate --- NTPClient.cpp | 126 ++++++++++++++++++++++++++++---------------------- NTPClient.h | 16 +++++-- 2 files changed, 84 insertions(+), 58 deletions(-) diff --git a/NTPClient.cpp b/NTPClient.cpp index 760068c..095c66f 100755 --- a/NTPClient.cpp +++ b/NTPClient.cpp @@ -81,82 +81,52 @@ 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); + if (this->receiveNTPPacket(timeout)) return true; - this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time - - 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 ((millis() - this->_sendTime >= this->_updateInterval) // Update after _updateInterval - || this->_sendTime == 0) { // Update if there was no update yet. - _sendTime = millis(); - if (!this->_udpSetup || this->_port != NTP_DEFAULT_LOCAL_PORT) this->begin(this->_port); // setup the UDP client if needed + if (this->needUpdate()) { this->sendNTPPacket(); - int code = 2; - if (_needUpdate) code = -1; // timeout + if (_needUpdate) return -1; // return -1 if timeout _needUpdate = true; - return code; + return 2; // return 2 if update is in progress } if (_needUpdate) { - int cb = this->_udp->parsePacket(); - if (cb == 0) return 2; - 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->_currentEpoc = secsSince1900 - SEVENZYYEARS; - this->_lastUpdate = millis(); + if (!this->receiveNTPPacket()) return 2; _needUpdate = false; return 0; } - return 1; // return false if update does not occur + return 1; // return 1 if update does not occur } bool NTPClient::isTimeSet() const { @@ -164,12 +134,14 @@ bool NTPClient::isTimeSet() const { } long long NTPClient::getEpochTimeMillis() const { - return this->_currentEpoc * 1000LL + millis() - this->_lastUpdate; + 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 } @@ -219,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 @@ -241,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 e89dfe3..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 @@ -22,12 +22,13 @@ class NTPClient { bool _needUpdate = true; unsigned long _sendTime = 0; - unsigned long _currentEpoc = 0; // In s + 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); @@ -61,20 +62,27 @@ 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.