Quantcast
Channel: Tutorials – lucadentella.it
Viewing all articles
Browse latest Browse all 84

ESP32 (17) – SNTP

$
0
0

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

sntp-00

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

Alcuni parametri di configurazione, tra i quali l’intervallo (in millisecondi) tra una interrogazione e la successiva sono configurabili modificando il file sntp_opts.h:

sntp-01

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:

sntp-02

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);
}
Molte delle funzioni a disposizione per la gestione di data e ora hanno due versioni, una “normale” e una restartable (es localtime e localtime_r). In ambiente multithread è sempre consigliato utilizzare le funzioni con suffisso _r che garantiscono la possibilità di essere eseguite in simultanea (reentrancy) non utilizzando buffer in comune.

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 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 startend 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);

sntp-03


Viewing all articles
Browse latest Browse all 84

Trending Articles