ESP8266. Récupérer l'heure avec NTPClient, stockage SPIFFS, calculer le temps écoulé et déclencher un événement • Domotique et objets connectés à faire soi-même

L’ESP8266 ne disposent pas d’horloge temps réel. Il est possible d’ajouter une horloge temps réelle via un module RTC (Real Time Clock). Pour des projets connectés au réseau WiFi et donc la plupart du temps à internet, il est beaucoup plus facile (et moins chère) de récupérer l’heure sur un serveur de temps (NTP) directement sur internet. 

En connaissant la date et l’heure, il sera possible d’horodater les mesures, de connaître le temps écoulé entre deux événements, d’afficher l’heure actuelle sur l’interface WEB, de déclencher une action programmée…

L’ESP8266 dispose d’une zone mémoire dans laquelle il est possible de stocker des fichiers. Dans le projet de station météo, on utilise la zone SPIFFS pour stocker les fichiers HTML, Javascript et CSS de l’interface Web.

Ici, nous allons créer un petit fichier texte qui stockera la date et l’heure ce qui nous permettra de calculer le temps écoulé entre deux événements même en cas de redémarrage. On pourrait également utiliser le stockage dans l’EEPROM mais la zone SPIFFS permet de stocker un grande quantité de données, idéal pour un enregistreur par exemple.

Le schéma ci-dessous explique comment nous allons faire

8mv5ae8v7hwavjqpaxog-4377902

Si vous effectuez une recherche à partir du gestionnaire de bibliothèques Arduino IDE, vous trouverez plusieurs bibliothèques permettant de récupérer l’heure d’un serveur NTP

  • NTPClient de Fabrice Weinberg, puissant et simple d’utilisation
  • EasyNTPClient de Harsha Alva, une librairie ultra simplifiée qui ne propose que 3 méthodes. Obtenir le décalage horaire, définir le décalage horaire et obtenir l’heure qui récupère l’heure au format UNIX en secondes. C’est un peu trop limité à mon goût.
  • NTPClientLib de German Martin, il est basé sur la bibliothèque officielle Time pour Arduino. Il fonctionne sur ESP8266, ESP32 et Arduino MKR1000
  • Time de Michael Margolis, la bibliothèque initialement développée pour l’Arduino. Il est difficile à utiliser (par rapport aux librairies récentes)

Installer la librairie NTPClient (le plus simple)

Après avoir testé la quasi-totalité des librairies NTP sur ESP8266, je vous conseille d’utiliser la librairie NTPClient de Fabrice Weinberg qui est l’une des plus simple à utiliser.

onxb5ahd3zfea2i1gagg-1541482

Lors du développement d’objets connectés fonctionnant sur batterie, on peut souhaiter connaître rapidement le temps de déclenchement pour appeler éventuellement une autre fonction. La bibliothèque NTPClient peut le faire très facilement en forçant la récupération du temps en appelant la connexion UDP pour se connecter à un serveur NTP. Pour cela, il faudra inclure la bibliothèque standard WiFiUdp.h dans l’en-tête du croquis.

Ensuite, il est possible d’initialiser l’objet NTPClient avec la configuration par défaut:

  • Serveur: pool.ntp.org
  • Intervalle de mise à jour: 60 secondes
  • Décalage horaire (en secondes): aucun
NTPClient timeClient(ntpUDP);

Ou pour spécifier des paramètres. Ici, nous appliquons un décalage horaire d’une heure

NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);

Ensuite, il ne reste plus qu’à démarrer le service avec la méthode forceUpdate(). C’est une méthode très utile pour les projets qui fonctionnent sur batterie car on peut forcer manuellement l’actualisation du temps dès que l’ESP8266 est connecté à internet

Voici un petit exemple qui récupère et affiche l’heure toutes les 10 secondes

#include 
#include 
#include 

#define NB_TRYWIFI        10    // Nbr de tentatives de connexion au réseau WiFi | Number of try to connect to WiFi network

const char* ssid = "xxxx";
const char* password = "xxxx";

WiFiClient espClient;
WiFiUDP ntpUDP;

NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);

