Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable fractional second timestamps (re-integration of #102) #146

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 103 additions & 20 deletions NTPClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,40 +90,108 @@ bool NTPClient::forceUpdate() {
while(this->_udp->parsePacket() != 0)
this->_udp->flush();

uint32_t tik,tok; //tik,tok to record wait time
this->sendNTPPacket();
tik=millis();
#ifdef DEBUG_NTPClient
Serial.println("Sent ntp packet");
#endif

// Wait till data is there or timeout...
byte timeout = 0;
int cb = 0;
uint16_t cb = 0;
do {
delay ( 10 );
delay (1); //poll more frequently to get high accuarcy
cb = this->_udp->parsePacket();
if (timeout > 100) return false; // timeout after 1000 ms
timeout++;
if ((millis()-tik)>this->_ntp_timeout){
this->_last_fail=millis();
return false;
}
} while (cb == 0);

this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time
tok=millis();
#ifdef DEBUG_NTPClient
Serial.println("got ntp packet.");
Serial.print("tik, tok, (tok-tik)/2: ");
Serial.print(tik);Serial.print(", ");
Serial.print(tok);Serial.print(", ");
Serial.println((tok-tik)/2.0);
#endif

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]);
uint32_t high_word,low_word;
uint32_t receive_int,transmit_int; //integer part
// 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;
high_word=word(this->_packetBuffer[32],this->_packetBuffer[33]);
low_word=word(this->_packetBuffer[34],this->_packetBuffer[35]);
receive_int=(high_word<<16) | low_word;
high_word=word(this->_packetBuffer[40],this->_packetBuffer[41]);
low_word=word(this->_packetBuffer[42],this->_packetBuffer[43]);
transmit_int=(high_word<<16) | low_word;
#ifdef DEBUG_NTPClient
Serial.print("receive_int, transmit_int: ");
Serial.print(receive_int);Serial.print(", ");
Serial.println(transmit_int);
#endif

this->_currentEpoc = secsSince1900 - SEVENZYYEARS;
float receive_dec=0,transmit_dec=0; //decimal part
high_word=word(this->_packetBuffer[36],this->_packetBuffer[37]);
receive_dec=high_word/65536.0;
#ifdef DEBUG_NTPClient
Serial.print("receive_dec, transmit_dec: ");
Serial.print(high_word,HEX);Serial.print(", ");
Serial.print(receive_dec,6);Serial.print(", ");
#endif
high_word=word(this->_packetBuffer[44],this->_packetBuffer[45]);
transmit_dec=high_word/65536.0;
#ifdef DEBUG_NTPClient
Serial.print(high_word,HEX);Serial.print(", ");
Serial.println(transmit_dec,6);
#endif

float ping_delay;
ping_delay=(tok-tik)/1000.0-(transmit_int-receive_int)-(transmit_dec-receive_dec);
ping_delay/=2.0;
if(ping_delay<=0){
Serial.println("ERROR: ping_delay < 0.0!");
}

this->_lastUpdate=tok;
this->_currentEpoc=transmit_int - SEVENZYYEARS ;
this->_current_epoc_dec=ping_delay+transmit_dec;
if(this->_current_epoc_dec>1){
this->_currentEpoc+=(int)this->_current_epoc_dec;
this->_current_epoc_dec-=(int)this->_current_epoc_dec;
}

#ifdef DEBUG_NTPClient
Serial.print("current Epoc: ");
Serial.print(this->_currentEpoc);Serial.print(" ");
Serial.println(this->_current_epoc_dec,6);
#endif

return true; // return true after successful update
}

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();
int8_t NTPClient::update() {
uint32_t now=millis();
if(now>=this->_lastUpdate){ //if not overflow
Copy link

@jamesmyatt jamesmyatt Aug 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure you need this logic. millis() - this->_lastUpdate should always give a positive integer by the power of integer arithmetic.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

millis() rollover (depending on mcu) every ~49 days

if(now-this->_lastUpdate>=this->_updateInterval){
if(now-this->_last_fail >= 1500){
return this->forceUpdate();
}else{
return 3; //return 3 if last failed was just happen
}
}
}else{ //if overflowed
if(now+0xffffffff-this->_lastUpdate >= this->_updateInterval){
if(now+0xffffffff-this->_last_fail >= 1500){
return this->forceUpdate();
}else{
return 3;
}
}
}
return false; // return false if update does not occur
return 2; // return 2 if update does not occur
}

