diff --git a/NTPClient.cpp b/NTPClient.cpp index b435855..4883e70 100755 --- a/NTPClient.cpp +++ b/NTPClient.cpp @@ -102,8 +102,6 @@ bool NTPClient::forceUpdate() { timeout++; } while (cb == 0); - 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]); @@ -111,10 +109,20 @@ bool NTPClient::forceUpdate() { // 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; - - return true; // return true after successful update + /// return early if received timestamp is invalid + if (secsSince1900 == 0) return false; + + unsigned long unixEpoch = secsSince1900 - SEVENZYYEARS; + /// compute the fractional time. we will use it to sync to the nearest millis() + highWord = word(this->_packetBuffer[44], this->_packetBuffer[45]); + lowWord = word(this->_packetBuffer[46], this->_packetBuffer[47]); + unsigned long fractionPart = highWord << 16 | lowWord; + unsigned long milliseconds = ((unsigned long long) fractionPart * 1000) >> 32; + + this->_lastUpdate = (unsigned long) (millis() - milliseconds); + this->_currentEpoc = unixEpoch; + + return true; } bool NTPClient::update() { @@ -131,9 +139,24 @@ bool NTPClient::isTimeSet() const { } unsigned long NTPClient::getEpochTime() const { + unsigned long t = millis(); + /// if millis() has rollover, get difference between lastUpdate and maximum value of uint32_t. + /// then add it to current value of millis() and add 1 for when millis() was 0. + if (t < this->_lastUpdate) { + t = (((unsigned long) -1) - this->_lastUpdate) + t + 1; + } else { + t = t - this->_lastUpdate; + } return this->_timeOffset + // User offset - this->_currentEpoc + // Epoch returned by the NTP server - ((millis() - this->_lastUpdate) / 1000); // Time since last update + this->_currentEpoc + // Epoc returned by the NTP server + (t / 1000); // Time since last update +} + +unsigned long NTPClient::setEpochTime(unsigned long localTime) { + this->_currentEpoc = localTime - this->_timeOffset; + this->_lastUpdate = millis(); + /// Return Unix epoch + return (this->_currentEpoc); } int NTPClient::getDay() const { diff --git a/NTPClient.h b/NTPClient.h index a31d32f..3e657ad 100755 --- a/NTPClient.h +++ b/NTPClient.h @@ -18,7 +18,7 @@ class NTPClient { unsigned int _port = NTP_DEFAULT_LOCAL_PORT; long _timeOffset = 0; - unsigned long _updateInterval = 60000; // In ms + unsigned long _updateInterval = 3600000; // In ms unsigned long _currentEpoc = 0; // In s unsigned long _lastUpdate = 0; // In ms @@ -107,6 +107,14 @@ class NTPClient { */ unsigned long getEpochTime() const; + /** + * Set internal _currentEpoch from the localTime. Useful when a hardware + * RTC is used to set the initial unix time of NTPClient. + * @param localTime, current local time in seconds since Jan. 1, 1970 + * @return unix epoch in seconds (GMT) since Jan. 1, 1970 + */ + unsigned long setEpochTime(unsigned long localTime); + /** * Stops the underlying UDP client */ diff --git a/examples/Advanced_with_RTC/Advanced_with_RTC.ino b/examples/Advanced_with_RTC/Advanced_with_RTC.ino new file mode 100644 index 0000000..4837043 --- /dev/null +++ b/examples/Advanced_with_RTC/Advanced_with_RTC.ino @@ -0,0 +1,91 @@ +/* Advanced NTPClient With RTC example + * Copyright (c) 2021 by Anthony F. Dela Paz + * + * Featuring: + * + an RTC (RTCLib can use hardware RTC by simply changing 'RTC_Millis' with appropriate declaration). + * + DateTime facility of the RTCLib to facilitate epochtime conversion + * + separately setting the ntp server, timezone offset, and update interval + * + syncing the internal software RTC of the NTPClient with the RTC time + * + * This example code demonstrates NTPClient's new capability of syncing with NTP time server + * down to millisecond accuracy. It's best to upload this code to at least two ESP-12E modules + * to see how both their blue LED blinks simultaneously upon synchronization with NTP server. + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "RTClib.h" + +const char *ssid = ""; // change the SSID and PASSWORD +const char *password = ""; + +WiFiUDP ntpUDP; +NTPClient timeClient(ntpUDP); + +RTC_Millis rtc; // this should be a hardware RTC but that won't work as a quick example + +const byte ledBlue = 2; // Blue LED on the ESP-12E module +unsigned long oldTick, blinkMillis; + +void setup() { + + pinMode(ledBlue, OUTPUT); + + Serial.begin(115200); + + WiFi.begin(ssid, password); + + while ( WiFi.status() != WL_CONNECTED ) { + delay ( 500 ); + Serial.print ( "." ); + } + + // following line sets the RTC to the date & time this sketch was compiled + rtc.begin(DateTime(F(__DATE__), F(__TIME__))); + // This line sets the RTC with an explicit local date & time, for example to set + // June 9, 2021 at 12:52:36 PM, you would call: + // rtc.adjust(DateTime(2021, 6, 9, 12, 52, 30)); + + timeClient.begin(); + timeClient.setTimeOffset((-4) * 3600); // in seconds, Time zone in New York, NY, USA (GMT-4) + timeClient.setPoolServerName("europe.pool.ntp.org"); + timeClient.setUpdateInterval(3600); // seconds + timeClient.setEpochTime(rtc.now().unixtime()); // set the timeClient's internal software rtc time. +} + +void loop() { + timeClient.update(); + + // Synchronize the blinking LED with transition of seconds. + if (timeClient.getEpochTime() != oldTick) { + oldTick = timeClient.getEpochTime(); + blinkMillis = millis(); + Serial.println(timeClient.getFormattedTime()); + } + + // blink the LED once per second with 500ms ON-time + if ((millis() - blinkMillis) % 1000 > 500) { + digitalWrite(ledBlue, HIGH); + } else { + digitalWrite(ledBlue, LOW); + } +} diff --git a/keywords.txt b/keywords.txt index edce989..27152bb 100644 --- a/keywords.txt +++ b/keywords.txt @@ -22,3 +22,4 @@ getEpochTime KEYWORD2 setTimeOffset KEYWORD2 setUpdateInterval KEYWORD2 setPoolServerName KEYWORD2 +setEpochTime KEYWORD2