void setup() {
  Serial.begin(115200);
  Serial.println("");
  Serial.print("Startup reason:");Serial.println(ESP.getResetReason());

  WiFi.begin(ssid, password);

  Serial.println("Connecting to WiFi.");
  int _try = 0;
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print("..");
    delay(500);
    _try++;
    if ( _try >= NB_TRYWIFI ) {
        Serial.println("Impossible to connect WiFi network, go to deep sleep");
        ESP.deepSleep(10e6);
    }
  }
  Serial.println("Connected to the WiFi network");
  
  // Démarrage du client NTP - Start NTP client
  timeClient.begin();
}

void loop() {
    // Met à jour l'heure toutes les 10 secondes | update time every 10 secondes
    timeClient.update();
    Serial.println(timeClient.getFormattedTime());
    delay(10 * 1000);  // Attendre 10s | wait 10 secondes  
}

Comment stockez la date et l’heure dans la zone SPIFFS ?

Dans de nombreux cas, il est utile de garder une trace du temps que nous venons de récupérer pour d’autres utilisations :

  • Calculer le temps écoulé depuis le dernier démarrage ou la dernière réinitialisation
  • Déclenchez une action si le temps écoulé est supérieur à un point de consigne. Par exemple, envoyer l’état d’un détecteur de mouvement si un deuxième mouvement est détecté 30 secondes après le premier. Ceci évitera de multiplier les notifications inutiles
  • Déclenchez une action pré-programmée. Par exemple, déclenchez un relais qui ouvre une vanne d’arrosage de jardin pendant 1 minute …

Pour stocker l’heure, il est beaucoup plus facile de la sauvegarder dans un simple fichier texte dans la zone SPIFFS que de la stocker dans l’EEPROM. Pour cela, déclarez simplement la bibliothèque fs.h au début du programme.

Ensuite, il sera très facile de calculer le temps écoulé depuis le dernier démarrage ou réinitialisation. Voici une adaptation du programme précédent.

Les fonctions suivantes ont été ajoutées au code précédent. Voici comment ça fonctionne :

  • loadLastEvent() permet de recharger la dernière heure NTP enregistrée dans le fichier lastEvent.txt situé dans la zone mémoire SPIFFS de l’ESP8266

La librairie fs.h met à disposition la classe SPIFFS qui permet de lire et d’enregistrer des fichiers.
La méthode open(fichier, option) permet d’ouvrir le fichier (attention, il n’est pas possible d’avoir des dossiers dans la zone SPIFFS).  L’option “r” indique qu’on souhaite un accès uniquement en lecture (read).

La méthode readStringUntil(‘n’) lit le contenu du fichier jusqu’au caractère indiqué, ici “n” qui est le caractère de contrôle du retour à la ligne.

On n’oublie pas de convertir en entier avec la méthode .toInt() ce qui nous permettra de calculer le temps écoulé avec une simple soustraction.

On ferme le fichier avec la méthode close().

File f = SPIFFS.open("/lastEvent.txt", "r"); 
lastEvent = f.readStringUntil('n').toInt();
f.close();
  • saveLastEvent() enregistre le temps NTP au format EPOCH Unix

Comme précédemment, on ouvre le fichier avec la méthode SPIFFS.open(). Cette fois, on indique qu’on souhaite écrire dans le fichier avec l’option “w”.

On enregistre dans un fichier comme sur le port série avec les méthode print() ou println(). La méthode println() ajoute un renvoi à la ligne, souvenez-vous, c’est le caractère “n” utilisé précédemment.

On ferme le fichier avec la méthode close().

File f = SPIFFS.open("/lastEvent.txt", "w")
f.println(_now); 
f.close();
  • timeSpent() calcule le temps écoulé en secondes depuis le dernier démarrage

Pour calculer facilement la durée écoulée, il suffit de récupérer le temps au format Unix à l’aide de la méthode getEpochTime(). Connaissant le temps précédent lastEvent, il suffit de faire une simple soustraction pour calculer le temps écoulé

_now = timeClient.getEpochTime(); 
int timeSpent = _now - lastEvent;
  • runEvent() exécute un événement si le temps écoulé est supérieur au temps spécifié dans la variable DELAY_NEXT_EVENT. On pourrait par exemple envoyer un message (MQTT ou autre) avec le niveau de batterie.

