#include <NTPClient.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <RTClib.h>
#define NTP_SERVER "nl.pool.ntp.org"
#define NTP_INTERVAL 3600000
#define TZ_OFFSET 1
uint32_t timeout = millis(); // timestamp for timeout
uint16_t interval; // interval of timeout
DateTime now;
time_t nowRTC;
time_t nowNTP;
bool RTCstatus;
bool NTPstatus;
bool DST;
bool oldDST;
// init RTC
RTC_DS1307 rtcTime;
// init UDP
WiFiUDP UDPClient;
// init NTP client
NTPClient ntpTime(UDPClient, NTP_SERVER, TZ_OFFSET * 3600, NTP_INTERVAL);
void setupWifi() {
uint8_t wifi_errorcount;
Serial.println("CONNECTING to WIFI");
WiFi.begin("Wokwi-GUEST", "", 6);
while ( WiFi.status() != WL_CONNECTED ) {
Serial.print(".");
delay(500);
if ( wifi_errorcount++ > 4 ) break;
}
if ( WiFi.status() == WL_CONNECTED ) {
Serial.println("\nWIFI connected OK");
Serial.println(WiFi.localIP());
} else {
Serial.println("WIFI connection FAILED");
}
Serial.println();
}
void getTime() {
DateTime RTCnow;
// check if RTC is running
if (! rtcTime.isrunning() ) {
Serial.println("RTC is NOT running. Start");
if ( rtcTime.begin() ) {
Serial.println("RTC started");
RTCstatus = 1;
} else {
Serial.println("ERROR: RTC NOT started");
RTCstatus = 0;
}
} else {
Serial.println("RTC is running");
RTCstatus = 1;
}
// get time from RTC
if ( RTCstatus ) {
Serial.println("Get time from RTC");
RTCnow = rtcTime.now();
nowRTC = RTCnow.unixtime();
if ( RTCnow.isValid() ) {
Serial.println("RTC time is valid");
RTCstatus = 1;
} else {
Serial.println("RTC time is NOT valid");
RTCstatus = 0;
}
}
Serial.println();
// update NTP if update > NTP_INTERVAL ago
if ( ntpTime.getEpochTime() - nowNTP > NTP_INTERVAL / 1000 ) {
Serial.println("Get time from NTP");
if ( ntpTime.update() ) {
Serial.println("NTP update SUCCESS");
NTPstatus = 1;
nowNTP = ntpTime.getEpochTime();
// compare NTP to RTC time
if ( ( nowRTC != nowNTP ) && (RTCstatus) ) {
Serial.println("NTP and RTC differ: adjust RTC from NTP");
// adjust if not the same
rtcTime.adjust(nowNTP);
}
} else {
Serial.println("NTP update FAILED");
NTPstatus = 0;
}
Serial.println();
}
// evaluate time status
if ( RTCstatus && NTPstatus ) {
Serial.println("RTC and NTP both OK. Time from RTC:");
now = RTCnow;
} else if ( RTCstatus ) {
Serial.println("RTC OK; NTP NOT OK. Time from RTC:");
now = RTCnow;
} else if ( NTPstatus ) {
Serial.println("RTC NOT OK; NTP OK. Time from NTP:");
now = nowNTP;
} else {
Serial.println("RTC and NTP both NOT OK. NO TIME SOURCE ERROR");
}
char dateformat[] = "hh:mm DDD DD MMM YYYY";
Serial.println(now.toString(dateformat));
Serial.println();
// check DST every hour on the hour and at startup
if ( ( now.minute() == 0 ) || ( interval == 0 ) ) {
setDST();
}
}
void setDST() {
Serial.println("Check DST");
time_t nowDST = now.unixtime();
time_t startDST;
time_t endDST;
uint8_t startDSTweekday;
uint8_t endDSTweekday;
// create tm struct pointer fill with current datetime
tm *DSTptr = gmtime(&nowDST);
// update struct with start DST date/hour for current year
DSTptr -> tm_mon = 2; // march (month starts at 0)
DSTptr -> tm_mday = 25; // 25th is first possible day of DST onset
DSTptr -> tm_hour = 2; // and DST starts at 02:00
DSTptr -> tm_min = 0;
DSTptr -> tm_sec = 0;
// update
mktime(DSTptr);
// get weekday of the 25th
startDSTweekday = ( ( DSTptr -> tm_wday + 6 ) % 7 + 1 );
// set DST start actual day
DSTptr -> tm_mday = 32 - startDSTweekday;
// set DST start time
startDST = mktime(DSTptr);
Serial.print("DST Start: ");
Serial.print(DSTptr -> tm_mday);
Serial.print("/");
Serial.print(DSTptr -> tm_mon + 1); // tm month starts at 0
Serial.print("/");
Serial.print(DSTptr -> tm_year + 1900);
Serial.println();
// update struct with end DST for current year
DSTptr -> tm_mon = 9; // October (month starts at 0)
DSTptr -> tm_mday = 25; // 25th is first possible day of DST
DSTptr -> tm_hour = 3; // and DST ends at 03:00 (including DST)
// update
mktime(DSTptr);
// get weekday of the 25th
endDSTweekday = ( ( DSTptr -> tm_wday + 6 ) % 7 + 1 );
// set DST end actual day
DSTptr -> tm_mday = 32 - endDSTweekday;
// set DST end time
endDST = mktime(DSTptr);
Serial.print("DST End: ");
Serial.print(DSTptr -> tm_mday);
Serial.print("/");
Serial.print(DSTptr -> tm_mon + 1); // tm month starts at 0
Serial.print("/");
Serial.print(DSTptr -> tm_year + 1900);
Serial.println();
char dateformat[] = "Today: DD/MM/YYYY";
Serial.println(now.toString(dateformat));
// determine DST in effect
if (now.unixtime() > startDST && now.unixtime() < endDST) {
DST = 1;
Serial.println("DST in effect");
} else {
DST = 0;
Serial.println("DST NOT in effect");
}
// check if DST changed
if ( DST != oldDST ) {
Serial.println("DST changed: Update NTP and RTC");
// update NTP
ntpTime.setTimeOffset( ( TZ_OFFSET + DST ) * 3600 );
nowNTP = ntpTime.getEpochTime();
Serial.println(nowNTP);
// update RTC
nowRTC = nowRTC + ( ( DST * 2 - 1 ) * 3600 );
rtcTime.adjust(nowRTC);
Serial.println(nowRTC);
// update now()
if ( RTCstatus ) {
now = nowRTC;
} else {
now = nowNTP;
}
Serial.println(now.unixtime());
oldDST = DST;
}
Serial.println();
}
void displayTime() {
/* // for future use with 7 segment display
display.setPosition( 0, number[now.hour() / 10] );
display.setPosition( 1, number[now.hour() % 10] );
display.setPosition( 2, number[now.minute() / 10] );
display.setPosition( 3, number[now.minute() % 10] ^ alarmSet );
*/
Serial.println("This is now:");
char dateformat[] = "hh:mm DDD DD MMM YYYY";
Serial.println(now.toString(dateformat));
Serial.println();
}
void setup () {
Serial.begin(9600);
Wire.begin();
Serial.println("INIT");
setupWifi();
// init NTP and update
ntpTime.begin();
// init RTC
rtcTime.begin();
}
void loop () {
if ( millis() - timeout >= interval ) {
Serial.println();
getTime();
if ( RTCstatus || NTPstatus ) {
displayTime();
} else {
Serial.println("ERROR: NO VALID TIME SOURCE AVAILABLE");
}
if ( WiFi.status() != WL_CONNECTED ) {
Serial.println("Wifi DISCONNECTED; Reset Wifi");
setupWifi();
}
timeout = millis();
interval = ( (60 - now.second()) * 1000 );
Serial.print("SLEEP: ");
Serial.println( interval / 1000 );
Serial.println();
}
delay(250);
}