OpenDagKlok/NTP_Time.h
2024-03-07 07:22:50 +01:00

365 lines
12 KiB
C

//====================================================================================
// Libraries
//====================================================================================
// Time library:
// https://github.com/PaulStoffregen/Time
#include <Time.h>
// Time zone correction library:
// https://github.com/JChristensen/Timezone
#include <Timezone.h>
// Choose library to load
#ifdef ARDUINO_ARCH_ESP8266
// ESP8266
#include <ESP8266WiFi.h>
#elif (defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)) && !defined(ARDUINO_RASPBERRY_PI_PICO_W)
// RP2040 Nano Connect
#include <WiFiNINA.h>
#else
// ESP32
#include <WiFi.h>
#endif
#include <WiFiUdp.h>
// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;
//====================================================================================
// Settings
//====================================================================================
#define TIMEZONE UK // See below for other "Zone references", UK, usMT etc
#ifdef ESP32 // Temporary fix, ESP8266 fails to communicate with some servers...
// Try to use pool url instead so the server IP address is looked up from those available
// (use a pool server in your own country to improve response time and reliability)
//const char* ntpServerName = "time.nist.gov";
//const char* ntpServerName = "pool.ntp.org";
const char* ntpServerName = "time.google.com";
#else
// Try to use pool url instead so the server IP address is looked up from those available
// (use a pool server in your own country to improve response time and reliability)
// const char* ntpServerName = "time.nist.gov";
const char* ntpServerName = "pool.ntp.org";
//const char* ntpServerName = "time.google.com";
#endif
// Try not to use hard-coded IP addresses which might change, you can if you want though...
//IPAddress timeServerIP(129, 6, 15, 30); // time-c.nist.gov NTP server
//IPAddress timeServerIP(24, 56, 178, 140); // wwv.nist.gov NTP server
IPAddress timeServerIP; // Use server pool
// Example time zone and DST rules, see Timezone library documents to see how
// to add more time zones https://github.com/JChristensen/Timezone
// Zone reference "UK" United Kingdom (London, Belfast)
TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; //British Summer (Daylight saving) Time
TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; //Standard Time
Timezone UK(BST, GMT);
// Zone reference "euCET" Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; //Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; //Central European Standard Time
Timezone euCET(CEST, CET);
// Zone reference "ausET" Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; //UTC + 11 hours
TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; //UTC + 10 hours
Timezone ausET(aEDT, aEST);
// Zone reference "usET US Eastern Time Zone (New York, Detroit)
TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; //Eastern Daylight Time = UTC - 4 hours
TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300}; //Eastern Standard Time = UTC - 5 hours
Timezone usET(usEDT, usEST);
// Zone reference "usCT" US Central Time Zone (Chicago, Houston)
TimeChangeRule usCDT = {"CDT", Second, dowSunday, Mar, 2, -300};
TimeChangeRule usCST = {"CST", First, dowSunday, Nov, 2, -360};
Timezone usCT(usCDT, usCST);
// Zone reference "usMT" US Mountain Time Zone (Denver, Salt Lake City)
TimeChangeRule usMDT = {"MDT", Second, dowSunday, Mar, 2, -360};
TimeChangeRule usMST = {"MST", First, dowSunday, Nov, 2, -420};
Timezone usMT(usMDT, usMST);
// Zone reference "usAZ" Arizona is US Mountain Time Zone but does not use DST
Timezone usAZ(usMST, usMST);
// Zone reference "usPT" US Pacific Time Zone (Las Vegas, Los Angeles)
TimeChangeRule usPDT = {"PDT", Second, dowSunday, Mar, 2, -420};
TimeChangeRule usPST = {"PST", First, dowSunday, Nov, 2, -480};
Timezone usPT(usPDT, usPST);
//====================================================================================
// Variables
//====================================================================================
TimeChangeRule *tz1_Code; // Pointer to the time change rule, use to get the TZ abbrev, e.g. "GMT"
time_t utc = 0;
bool timeValid = false;
unsigned int localPort = 2390; // local port to listen for UDP packets
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE ]; //buffer to hold incoming and outgoing packets
uint8_t lastMinute = 0;
uint32_t nextSendTime = 0;
uint32_t newRecvTime = 0;
uint32_t lastRecvTime = 0;
uint32_t newTickTime = 0;
uint32_t lastTickTime = 0;
bool ntp_start = 1;
uint32_t no_packet_count = 0;
//====================================================================================
// Function prototype
//====================================================================================
void syncTime(void);
void displayTime(void);
void printTime(time_t zone, char *tzCode);
String timeString();
void decodeNTP(void);
void sendNTPpacket(IPAddress& address);
//====================================================================================
// Update Time
//====================================================================================
void syncTime(void)
{
if (ntp_start) { // Run once
// Call once for ESP32 and ESP8266
#if !defined(ARDUINO_ARCH_MBED)
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
#endif
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
#if defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)
if (WiFi.status() != WL_CONNECTED) WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
#endif
delay(500);
}
Serial.println();
udp.begin(localPort); ntp_start = 0;
}
// Don't send too often so we don't trigger Denial of Service
if (nextSendTime < millis()) {
// Wait 1 hour for next sync
nextSendTime = millis() + 60 * 60 * 1000;
// Get a random server from the pool
WiFi.hostByName(ntpServerName, timeServerIP);
sendNTPpacket(timeServerIP); // send an NTP packet to a time server
decodeNTP();
if ( no_packet_count > 0 ) {
// Wait 1 minute for next sync
nextSendTime = millis() + 60 * 1000;
}
else {
// Wait 1 hour for next sync
nextSendTime = millis() + 60 * 60 * 1000;
}
}
}
//====================================================================================
// Send an NTP request to the time server at the given address
//====================================================================================
void sendNTPpacket(IPAddress& address)
{
// Serial.println("sending NTP packet...");
// 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();
}
//====================================================================================
// Decode the NTP message and print status to serial port
//====================================================================================
void decodeNTP(void)
{
timeValid = false;
uint32_t waitTime = millis() + 500;
while (millis() < waitTime && !timeValid)
{
yield();
if (udp.parsePacket())
{
newRecvTime = millis();
// We've received a packet, read the data from it
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
Serial.print("\nNTP response time was : ");
Serial.print(500 - (waitTime - newRecvTime));
Serial.println(" ms");
Serial.print("Time since last sync is: ");
Serial.print((newRecvTime - lastRecvTime) / 1000.0);
Serial.println(" s");
lastRecvTime = newRecvTime;
// The timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, extract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], 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;
// Now convert NTP Unix time (Seconds since Jan 1 1900) into everyday time:
// UTC time starts on Jan 1 1970. In seconds the difference is 2208988800:
utc = secsSince1900 - 2208988800UL;
setTime(utc); // Set system clock to utc time (not time zone compensated)
timeValid = true;
// Print the hour, minute and second:
Serial.print("Received NTP UTC time : ");
uint8_t hh = hour(utc);
Serial.print(hh); // print the hour (86400 equals secs per day)
Serial.print(':');
uint8_t mm = minute(utc);
if (mm < 10 ) Serial.print('0');
Serial.print(mm); // print the minute (3600 equals secs per minute)
Serial.print(':');
uint8_t ss = second(utc);
if ( ss < 10 ) Serial.print('0');
Serial.println(ss); // print the second
time_secs = hh * 3600 + mm * 60 + ss; // Update the clock time
}
}
// Keep a count of missing or bad NTP replies
if ( timeValid ) {
no_packet_count = 0;
}
else
{
Serial.println("\nNo NTP reply, trying again in 1 minute...");
no_packet_count++;
}
if (no_packet_count >= 10) {
no_packet_count = 0; // Reset to one hour to try later
// TODO: Flag the lack of sync on the display
Serial.println("\nNo NTP packet in last 10 minutes");
}
}
//====================================================================================
// Time string: 00:00:00
//====================================================================================
String timeString(uint32_t t_secs)
{
String timeNow = "";
uint8_t h = t_secs / 3600;
if ( h < 10) timeNow += "0";
timeNow += h;
timeNow += ":";
uint8_t m = (t_secs - ( h * 3600 )) / 60;
if (m < 10) timeNow += "0";
timeNow += m;
timeNow += ":";
uint8_t s = t_secs - ( h * 3600 ) - ( m * 60 );
if (s < 10) timeNow += "0";
timeNow += s;
return timeNow;
}
//====================================================================================
// Debug use only
//====================================================================================
void printTime(time_t t, char *tzCode)
{
String dateString = dayStr(weekday(t));
dateString += " ";
dateString += day(t);
if (day(t) == 1 || day(t) == 21 || day(t) == 31) dateString += "st";
else if (day(t) == 2 || day(t) == 22) dateString += "nd";
else if (day(t) == 3 || day(t) == 23) dateString += "rd";
else dateString += "th";
dateString += " ";
dateString += monthStr(month(t));
dateString += " ";
dateString += year(t);
// Print time to serial port
Serial.print(hour(t));
Serial.print(":");
Serial.print(minute(t));
Serial.print(":");
Serial.print(second(t));
Serial.print(" ");
// Print time t
Serial.print(tzCode);
Serial.print(" ");
// Print date
Serial.print(day(t));
Serial.print("/");
Serial.print(month(t));
Serial.print("/");
Serial.print(year(t));
Serial.print(" ");
// Now test some other functions that might be useful one day!
Serial.print(dayStr(weekday(t)));
Serial.print(" ");
Serial.print(monthStr(month(t)));
Serial.print(" ");
Serial.print(dayShortStr(weekday(t)));
Serial.print(" ");
Serial.print(monthShortStr(month(t)));
Serial.println();
}
//====================================================================================