In applicazioni embedded è spesso necessario avere a disposizione un orologio quanto più preciso possibile. Pensiamo ad esempio a dispositivi che si devono attivare in un preciso istante o a logger che devono memorizzare letture di una grandezza (ad esempio la temperatura esterna) a precisi intervalli.
La soluzione spesso adottata è l’utilizzo di un chip RTC (Real Time Clock). Il problema diviene quindi come configurare il chip RTC con l’orario corrente e come tenerlo allineato. Qualche tempo fa ho pubblicato un progetto (RTCSetup) per configurare un RTC collegato ad Arduino usando l’orario del proprio PC; oggi vedremo invece come utilizzare un servizio Internet con il chip esp32.
SNTP
SNTP (Simple Network Time Protocol) è un protocollo pensato per sincronizzare l’orario dei dispositivi connessi ad Internet. L’ultima versione del protocollo (SNTPv4) è definita nella RFC 4330. Il nome “simple” (semplice) è dovuto al fatto che SNTP è una versione ridotta e semplificata del protocollo NTP (Network Time Protocol); tale protocollo infatti – per consentire elevata accuratezza – ha un funzionamento molto complesso.
Il funzionamento base di SNTP è il seguente:
- il dispositivo client contatta un server usando il protocollo UDP sulla porta 123
- il server restituisce un valore di timestamp di 64bit, dove:
- i primi 32 bit rappresentano il numero di secondi trascorsi dal giorno 01/01/1990
- i rimanenti 32 bit rappresentano la frazione di secondi trascorsi
esp-idf
In un precedente articolo vi ho parlato della libreria lwip, scelta dal framework esp-idf per gestire le comunicazioni di rete. Tale libreria include una app (sntp.c) che implementa un client SNTP.
Vediamo come utilizzarla in un nostro programma (il sorgente completo del programma è disponibile nel mio repository Github).
Per prima cosa dobbiamo includere il relativo header file:
#include "apps/sntp/sntp.h"
|
Quindi configuriamo il client SNTP in modo che interroghi il server (pool mode) ogni tot secondi:
sntp_setoperatingmode(SNTP_OPMODE_POLL); |
In alternativa possiamo configurare il client in modalità listen only (SNTP_OPMODE_LISTENONLY). In tale modalità il client SNTP rimarrà in attesa di ricevere aggiornamenti via broadcast, senza interrogare attivamente alcun server.

sntp_opts.h
Dobbiamo ora indicare al client SNTP quale server utilizzare. Una delle scelte più comuni è affidarsi al cluster di server pool.ntp.org. In alternativa io, vivendo in Italia, normalmente utilizzo il server SNTP messo a disposizione dall’Istituto Nazionale di Ricerca Meterologica (ntp1.inrim.it) e sincronizzato con un orologio atomico.
Nell’esempio che ho preparato, il nome del server può essere configurato via menuconfig:
sntp_setservername(0, CONFIG_SNTP_SERVER); |
Terminata la configurazione, possiamo attivare il client con:
sntp_init(); |
Il client sarà eseguito in maniera autonoma rispetto al programma principale.
Per ottenere l’orario attuale del sistema, utilizziamo il metodo time() che aggiorna un puntatore ad una variabile di tipo time_t:
time_t now; time(&now); |
La variabile time_t rappresenta normalmente il numero di secondi trascorsi dalla data chiamata Epoch (01/01/1970). Per ottenere i vari campi (anno, mese, giorno…) possiamo utilizzare il metodo localtime_r che aggiorna una struct tm:
struct tm timeinfo; localtime_r(&now, &timeinfo); |
I campi che compongono la struct tm sono dettagliati nel file time.h:
Possiamo quindi capire se la prima sincronizzazione ha avuto successo verificando ad esempio l’anno:
while(timeinfo.tm_year < (2016 - 1900)) { printf("Time not set, waiting...\n"); vTaskDelay(5000 / portTICK_PERIOD_MS); time(&now); localtime_r(&now, &timeinfo); } |
Avendo a disposizione la struct tm, possiamo utilizzare il metodo strftime() per formattare una stringa secondo un formato scelto. I placeholders disponibili sono molti e descritti nella manpage di tale metodo. Di seguito alcuni esempi:
char buffer[100]; // es. 08/05/2017 15:10:34 strftime(buffer, sizeof(buffer), "%d/%m/%Y %H:%M:%S", &timeinfo); // es. Monday, 08 May 2017 strftime(buffer, sizeof(buffer), "%A, %d %B %Y", &timeinfo); // es. Today is day 128 of year 2017 strftime(buffer, sizeof(buffer), "Today is day %j %Y", &timeinfo); |
Fuso orario
I server SNTP restituiscono l’orario secondo il fuso orario UTC (Tempo coordinato universale). E’ possibile applicare un proprio fuso orario modificando la variabile di ambiente TZ, con il metodo:
int setenv(const char *name, const char *value, int overwrite); |
name indica il nome della variabile mentre value il valore che tale variabile deve assumere. Se overwrite è > 0 il metodo setenv aggiorna la variabile se già esiste.
Una volta aggiornato il valore della variabile TZ, è possibile inizializzare la funzionalità di timezone con il comando tzset().
La variabile TZ può assumere due tipi di valori, a seconda che si voglia gestire o meno l’ora legale.
Nel caso più semplice (nessuna gestione dell’ora legale), il formato è std offset, dove std indica il nome del fuso orario (3 o più caratteri), mentre offset indica il tempo da aggiungere a quello locale per arrivare a UTC. Ad esempio l’Italia è nel fuso orario CET (Central European Time) che è di un’ora avanti rispetto a UTC (quindi offset -1); il comando da utilizzare è:
setenv("TZ", "CET-1", 1); |
Se si vuole gestire anche l’ora legale, è possibile utilizzare il formato std offset dst [offset],start[/time],end[/time]. std e offset iniziali hanno lo stesso significato descritto in precedenza. dst e il secondo offset indicano il nome del fuso orario durante l’ora legale e il relativo offset (se questo secondo offset è omesso, il default è +1 rispetto all’orario normale). Infine start e end indicano giorno e ora di inizio e fine dell’ora legale.
Sempre nel caso dell’Italia, durante l’ora legale si utilizza il fuso orario CEST (Central European Summer Time) che è attiva (direttiva 2008/84/CE del Parlamento Europeo) dalle 02:00 dell’ultima domenica di Marzo fino alle 03:00 dell’ultima domenica di Ottobre.
Il comando da utilizzare in questo caso è quindi:
setenv("TZ", "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", 1); |
M3.5.0 indica infatti Marzo (3), ultima settimana (5), giorno domenica (0).
Su Internet si trovano siti che raccolgono i valori di TZ per i differenti fusi orari.
Gli step per formattare l’orario secondo un fuso orario sono quindi:
- configurare la variabile TZ (setenv())
- inizializzare la funzionalità di conversione orario (tzset())
- aggiornare la struct tm (localtime_r())
Nel programma di esempio:
// print the actual time in Italy printf("Actual time in Italy:\n"); localtime_r(&now, &timeinfo); strftime(buffer, sizeof(buffer), "%d/%m/%Y %H:%M:%S", &timeinfo); printf("%s\n", buffer); |