bool NTPClient::isTimeSet() const {
Expand All @@ -133,7 +201,13 @@ bool NTPClient::isTimeSet() const {
unsigned long NTPClient::getEpochTime() const {
return this->_timeOffset + // User offset
this->_currentEpoc + // Epoch returned by the NTP server
((millis() - this->_lastUpdate) / 1000); // Time since last update
((millis() - this->_lastUpdate + this->_current_epoc_dec*1000)/1000.0); // Time since last update
}

float NTPClient::get_millis() const{
float ms = millis() - this->_lastUpdate + this->_current_epoc_dec*1000.0;
ms-=(int)(ms/1000)*1000;
return ms;
}

int NTPClient::getDay() const {
Expand Down Expand Up @@ -178,7 +252,16 @@ void NTPClient::setUpdateInterval(unsigned long updateInterval) {
}

void NTPClient::setPoolServerName(const char* poolServerName) {
this->_poolServerName = poolServerName;
this->_poolServerName = poolServerName;
}

void NTPClient::setPoolServerIP(IPAddress server_ip){
this->_poolServerIP = server_ip;
this->_poolServerName = NULL;
}

void NTPClient::setTimeout(uint16_t t_ms){
this->_ntp_timeout=t_ms;
}

void NTPClient::sendNTPPacket() {
Expand Down
33 changes: 30 additions & 3 deletions NTPClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define SEVENZYYEARS 2208988800UL
#define NTP_PACKET_SIZE 48
#define NTP_DEFAULT_LOCAL_PORT 1337
#define DEBUG_NTPClient

class NTPClient {
private:
Expand All @@ -21,7 +22,11 @@ class NTPClient {
unsigned long _updateInterval = 60000; // In ms

unsigned long _currentEpoc = 0; // In s
unsigned long _lastUpdate = 0; // In ms
float _current_epoc_dec = 0; // In s, decimal part of current epoc
uint32_t _lastUpdate = 0xff000000;// In ms

uint16_t _ntp_timeout = 1000; // In ms
uint32_t _last_fail = 0xffff0000; // In ms

byte _packetBuffer[NTP_PACKET_SIZE];

Expand Down Expand Up @@ -49,6 +54,20 @@ class NTPClient {
*/
void setRandomPort(unsigned int minValue = 49152, unsigned int maxValue = 65535);

/**
* clear time server and set ip
*
* @param ServerIP
*/
void setPoolServerIP(IPAddress server_ip);

/**
* Set ntp timeout (recommendation < 1000ms)
*
* @param t_ms
*/
void setTimeout(uint16_t t_ms);

/**
* Starts the underlying UDP client with the default local port
*/
Expand All @@ -63,9 +82,12 @@ class NTPClient {
* 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
* @return 1(true) on updated and success
* 0(false) on updated and failure
* 2 on not time to update
* 3 on it's time to update but last failed was just happen, so it decided to wait more
*/
bool update();
int8_t update();

/**
* This will force the update from the NTP Server.
Expand Down Expand Up @@ -107,6 +129,11 @@ class NTPClient {
*/
unsigned long getEpochTime() const;

/**
* @return ms of this second, in ms
*/
float get_millis() const;

/**
* Stops the underlying UDP client
*/
Expand Down
75 changes: 75 additions & 0 deletions examples/HiRes/HiRes.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h>
//#include <WiFi101.h>
#include <WiFiUdp.h>

const char *ssid = "<SSID>";
const char *password = "<PASSWORD>";

WiFiUDP ntpUDP;

// You can specify the time server pool and the offset (in seconds, can be
// changed later with setTimeOffset() ). Additionally you can specify the
// update interval (in milliseconds, can be changed using setUpdateInterval() ).
NTPClient my_time_client(ntpUDP, "europe.pool.ntp.org", 28800, 20000);

inline void sync_time(){
my_time_client.begin();
//smaller timeout will give you more accuracy
//but also larger possibility to fail
my_time_client.setTimeout(800);
Serial.println("syncing...");

while(my_time_client.update()!=1){
delay(2000);
my_time_client.forceUpdate();
}

Serial.print("success: ");
Serial.println(my_time_client.getFormattedTime());
}

void setup(){
Serial.begin(115200);

WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}

sync_time();
}

String s_last_time="s_last_time";

void loop(){
String s_time=my_time_client.getFormattedTime();
if(s_time!=s_last_time){
Serial.print("a second passed ");
Serial.print(s_time);Serial.print(" ");
Serial.print(my_time_client.get_millis(),3);
Serial.println("ms");
s_last_time=s_time;

//please do not update too frequently
int8_t re=my_time_client.update();
if(re==0){
Serial.println("0: sync but failed");
delay(500);
}else if(re==1){
Serial.println("1: sync and suc");
}else if(re==2){
;//Serial.println("2: not time to sync");
}else if(re==3){
Serial.println("3: last failed was just happen");
}else{
Serial.print("return value error: ");
Serial.println(re);
}
}

delay(1);
}