I want to include a function on my ESP8266 project, which can return the current UTC time in time_t format.
My idea is to call a nist server on startup after the ESP8266 has connected to a WiFi router and retrieve the current UTC time. Then it will store this as well as the return of millis().
Then my currenttime() function will return a value that is the time retrieved from the NTP server plus the difference between millis() and the stored value of millis() at the time of UTC retrieval from the NTP server.
I have googled a lot for this but all I get are complete sketches (ino files) which do a lot of printing to serial and repeatedly call the NTP server inside the loop() function. That is not what I want, instead I need something like the following functions:
bool GetUTCFromNTP(); //Retrieve the UTC time from an NTP server, save value and millis()
long GetCurrentTimeUTC(); //Return current UTC based on data saved during last use of GetUTCFromNTP()
All of the needed code should reside in a regular cpp file ready to be called from any ino sketch. I want to be able to include this file (and the h file) into any project where I need the time.
In order to keep the time updated I plan on using a check in the loop() function where the GetUTCFromNTP() function will be called once per 24 hours.
What purpose is the TelnetStream object? It looks like the GIT example is in an example suite for TelnetStream...
Is it needed for NTP to function?
Apart from that, which confuses me, the library NtpClientLib is really what I was trying to locate, one file that handles everything (except the TelnetStrea...).
Can the NTP object be started and forgotten about afterwards; i.e. it will update its time every 24 hours or so?
Don't need to check more often than that. And I don't need to send the result anywhere either, just time for internal use.
I just happened to have this to hand, but I haven't used it for quite a while. It did work...
#include <TimeLib.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <SD.h>
#include <SPI.h>
#include <PCD8544.h>
static PCD8544 lcd;
const char ssid[] = "Snookypoo"; // your network SSID (name)
const char pass[] = "++++++++++"; // your network password
// NTP Servers:
static const char ntpServerName[] = "us.pool.ntp.org";
//static const char ntpServerName[] = "time.nist.gov";
const int timeZone = 10; // AEST
WiFiUDP Udp;
unsigned int localPort = 8888; // local port to listen for UDP packets
time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);
time_t prevDisplay = 0; // when the digital clock was displayed
char filename[] = "00000000.CSV";
File myFile;
char dumpName[] = "00000000.CSV";
File dumpFile;
char dailydate[] = "00000000.CSV";
void setup()
{
Serial.begin(115200);
delay(250);
Serial.println("TimeNTP Example");
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
lcd.begin(84, 48);
lcd.setCursor(0,0);
lcd.print("Today it is");
}
Serial.print("IP number assigned by DHCP is ");
Serial.println(WiFi.localIP());
Serial.println("Starting UDP");
Udp.begin(localPort);
Serial.print("Local port: ");
Serial.println(Udp.localPort());
Serial.println("waiting for sync");
setSyncProvider(getNtpTime);
setSyncInterval(300);
/*
pinMode(10, OUTPUT);
Serial.println("Init SD CARD");
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect))
{
lcd.println("Card failed");
return;
}
lcd.println("CARD OK");
*/
}
void loop()
{
if (timeStatus() != timeNotSet) {
if (now() != prevDisplay) { //update the display only if time has changed
prevDisplay = now();
digitalClockDisplay();
lcddisplay();
getFileName();
Serial.println(filename);
}
}
}
void digitalClockDisplay()
{
// digital clock display of the time
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(day());
Serial.print(".");
Serial.print(month());
Serial.print(".");
Serial.print(year());
Serial.println();
}
void printDigits(int digits)
{
// utility for digital clock display: prints preceding colon and leading 0
Serial.print(":");
if (digits < 10)
Serial.print('0');
Serial.print(digits);
}
void lcddisplay{
lcd.setCursor(0,4);
lcd.print(day());
lcd.print("/");
lcd.print(month());
lcd.print("/");
lcd.print(year());
lcd.setCursor(0,5);
if( second==0)
{
lcd.print(" ");
lcd.setCursor(0,5);
}
lcd.print(hour);
lcd.print(":");
lcd.print(minute);
lcd.print(":");
lcd.print(second);
}
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime()
{
IPAddress ntpServerIP; // NTP server's ip address
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println("Transmit NTP Request");
// get a random server from the pool
WiFi.hostByName(ntpServerName, ntpServerIP);
Serial.print(ntpServerName);
Serial.print(": ");
Serial.println(ntpServerIP);
sendNTPpacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
Serial.println("No NTP Response :-(");
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
void getFileName(){
sprintf(filename, "%02d%02d%02d.csv", year(), month(), day());
}
BosseB:
What purpose is the TelnetStream object? It looks like the GIT example is in an example suite for TelnetStream...
Is it needed for NTP to function?
Apart from that, which confuses me, the library NtpClientLib is really what I was trying to locate, one file that handles everything (except the TelnetStrea...).
Can the NTP object be started and forgotten about afterwards; i.e. it will update its time every 24 hours or so?
Don't need to check more often than that. And I don't need to send the result anywhere either, just time for internal use.
TelnetStream is a library for debuging over-the-air, it is not important here.
OK, thanks!
I have now tried to use the NtpClientLib library according to the example.
But now I am stuck at this in the ino file:
void processSyncEvent (NTPSyncEvent_t ntpEvent) {
if (ntpEvent) {
SerialDebug.print ("Time Sync error: ");
if (ntpEvent == noResponse)
SerialDebug.println ("NTP server not reachable");
else if (ntpEvent == invalidAddress)
SerialDebug.println ("Invalid NTP server address");
} else {
SerialDebug.print ("Got NTP time: ");
SerialDebug.println (NTP.getTimeDateString (NTP.getLastNTPSync ()));
}
}
I get errors as follows: Type '' could not be resolved for these identifiers:
NTPSyncEvent_t
noResponse
invalidAddress
NTP
getTimeDateString
getLastNTPSync
But I have set the #include as follows:
/* The NTP time retrieval needs this: */
#include <TimeLib.h>
#include "NtpClientLib.h"
I copied in the two NtpClientLib files downloaded from GIT into the main project dir, hence the "" rather than <>.
Why can't it resolve these identifiers?
Will it matter if I don't include the function void processSyncEvent (NTPSyncEvent_t ntpEvent) ?
Note that in Sloeber IDE if I right-click the NTPSyncEvent_t word and select "Open declaration" it jumps to the location inside the NtpClientLib.h file where it is declared. So Eclipse can find it in that case....
I did so already but it changed nothing. Same errors displayed and I have used F5 in the Project explorer to refresh stuff...
Then as a final test I used F5 in the code editor window and now the errors went away!
Strange, shouldn't Eclipse be able to do this by itself?
Anyway, errors gone so I will continue copying sample code....
What is the purpose of the 4th parameter in begin()?
What is the unit of the setInterval() parameter? milliseconds, seconds, minutes or what?
Is there no way to retrieve the current time from the NTP object following its first sync with an NTP server?
The function getTime() actually runs a connection against the server, when it could calculate current time internally.
Is there no way to have GetDateStr() return the string in ISO format?
Regarding the update interval I found this inside the NtpClientLib.h file:
#define DEFAULT_NTP_INTERVAL 1800 // Default sync interval 30 minutes
#define DEFAULT_NTP_SHORTINTERVAL 15 // Sync interval when sync has not been achieved. 15 seconds
...
/**
* Changes sync period.
* @param[in] New interval in seconds.
* @param[out] True if everything went ok.
*/
bool setInterval (int interval);
/**
* Changes sync period in sync'd and not sync'd status.
* @param[in] New interval while time is not first adjusted yet, in seconds.
* @param[in] New interval for normal operation, in seconds.
* @param[out] True if everything went ok.
*/
bool setInterval (int shortInterval, int longInterval);
/**
* Gets sync period.
* @param[out] Interval for normal operation, in seconds.
*/
This does not make sense to me because the example uses NTP.setInterval(63), which implies that the server comm interval is 63 seconds (why?). Such a strange value where one would think that a value close to 86400 (24 hours) would be more appropriate.
But another thing is that I tried to get the time as string in a function that is executed once every 2 minutes:
Accordig to the header file when calling getTimeDateString() without parameter it will be executed with the parameter now() which is handled by library Time.cpp:
time_t now() {
// calculate number of seconds passed since last call to now()
while (millis() - prevMillis >= 1000) {
// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
sysTime++;
prevMillis += 1000;
#ifdef TIME_DRIFT_INFO
sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift
#endif
}
if (nextSyncTime <= sysTime) {
if (getTimePtr != 0) {
time_t t = getTimePtr();
if (t != 0) {
setTime(t);
} else {
nextSyncTime = sysTime + syncInterval;
Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync;
}
}
}
return (time_t)sysTime;
}
I don't understand a lot of what is happening here but I see no NTP call in any case...
What actually happens when I call the function with no parameter is that the NTP server gets called to supply the current time!!!
So I get an NTP call every time I need to use the current time in any way...
Is there a way to fix this? It seems unnecessary to call NTP server with only minutes intervals...
PS: I had to copy the library files into my project so I could change the date format into ISO instead of the strange default format used. So now I can do also other changes if needed/suggested. DS
My system seems to be running OK now with the time retrieved at boot.
I tried to capture the traffic from the ESP8266 using Wireshark to verify that it is not doing additional updates except for the ones I have set in these commands:
NTP.begin ("pool.ntp.org", 1, true, 0);
NTP.setInterval (60, 86400); //Initial NTP call repeat 1 min, then 24h between calls
But I can see no traffic at all for the ESP8266 IP address 192.168.119.133.
My Win7 laptop is connected by wire to the same router as the ESP is connected to via WiFi, so they are on the same network (the laptop has last byte=241 and the ESP is 133.
Apparently one cannot catch packets with Wireshark in this situation, only when the ESP and PC are communication with each other...
Is there some way to verify that the ESP is only talking to NTP on startup and after that every 24 hours?