365 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			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();
 | |
| }
 | |
| 
 | |
| //====================================================================================
 |