Vous pouvez également tester en activant le mode Deep-Sleep du module ESP8266. Pour cela, il faudra faire ces deux adaptations dans le code proposé ci-dessous

  • DEEP_SLEEP à True
  • Téléverser le programme puis connecter RST à D0 avec un Jumper et faire un Reset de l’ESP8266

inbxacptmm3c2nzn8som-2696008

On active le mode deep-sleep de l’ESP8266 en reliant les broches RST et D0

Pour tout savoir sur la mise en veille des modules ESP8266, lisez cet article

Voici ce que fait le programme

  • Au démarrage :
    • L’ESP8266 se connecte au réseau WiFi et configure la connexion au serveur NTP
    • Si le mode Deep Sleep est activité, il exécute la méthode runEvent() qui fait ceci
      • Calcul et affiche le temps écoulé depuis le dernier événement en lisant
      • Si le temps écoulé à dépassé la consigne DELAY_NEXT_EVENT, le temps récupéré sur le serveur NTP est enregistré dans le fichier affiche le temps écoulé depuis de dernier événement
  • Si le mode Deep Sleep est activé, exécute la méthode runEvent() au réveille avant de se rendormir>
  • Si le mode Deep Sleep est désactivé, toutes les 10 secondes
    • Actualise le temps
    • Exécute runEvent()

Avant de téléverser le programme, n’oubliez pas de modifier l’identifiant réseau (SSID) et le mot de passe (password)

#include 
#include 
#include 
#include "FS.h"

#define NB_TRYWIFI        3     // Nbr de tentatives de connexion au réseau WiFi | Number of try to connect to WiFi network
#define DELAY_NEXT_EVENT  60    // délai entre deux événements en secondes | delay before new event in second 

#define DEEP_SLEEP        false
#define TIME_TO_SLEEP     30 * 1000 * 1000  // Durée de mise en sommeil en microsecondes (30s ici) | Sleep duration in microseconds (30s here)
// Paramètres WiFi | WiFi settings
const char* ssid = "****";
const char* password = "****";

WiFiClient espClient;
WiFiUDP ntpUDP;

NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);

int lastEvent;
int start = millis();
long int _now = 0;

bool loadLastEvent() {
  // Recharge le dernier horodatage depuis la zone SPIFFS | Reload last date/time from SPIFFS area
  File f = SPIFFS.open("/lastEvent.txt", "r");
  if (!f) {
    Serial.println("Failed to open file");
    lastEvent = DELAY_NEXT_EVENT + 1; // force l'enregistrement horodatage au premier démarrage | force to save date/time on first boot
    return false;
  }
  lastEvent = f.readStringUntil('n').toInt(); 
  f.close();
  return true;
}

bool saveLastEvent() {
  File f = SPIFFS.open("/lastEvent.txt", "w");
  if (!f) {
    Serial.println("file open failed");
    return false;
  }
  f.println(_now);
  f.close();
  return true;
}

long int timeSpent() {
  loadLastEvent();
  timeClient.forceUpdate();
  _now = timeClient.getEpochTime();
  int timeSpent = _now - lastEvent;
  Serial.print("Time spent since last alarm (s): "); Serial.println(timeSpent);
  return timeSpent;
}

void runEvent() {
  if ( timeSpent() >= DELAY_NEXT_EVENT ) {
    Serial.println("RUN EVENT");
    saveLastEvent();
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("");
  Serial.print("Startup reason:"); Serial.println(ESP.getResetReason());

  SPIFFS.begin();

  WiFi.begin(ssid, password);

  Serial.println("Connecting to WiFi.");
  int _try = 0;
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print("..");
    delay(500);
    _try++;
    if ( _try >= NB_TRYWIFI ) {
      Serial.println("Impossible to connect WiFi network, go to deep sleep");
      ESP.deepSleep(TIME_TO_SLEEP);
    }
  }
  Serial.println("Connected to the WiFi network");

  // Démarrage du client NTP - Start NTP client
  timeClient.begin();
  
  //Dort un peu si le mode Deep Sleep est activé | Sleep a little if Deep Sleep mode is activated
  if ( DEEP_SLEEP ) {
    Serial.println(""); Serial.println("Go to deep sleep");
    runEvent();
    ESP.deepSleep(TIME_TO_SLEEP);
  }
}

void loop() {
  // Le mode Deep Sleep n'est pas activé | Deep Sleep mode is not activated
  if ( !DEEP_SLEEP ) {
    timeClient.update();
    Serial.println(timeClient.getFormattedTime());
    runEvent();
    // Attend 30s | Wait 30 seconds
    delay(30000);
  }
}

Comparaison des bibliothèques

Voici un petit tableau récapitulant les fonctions offertes par les principales bibliothèques testées

NTPClient EasyNTPClient NTPCLientLib
WiFi UDP X X
Port UDP personnel X
Compatibilité NETWORK_ESP8266, NETWORK_ESP32,  ARDUINO_ARCH_SAMD or NETWORK_W5100 in begin()
Paramètres 
Serveur NTP object or setPoolServerName X object or setNtpServerName
Décalage horaire (Jet lag) object orsetTimeOffset SetTimeOffset
Interval de synchronisation object or setUpdateInterval object or setInterval
Zone horaire (time zone) setTimeZone
Heure d’été (daylight) setDayLight
Méthodes
Récupérer le temps NTP udpate getUnixTime
Forcer la synchronisation forceUpdate getUnixTime
Décalage horaire (Jet lag) getTimeOffset
Zone horaire (Time zone) getTimeZone ou getTimeZoneMinutes
Suspendre la synchronisation stop
Format date/heure
UNIX (Epoch) temps unix getEpochTime getUnixTime
Jours getDay
Heures getHours
Minutes getMinutes
Secondes getSecondes
HH:MM:SS getFormattedTime getTimeStr
Date JJ:MM:AAAA getDateStr
date + temps sous la forme d’une chaine getTimeDateString
Fonctions avancée
Temps écoulé depuis de démarrage* getLastBootTime
Première synchronisation getFirstSync
Heure d’été isSummerTimePeriod

(*) ne fonctionne pas en cas de RESET ou de redémarrage manuel

Compatibilité des librairies

Voici également une liste des cartes de développement supportées par les bibliothèques. Les développeurs ne peuvent pas tester toutes les plates-formes. Certaines cartes pourraient être prises en charge sans figurer dans le tableau ci-dessous. N’hésitez pas à partager votre expérience si tel est le cas.

NTPClient EasyNTPClient NTPCLientLib
Arduino X X X
Arduino MKR1000 X X X
ESP8266 X X X
ESP32 X probable X
AVR ? ? X

Quelques serveurs de temps (NTP)

Voici une petite liste de serveurs de temps (en France et dans le monde). Il en existe des centaines, voici les principaux

Monde entier pool.ntp.org
Asie asia.pool.ntp.org
Europe europe.pool.ntp.org
Amérique du Nord north-america.pool.ntp.org
America du Sud south-america.pool.ntp.org
Oceanie oceania.pool.ntp.org

Librairies NTPClientLib et Time (déconseillé)

Les bibliothèques NTPClientLib sont deux bibliothèques très anciennes. La Time Library est la librairie officielle de gestion du temps avec le code Arduino. La bibliothèque NTPClientLib est une superposition adaptée aux modules ESP8266. Il prend désormais en charge les cartes Arduino MKR1000 ainsi que ESP32.

Avec l’ESP8266, on dispose d’une connexion internet permanente. Pour pouvoir récupérer le temps depuis un serveur de temps (serveur NTP, Network Time Protocol), l’ESP8266 doit être connecté à internet. Il n’est donc pas possible de l’utiliser en point d’accès (mode AP). Sauf à gérer une étape d’initialisation durant laquelle vous irez récupérer le temps.

Il est très facile de récupérer le temps sur internet à l’aide de la librairie NTPClientLib développée par gmag11. Pour fonctionner, il faudra également installer et déclarer la librairie TimeLib standard de l’Arduino. Ces deux librairies sont disponibles depuis le gestionnaire de bibliothèque de l’IDE Arduino.

