Protocollo NTP, fuso orario e ora legale (DST) con esp8266, esp32 o Arduino
Quando si crea un dispositivo, probabilmente è necessario conoscere l’ora corretta, su un dispositivo wifi la scelta ragionevole è utilizzare Internet per ottenere l’ora tramite NTP.
Il Network Time Protocol (NTP) è un protocollo di rete per la sincronizzazione dell’orologio tra sistemi informatici su reti di dati a commutazione di pacchetto, a latenza variabile. In funzione da prima del 1985, NTP è uno dei più antichi protocolli Internet attualmente in uso. NTP è stato progettato da David L. Mills dell’Università del Delaware. (cit. wiki)
Libreria NTPClient
L’IDE Arduino mette a disposizione una libreria NTPClient molto semplice da usare.
Puoi scaricarla direttamente dall’IDE Arduino IDE --> Manage Libraries
.
Vado a mostrare subito lo sketch di esempio, in quest’articolo mostro direttamente il codice e vado a spiegare direttamente con i commenti il funzionamento vista la semplicità.
/*
* Simple NTP client
* https://mischianti.org/
*
* The MIT License (MIT)
* written by Renzo Mischianti <www.mischianti.org>
*/
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>
const char *ssid = "<SSID>";
const char *password = "<PASSWORD>";
WiFiUDP ntpUDP;
// By default 'pool.ntp.org' is used with 60 seconds update interval and
// no offset
// NTPClient timeClient(ntpUDP);
// You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds).
int GTMOffset = 1;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", GTMOffset*60*60, 60000);
void setup(){
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
timeClient.begin();
}
void loop() {
// Update NTP client
timeClient.update();
// Print current date
Serial.println(timeClient.getFormattedTime());
delay(1000);
}
Ora il tuo arduino o esp è diventato un orologio.
Time library
Questo esempio presenta alcune limitazioni, per aggiornare la data che è necessario chiamare il servizio, un modo semplice per aggiornare localmente la data è utilizzare la libreria Time. Questa libreria di Arduino utilizza un orologio interno come un normale orologio che dopo averlo regolato tramite NTP è un normale orologio.
Puoi trovare la libreria qui GitHub.
Ecco un semplice esempio.
/*
* Simple NTP client
* https://mischianti.org/
*
* The MIT License (MIT)
* written by Renzo Mischianti <www.mischianti.org>
*/
const char *ssid = "<SSID>";
const char *password = "<PASSWORD>";
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <time.h>
/**
* Input time in epoch format and return tm time format
* by Renzo Mischianti <www.mischianti.org>
*/
static tm getDateTimeByParams(long time){
struct tm *newtime;
const time_t tim = time;
newtime = localtime(&tim);
return *newtime;
}
/**
* Input tm time format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getDateTimeStringByParams(tm *newtime, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
char buffer[30];
strftime(buffer, 30, pattern, newtime);
return buffer;
}
/**
* Input time in epoch format format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getEpochStringByParams(long time, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
// struct tm *newtime;
tm newtime;
newtime = getDateTimeByParams(time);
return getDateTimeStringByParams(&newtime, pattern);
}
WiFiUDP ntpUDP;
// By default 'pool.ntp.org' is used with 60 seconds update interval and
// no offset
// NTPClient timeClient(ntpUDP);
// You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds).
int GTMOffset = 1;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", GTMOffset*60*60, 60*60*1000);
void setup(){
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
timeClient.begin();
delay ( 1000 );
if (timeClient.update()){
Serial.print ( "Adjust local clock" );
unsigned long epoch = timeClient.getEpochTime();
// HERE I UPDATE LOCAL CLOCK
setTime(epoch);
}else{
Serial.print ( "NTP Update not WORK!!" );
}
}
void loop() {
// Now use local clock to show the time
Serial.println(getEpochStringByParams(now()));
delay(1000);
}
Come puoi vedere, creo alcune semplici funzioni per analizzare e formattare la data.
/**
* Input time in epoch format and return tm time format
* by Renzo Mischianti <www.mischianti.org>
*/
static tm getDateTimeByParams(long time){
struct tm *newtime;
const time_t tim = time;
newtime = localtime(&tim);
return *newtime;
}
/**
* Input tm time format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getDateTimeStringByParams(tm *newtime, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
char buffer[30];
strftime(buffer, 30, pattern, newtime);
return buffer;
}
/**
* Input time in epoch format format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getEpochStringByParams(long time, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
// struct tm *newtime;
tm newtime;
newtime = getDateTimeByParams(time);
return getDateTimeStringByParams(&newtime, pattern);
}
Devi prestare attenzione alla linea 20, lì puoi scegliere se vuoi l’ora UTC, impostando l’offset GMT su 0 o se vuoi impostare l’GTMoffset
dell’area.
Vivo in Italia, quindi ho intenzione di impostare GTM + 1 e ho impostato l’ultimo parametro su 60000, l’intervallo di aggiornamento in millisecondi, questo protocollo usa UDP come puoi vedere e devi scegliere un server NTP.
Ma questo non è sufficiente, alcune zone hanno il DST (ora legale), quindi è necessario calcolare la variazione dell’offset. In estate l’ora italiana imposta un’altra ora all’offset (quindi stiamo andando formalmente in GTM + 2), ma molti paese per il risparmio energetico utilizzano l’ora legale.
Ma calcolare che l’offset dinamico è molto noioso, per fortuna esiste una libreria che lo fa per te.
Libreria Timezone
Per gestire l’ora legale è possibile utilizzare la libreria Timezone.
Puoi scaricarla direttamente dall’IDE Arduino IDE --> Manage Libraries
.
L’uso non è così semplice, ma con l’esempio puoi capirlo.
Per prima cosa devi configurare il tuo DST, c’è un esempio con alcune configurazioni che puoi usare come punto di partenza.
// 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);
// Moscow Standard Time (MSK, does not observe DST)
TimeChangeRule msk = {"MSK", Last, Sun, Mar, 1, 180};
Timezone tzMSK(msk);
// 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 CE(CEST, CET);
// United Kingdom (London, Belfast)
TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; // British Summer Time
TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; // Standard Time
Timezone UK(BST, GMT);
// UTC
TimeChangeRule utcRule = {"UTC", Last, Sun, Mar, 1, 0}; // UTC
Timezone UTC(utcRule);
// 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);
// US Central Time Zone (Chicago, Houston)
TimeChangeRule usCDT = {"CDT", Second, Sun, Mar, 2, -300};
TimeChangeRule usCST = {"CST", First, Sun, Nov, 2, -360};
Timezone usCT(usCDT, usCST);
// US Mountain Time Zone (Denver, Salt Lake City)
TimeChangeRule usMDT = {"MDT", Second, Sun, Mar, 2, -360};
TimeChangeRule usMST = {"MST", First, Sun, Nov, 2, -420};
Timezone usMT(usMDT, usMST);
// Arizona is US Mountain Time Zone but does not use DST
Timezone usAZ(usMST);
// US Pacific Time Zone (Las Vegas, Los Angeles)
TimeChangeRule usPDT = {"PDT", Second, Sun, Mar, 2, -420};
TimeChangeRule usPST = {"PST", First, Sun, Nov, 2, -480};
Timezone usPT(usPDT, usPST);
Nel mio caso devo impostare i parametri CET e CEST (riga 10) e vado a modificare l’esempio NTP originale.
/*
* Simple NTP client
* https://mischianti.org/
*
* The MIT License (MIT)
* written by Renzo Mischianti <www.mischianti.org>
*/
const char *ssid = "<YOURSSID>";
const char *password = "<YOURPASSWD>";
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <time.h>
#include <Timezone.h> // https://github.com/JChristensen/Timezone
/**
* Input time in epoch format and return tm time format
* by Renzo Mischianti <www.mischianti.org>
*/
static tm getDateTimeByParams(long time){
struct tm *newtime;
const time_t tim = time;
newtime = localtime(&tim);
return *newtime;
}
/**
* Input tm time format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getDateTimeStringByParams(tm *newtime, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
char buffer[30];
strftime(buffer, 30, pattern, newtime);
return buffer;
}
/**
* Input time in epoch format format and return String with format pattern
* by Renzo Mischianti <www.mischianti.org>
*/
static String getEpochStringByParams(long time, char* pattern = (char *)"%d/%m/%Y %H:%M:%S"){
// struct tm *newtime;
tm newtime;
newtime = getDateTimeByParams(time);
return getDateTimeStringByParams(&newtime, pattern);
}
WiFiUDP ntpUDP;
// By default 'pool.ntp.org' is used with 60 seconds update interval and
// no offset
// NTPClient timeClient(ntpUDP);
// You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds).
int GTMOffset = 0; // SET TO UTC TIME
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", GTMOffset*60*60, 60*60*1000);
// 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 CE(CEST, CET);
void setup(){
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
timeClient.begin();
delay ( 1000 );
if (timeClient.update()){
Serial.print ( "Adjust local clock" );
unsigned long epoch = timeClient.getEpochTime();
setTime(epoch);
}else{
Serial.print ( "NTP Update not WORK!!" );
}
}
void loop() {
Serial.println(getEpochStringByParams(CE.toLocal(now())));
delay(1000);
}
Ora hai il tuo tempo con l’ora legale impostata correttamente.
ciao
ho cercato di modificare i nomi dei mesi e giorni da inglese a italiano ma non riesco.
C’e un sistema
grazie
Ciao Ivan,
credo che basti usare
Ciao Renzo
Salve,
Ho diversi dispositivi che sono sincronizzati tra loro tramite NTP
Noto però che uno con un ESP32, con il passare dei giorni, accumula qualche secondo di ritardo
Chiedo
– se c’è un numero massimo di dispositivi sincronizzabili?
– potrei evitare l’inconveniente con un secondo router?
– e se invece facci un hard-reset a un’ora prestabilita?
Grazie a chi vorrà rispondere
Ciao Luigi,
negli esempi che ho postato non ho sfruttato lo schedulatore che offre l’NTPClient.
Ti consiglio di spostare tutto il blocco
nel
con una configurazione di questo tipo
l’aggiornamento avverrà ogni 60*60*1000, cioè ogni ora, così da rimanere sempre sincronizzato.
Ciao Renzo
Buongiorno,
sto sviluppando il controllo di un orologio comunale sfruttando queste librerie. In particolare ho la necessità di lavorare con una serie di relay per il controllo meccanico delle lancette in funzione del passaggio dall’ora solare a quella legale e viceversa. Come posso fare per determinare il passaggio dello stato da CEST a CET e viceversa ed attivare i relay al cambio d’orario?
Grazie del lavoro che avete fatto.
Ciao Antonio,
credo che la soluzione più semplice sia creare un flag che verifica se il GTM è uguale al DST, Timezone ti offre un comando specifico.
Ciao Renzo
Buonasera Renzo,
vediamo se ho capito bene…
facendo riferimento alle linee 65,66,67 del tuo scketch, posso scrivere…
if (CE.utcIsDST(utc) == false) {
// eseguo il mio primo codice
} else {
// eseguo il mio secondo codice
}
Grazie,
Antonio
Ciao Antonio,
devi fare un pò di controlli sul tipo dati, ma credo che con la funzione
getDateTimeByParams(long time)
passandogli il
now()
ottieni illocaltime
intime_t
da passare alla funzionelocIsDST
.Prova a fare qualche test e poi dacci un feedback.
Ciao Renzo
Buongiorno Renzo,
leggendo attentamente il file README.md della libreria “Timezone”, ho trovato:
if (usEastern.utcIsDST(utc)) { /*do something*/ }, nel nostro caso “usEastern” viene sostituito da “CE”. Perciò penso che vada bene quello che ho scritto nel post precedente. Infatti, inserendo un Serial.print, mi restituisce CET. Per testare la funzione, ho modificato il mese del “TimeChangeRule” da “Mar” a “Oct” per simulare una timezone differente, e mi stampa CEST.
Ad ogni modo vi terrò aggiornati.
Grazie e buon lavoro,
Antonio
Ottimo Antonio, Grazie
Buona sera Renzo, ho trovato il tuo sketch molto comodo e vorrei integrarlo nel mio progetto.
Per visualizzare le info di ora e data uso una serie di 24 display 8×8 che utilizzano la libreria MD_PAROLA e MD_MAX72XX.
il dato da visualizzare lo passo tramite il metodo
displayZoneText ( uint8_t z,
const char * pText,
textPosition_t align,
uint16_t speed,
uint16_t pause,
textEffect_t effectIn,
textEffect_t effectOut = PA_NO_EFFECT
)
Come faccio ad esportare separatamente data, ora UTC e ora CET/CEST per passarlo come char array al metodo?
spero di essermi spiegato
Qui il codice, grazie:
Ciao Michele,
se ti serve la data UTC basta che usi il metodo senza applicare il Timezone
Ciao Renzo
Buon Pomeriggio,
volevo sapere se c’è la possibilità di stampare solo l’ora o solo il mese
Esempio:
Invece di leggere
30/11/2022 14:12:14
Dovrei avere
Ora : 14
Mese : 11
Certo,
devi modificare il pattern delle funzioni che scrivono la data.
Ciao Renzo
Buongiorno,
forse mi sono espresso male..
Io vorrei prendere il valore dell’ora (esempio 23), associarlo ad una variabile e confrontarlo con altro dato
Esempio
if (ora==23)…
Ciao Daniele,
partendo sempre da quelle due funzioni, se hai un tm hai una struttura di questo tipo
altrimenti è un epoch time, usando la libreria TimeLib hai le funzioni
Ciao Renzo
Ciao Renzo quando si connette al wifi non mi aggiorna l’orario e la data e parte da ……………………..NTP Update not WORK!!01/01/1970 01:04:16
Ciao Alessandro,
verifica se il server NTP è raggiungibile dalla tua rete.
Ciao RM
Ho risolto così:
void setup() {
Serial.begin(115200);
IPAddress ip(192, 168, 1, 111); // Imposta l’indirizzo IP
IPAddress gateway(192, 168, 1, 254); // Imposta il gateway
IPAddress subnet(255, 255, 255, 0); // Imposta la maschera di sottorete
IPAddress dns(192, 168, 1, 254); // Imposta il DNS
// Configura l’indirizzo IP statico
WiFi.config(ip, gateway, subnet, dns);
perché prima non prendeva un ip corretto dal DHCP del router. Grazie di tutto.
Ciao Alessandro,
grazie per il feedback, sarà sicuramente utili anche ad altri.
Ciao RM
Salve,
Lo sketch singolarmente funziona benissimo, avrei due domande:
1- con ESP32 non compila;
2- se inserisco per usare in un mio sketch la data normalmente dichiaro
String now;
nella compilazione mi da il seguente errore:
Non mi pare che nelle librerie.
#include
#include
#include
compaia una variabile
Grazie
Renzo Giurini
Ciao Renzo,
ho provato sul mio IDE con il core 2.0.14 e compila correttamente.
Sul tuo messaggio mancano delle parti.
Per verificare meglio e vedere qual è il problema ti chiederei di aprire un topic nella sezione specifica.
Ciao RM
Buonasera, sto sviluppando dei device che devono eseguire tutto il codice nel setup senza la parte di loop perchè usano il sleep mode. Devo inserire una sincronizzazione ntp per avere l’ora esatta in cui avviene l’avvio, come posso inserire il suo codice nel setup? ho provato a spostare la parte di serial print del lopp nel setup ma non funziona.
Ciao Marco,
non ho ben capito, mi potresti spiegare meglio.
Magari se apri un topic nel forum e metti il codice così è più facile.
Grazie RM
Ciao, ho aperto il topic ma non lo vedo! Probabilemente ho aperto piu’ topic preche’ non vedevo il precedente.
In caso cancella i piu vecchi..