Domotique et objets connectés à faire soi-même

ESP8266. Récupérer l’heure avec NTPClient, stockage SPIFFS, calculer le temps écoulé et déclencher un événement

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

 

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

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.

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:

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 <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

#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 :

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.

La librairie fs est une librairie standard, inutile donc de l’installer manuellement depuis le gestionnaire.

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 :

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

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

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;

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

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

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

#include <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#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

Zone Serveur NTP
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.

J’ai cependant rencontré des problèmes de synchronisation du temps. La bibliothèque NTPClientLib ne convient pas aux projets fonctionnant sur batterie. La synchronisation de l’heure peut prendre quelques instants. L’heure est récupérée via une procédure de rappel qui peut prendre du temps et donc consommer inutilement la batterie. Cependant, j’ai préféré garder cette partie du tutoriel dans le cas où la bibliothèque NTPClient ne conviendrait pas à votre projet.

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 <TimeLib.h>
#include <NtpClientLib.h>

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():

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:

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

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 ?
[Total: 0 Moyenne: 0]
Exit mobile version