Un fois les librairies installées, il ne reste plus qu’à les déclarer dans le sketch.

#include 
#include 

La bibliothèque NtpClient fournit toutes les méthodes nécessaires à la gestion du temps, mais elle utilise la bibliothèque TimeLib pour fonctionner. Vous trouverez de nombreux (mais anciens) tutoriels sur Internet qui décodent directement les messages UDP envoyés par le serveur de temps.

Avant de pouvoir récupérer l’heure sur Internet, vous devez déjà démarrer l’objet NTP. Il est démarré avec la méthode begin():

  • L’URL du serveur de temps. Par défaut, vous pouvez interroger pool.ntp.org
  • Décalage horaire correspondant à votre fuseau horaire
  • Heure d’été. Réglez sur True pour gérer le mode heure d’été, les hivers

La méthode setInterval() est utilisée pour définir la fréquence d’interrogation du serveur de temps. Restez raisonnable, inutile de re-synchroniser l’horloge de l’ESP8266 10 fois par seconde! Vous surchargerez inutilement le serveur NTP.

Ici, l’heure sera re-synchronisée toutes les minutes (ce qui est déjà trop).

NTP.begin("pool.ntp.org", 1, true);
NTP.setInterval(60);

On peut créer un fonction callback qui sera appelée à chaque fois qu’un événement se produit (serveur inaccessible, mauvaise URL, demande de temps).

NTP.onNTPSyncEvent([](NTPSyncEvent_t error) {
  if (error) {
    Serial.print("Time Sync error: ");
    if (error == noResponse)
      Serial.println("NTP server not reachable");
    else if (error == invalidAddress)
      Serial.println("Invalid NTP server address");
    }
  else {
    Serial.print("Got NTP time: ");
    Serial.println(NTP.getTimeDateString(NTP.getLastNTPSync()));
  }
});

Maintenant que tout est prêt, on peut demander l’heure à tout moment à son tour le code Arduino. La bibliothèque NtpClientLib offre de nombreuses fonctions qui faciliteront notre travail, en plus de synchroniser l’heure avec un serveur NTP:

  • TimeZone(get or set) Décalage horaire de la zone horaire allant de -11 à + 13
  • Stop() arrête la synchronisation de l’heure
  • Intervalle(get ou set) le temps en millisecondes avant la prochaine synchronisation avec le serveur NTP
  • DayLight(get or set) permet de gérer l’heure d’été
  • GetTimeStr(heure unix) convertit directement l’heure unix en chaîne, pratique pour créer un affichage
  • GetDateStr()
  • GetDateStr(heure_unix) chaîne contenant la date
  • GetTimeDateString() chaîne contenant la date et l’heure
  • GetLastNTPSync() dernière synchronisation

Comme vous pouvez le voir, il existe presque toutes les combinaisons. Seul hic, les chaînes ne sont pas (encore) dans une norme ISO. Vous pouvez rencontrer des problèmes si vous devez traiter ces dates en code navigateur (Javascript). Pour l’instant, le mieux est de conserver l’horodatage unix renvoyé par la fonction getTime().

L’horodatage présente également plusieurs avantages :

Deux dates peuvent facilement être comparées par une simple soustraction. Le décalage horaire sera en secondes. C’est un nombre assez court, donc économique en mémoire. Ceci est important dans un projet Arduino / ESP8266. Ce sera facile à traiter en code Javascript. La fonction nouvelle date (horodatage) permet de créer une date très simplement.

Il existe également des fonctions pratiques pour gérer le système

  • GetUptimeString() durée d’exécution de l’ESP8266
  • GetUptime() idem mais sous la forme d’un horodatage Unix
  • GetLastBootTime() temps écoulé depuis le dernier démarrage de l’ESP8266
  • GetFirstSync() première synchronisation avec le serveur depuis le démarrage

Pour vérifier si la conversion de l’heure est correct, on troue de nombreux convertisseurs sur internet, en voici 2

Mises à jour

30/07/2020 Première publication du tutoriel

Avez-vous aimé cet article ?