Protocollo NTP, fuso orario e ora legale (DST) con esp8266, esp32 o Arduino

Spread the love

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.

NTP client, Time Zones, ora legale con Arduino ed esp8266

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.

NTP Library Arduino IDE

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.

DST Countries Map

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.

Timezone Library Arduino IDE

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.

Grazie


Spread the love

26 Risposte

  1. ivan ha detto:

    ciao
    ho cercato di modificare i nomi dei mesi e giorni da inglese a italiano ma non riesco.
    C’e un sistema
    grazie

  2. Decaria Luigi ha detto:

    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

    • Renzo Mischianti ha detto:

      Ciao Luigi,
      negli esempi che ho postato non ho sfruttato lo schedulatore che offre l’NTPClient.
      Ti consiglio di spostare tutto il blocco

      delay ( 1000 );
        if (timeClient.update()){
           Serial.print ( "Adjust local clock" );
           unsigned long epoch = timeClient.getEpochTime();
           // HERE I'M UPDATE LOCAL CLOCK
           setTime(epoch);
        }else{
           Serial.print ( "NTP Update not WORK!!" );
        }
      

      nel

      loop()

      con una configurazione di questo tipo

      NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", GTMOffset*60*60, 60*60*1000);
      

      l’aggiornamento avverrà ogni 60*60*1000, cioè ogni ora, così da rimanere sempre sincronizzato.
      Ciao Renzo

  3. Antonio Teti ha detto:

    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.

    • Renzo Mischianti ha detto:

      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.

      bool utcIsDST(time_t utc);
      bool locIsDST(time_t local);
      

      Ciao Renzo

      • Antonio Teti ha detto:

        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

        • Renzo Mischianti ha detto:

          Ciao Antonio,
          devi fare un pò di controlli sul tipo dati, ma credo che con la funzione
          getDateTimeByParams(long time)
          passandogli il now() ottieni il localtime in time_t da passare alla funzione locIsDST .
          Prova a fare qualche test e poi dacci un feedback.
          Ciao Renzo

          • Antonio Teti ha detto:

            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

  4. Michele ha detto:

    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

  5. Daniele ha detto:

    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

    • Renzo Mischianti ha detto:

      Certo,
      devi modificare il pattern delle funzioni che scrivono la data.

      /**
       * 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);
      }
      

      Ciao Renzo

      • Daniele ha detto:

        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)…

        • Renzo Mischianti ha detto:

          Ciao Daniele,
          partendo sempre da quelle due funzioni, se hai un tm hai una struttura di questo tipo

          struct tm
          {
            int	tm_sec;
            int	tm_min;
            int	tm_hour;
            int	tm_mday;
            int	tm_mon;
            int	tm_year;
            int	tm_wday;
            int	tm_yday;
            int	tm_isdst;
          #ifdef __TM_GMTOFF
            long	__TM_GMTOFF;
          #endif
          #ifdef __TM_ZONE
            const char *__TM_ZONE;
          #endif
          };
          

          altrimenti è un epoch time, usando la libreria TimeLib hai le funzioni

          hour(t);
          minute(t);
          second(t);
          day(t);
          month(t);
          year(t);
          

          Ciao Renzo

  6. Alessandro ha detto:

    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

  7. Alessandro ha detto:

    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.

  8. Renzo ha detto:

    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

  9. Marco ha detto:

    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.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *