Projet station météo ESP8266 (Partie 4). ArduinoJson, charger, enregistrer des fichiers (SPIFFS)

Partager sur facebook
Partager sur twitter
Partager sur linkedin
Partager sur pinterest
Partager sur email
Partager sur telegram

Dans cette 4ème partie du projet de station météo connectée, nous allons aborder le stockage des fichiers dans le système de fichier SPIFFS. Les mesures (température, humidité et pression atmosphérique) seront enregistrées au format JSON à l’aide de la librairie ArduinoJson dans un fichier stocké dans la zone de fichier SPIFFS de l’ESP8266.

 

Les données ainsi enregistrées serviront à alimenter un historique. Les mesures seront recharger au démarrage de l’ESP8266 pour poursuivre l’acquisition de données.

Petits rappels sur le format JSON avant de commencer

JSON est un format qui permet de structurer les données pour le stockage et le transfert. Il remplace très avantageusement le format XML dans le langage Javascript (entre autre) ou il est supporté nativement. Il est également très simple de créer une structure JSON dans le code Arduino.

Pour tout savoir sur le format JSON, je vous conseille de commencer par lire ce tutoriel détaillé

A LIRE AUSSI :
Débuter avec ArduinoJSON v6, librairie Arduino pour manipuler des objets JSON

Sans trop rentrer dans les détails, voici comment ça fonctionne :

  • Les données sont représentées par des paires clé:valeur
  • Le signe deux-points  sépare la clé de la valeur
  • Le signe guillemet  ”  permet d’encadrer une chaîne de caractères
  • les paires clé:valeur sont séparées par des virgules  (sauf la dernière ligne)
  • Les accolades  {}  contiennent des objets. On peut y stocker ce que l’on souhaite à condition de respecter le formaliste JSON ci-dessus
  • Les crochets  []  contiennent des tableaux

Ce qui donne par exemple

{
  "cle1":"valeur1",
  "cle2":"[1,2,3,4]",
  "cle3":{
    "clea":"valeura",
    "cleb":"['a','b','c']"
  }
}

Ou un tableau

[{
    "temperature": "18",
    "humidite": "52"
}, {
    "temperature": "19",
    "humidite": "52"
}]

Dernière remarque, le format JSON n’est rien d’autre qu’une chaîne de caractère, ce qui donne

[{"temperature":"18","humidite":"52"},{"temperature":"19","humidite":"52" }]

Super pratique pour envoyer des données ou des commandes à l’aide de messages MQTT ou Node-RED via le port série ! D’ailleurs, si le sujet vous intéresse, voici un projet complet

A LIRE AUSSI :
Débuter avec l'Arduino et Node-RED. Enregistrer et afficher des mesures en JSON depuis le port série

Pour tester vos JSON, je vous conseille jsonlint disponible en ligne et largement suffisant.

Installer la librairie ArduinoJson

Il est possible de créer une structure pour gérer les données mais le format JSON (déjà expliqué dans le tutoriel précédent) offre de nombreux avantages. La librairie ArduinoJson développée par Benoît Blanchon est un modèle du genre. Très bien documentée, elle est très simple de mise en oeuvre.

Seul bémol, manipuler des objets JSON n’est pas sans conséquences sur le programme Arduino. En effet, l’objet JSON est stocké en mémoire, ce qui limite grandement nos ambitions. Si vous avez besoin de créez un système d’acquisition de données, cette librairie n’est pas la mieux adaptée. Dans le cadre de ce projet, elle est très intéressante car il est très facile de manipuler les données et les envoyer vers l’interface Web (juste en 2 lignes de code !).

Pour tout savoir sur la version 6 de la librairie ArduinoJSON, je vous conseille de commencer par lire ce tutoriel détaillé

A LIRE AUSSI :
Débuter avec ArduinoJSON v6, librairie Arduino pour manipuler des objets JSON
Attention, le code a été développé avec la version 5.6.2 de la librairie ArduinoJson. Il n’est pas compatible avec la version 6.

Pour installer la librairie ArduinoJson, allez tout simplement dans le gestionnaire de bibliothèque.

arduinojson esp8266 json charger enregistrer spiffs historique acquisition donnees

Préparer un buffer et l’objet JSON

Ensuite, on ajoute une déclaration en début de programme

#include <ArduinoJson.h>

La librairie ArduinoJson est très bien documentée (le wiki sur trouve sur GitHub). Je vous propose un exemple d’utilisation typique dans lequel nous allons réaliser les manipulations suivantes :

  • Création d’un objet JSON
  • Ajout de clés : timestamp, t (température), h (humidité), pa (pression atmosphérique), bart et barh (données classifiées pour construire un histogramme en barre)
  • Exporter et enregistrer le JSON dans la zone SPIFFS
  • Recharger le fichier de données depuis la zone SPIFFS et recréer l’objet JSON correspondant

On commence donc par créer un Buffer qui va contenir l’objet JSON. Il est possible de laisser la librairie changer dynamiquement la taille du buffer, mais les performances en seront affectée. Pour déterminer la taille du buffer, reportez vous à cette page (section Memory Usage). Estimons la taille pour ce projet. On va enregistrer 12 mesures durant 7 heures, soit 84 points par grandeur physique.

  • 4 tableaux de 84 éléments (temps, température, pression atmosphérique) = 4 x ( 8 + 12 x 84 ) = 4064 bytes
  • 2 tableaux de 7 éléments (pour histogramme en barre) = 2 x ( 8 + 12 x 7 ) = 184 bytes

Soit un total de 4248 bytes. Par sécurité, on va arrondir à 4400 bytes. S’il manque un peu de place, tous les points de mesure ne pourront pas être enregistrés.

StaticJsonBuffer<44000> jsonBuffer;

Maintenant, on créé l’objet JSON root  dans ce buffer

JsonObject& root = jsonBuffer.createObject();

Puis on créé les clés qui vont contenir les tableaux de données.On a le choix entre ajouter un tableau (createNestedArray) ou un objet (createNestedObject). Ici, on ajoute 5 tableaux. La taille des tableaux n’est pas fixée d’avance, à nous à calculer l’espace nécessaire au moment de la création du buffer. S’il n’y a plus de place dans le buffer, il ne sera plus possible d’ajouter des données supplémentaires.

JsonArray& timestamp = root.createNestedArray("timestamp");
JsonArray& hist_t = root.createNestedArray("t");
JsonArray& hist_h = root.createNestedArray("h");
JsonArray& hist_pa = root.createNestedArray("pa");
JsonArray& bart = root.createNestedArray("bart");  
JsonArray& barh = root.createNestedArray("barh");

On en profite également pour créer un buffer qui servira aux exportations. Si on doit exporter les données (ce qui est généralement le cas), il faudra donc diviser la mémoire disponible en deux, la première partie pour le JSON, la seconde pour le tampon d’exportation.

char json[44000];

Manipuler un JSON dans du code Arduino. Ajout, modification, suppression de données

Maintenant que l’objet JSON est prêt, on va pouvoir ajouter des points régulièrement. Ici, on va stocker dans le JSON un point toutes les 5 minutes. On dispose de plusieurs fonctions pour manipuler les données dans le JSON :

  • add(data) : ajoute une nouvelle données à la suite. On pousse une nouvelle valeur dans un tableau par exemple.
  • removeAt(index) : on supprime la données à l’index indiqué.
  • set(index,data) : on actualise la données à l’index indiqué.

La fonction addPtToHist suivante permet d’ajouter un point de mesure pour chaque grandeur à intervalle régulier (ici intervalHist = 5000 ms). Le temps est récupéré sur le serveur NTP à l’aide de la méthode NTP.getTime() présentée dans le tutoriel précédent. On limite le nombre de chiffres significatifs pour les nombres décimaux à l’aide de la méthode double_with_n_digits(variable, nombre_chiffre_significatif).

Enfin, comme on est obligé de limiter le nombre de mesures enregistrées (ici 84 par variable), on doit supprimer la plus ancienne. Pour cela il suffit de tester la taille du tableau, si elle est supérieure à la taille définie, on supprime le premier élément de chaque tableau à l’aide de la méthode removeAt(index). Il est ainsi très facile de mettre en place un buffer tournant.

void addPtToHist(){
  unsigned long currentMillis = millis();
  if ( currentMillis - previousMillis > intervalHist ) {
    previousMillis = currentMillis;
    timestamp.add(NTP.getTime());
    hist_t.add(double_with_n_digits(t, 1));
    hist_h.add(double_with_n_digits(h, 1));
    hist_pa.add(double_with_n_digits(pa, 1));
    if ( hist_t.size() > sizeHist ) {
      //Serial.println("efface anciennes mesures");
      timestamp.removeAt(0);
      hist_t.removeAt(0);
      hist_h.removeAt(0);
      hist_pa.removeAt(0);
    } 
    saveHistory();
  }
}

D’autres fonctions utiles :

  • size() Retourne la taille d’un tableau (tableau.size())
  • success() Retourne vrai si un tableau a été correctement parsé (converti d’une chaine en objet JSON) ou alloué

Comment exporter l’objet JSON et l’enregistrer dans la zone SPIFFS de l’ESP8266

Vous avez du remarquer l’appel à la fonction saveHistory() dans la fonction précédente. Voyons comment faire pour exporter un objet JSON et l’enregistrer dans un simple fichier texte dans l’espace SPIFFS.

Petits rappels :

  • On appel la librairie FS.h en début de programme
  • Pour pouvoir accéder aux fichiers stockés dans la zone SPIFFS, il faut initialiser la librairie en appelant la fonction SPIFFS.begin() dans le setup().

Maintenant, on peut accéder au système de fichier SPIFFS.  Exporter le JSON dans un fichier texte, c’est trois lignes de code avec les librairies ArduinoJson et FS !.

  • On ouvre le fichier avec la méthode SPIFFS.open(nom_fichier, option). Ici on va accéder au fichier en écriture w. Si le fichier n’existe pas, il sera créé.
  • La méthode printTo permet de sérialiser(serialize), c’est à dire de transformer l’objet JSON en une chaine de caractère. On indique la sortie de la fonction, ici le fichier ouvert (historyFile).
  • On referme le fichier (close).
  • C’est terminé.
void saveHistory(){          
  File historyFile = SPIFFS.open(HISTORY_FILE, "w");
  root.printTo(historyFile); // Exporte et enregistre le JSON dans la zone SPIFFS - Export and save JSON object to SPIFFS area
  historyFile.close();  
}

La méthode printTo est très pratique, elle permet donc de sérialiser le JSON et de l’envoyer aussi bien vers un fichier que vers le port série pour la mise au point du programme. Il existe également la variante prettyPrintTo qui permet de rendre plus lisible (pour un humain !) l’objet JSON comme le fait http://jsonlint.com/.

Importer et parser un fichier texte en JSON (historique de mesure)

On va recharger l’historique sauvegardé précédemment au démarrage de l’ESP8266 et poursuivre l’acquisition de données. Pour parser une chaine de caractères en objet JSON, on doit disposer d’un buffer dont la taille correspond à l’objet JSON final. Comme on va recharger l’historique courant, on dispose déjà du buffer root. Inutile d’en créer un nouveau. On ouvre le fichier avec la méthode SPIFFS.open mais cette fois en lecture uniquement (paramètre r). Avant de faire la conversion, il est préférable de tester si le fichier n’est pas vide (file.size()).

On charge le contenu du fichier dans un buffer (source) temporaire (buf). On appel ensuite la méthode jsonBuffer.paseObject en lui passant en paramètre le contenu du buffer (buf.get()).

Attention. Il faudra assez de mémoire pour recharger le JSON.

La méthode success permet de vérifier que l’objet JSON est correct.

void loadHistory(){
  File file = SPIFFS.open(HISTORY_FILE, "r");
  if (!file){
    Serial.println("Aucun historique existe - No History Exist");
  } else {
    size_t size = file.size();
    if ( size == 0 ) {
      Serial.println("Fichier historique vide - History file empty !");
    } else {
      std::unique_ptr<char[]> buf (new char[size]);
      file.readBytes(buf.get(), size);
      JsonObject& root = jsonBuffer.parseObject(buf.get());
      if (!root.success()) {
        Serial.println("Impossible de lire le JSON - Impossible to read JSON file");
      } else {
        Serial.println("Historique charge - History loaded");
        root.printTo(Serial);  
      }
    }
    file.close();
  }
}

Dans le prochain tutoriel nous verrons comment préparer une réponse à partir d’éléments extraits du JSON avant de l’envoyer à l’interface WEB.

Exemples utilisés pour l’écriture de l’article

JSON :  https://github.com/bblanchon/ArduinoJson/wiki
SPIFFS : https://github.com/esp8266/Arduino/issues/1685
SPIFFS : https://github.com/esp8266/Arduino/blob/master/libraries/esp8266/examples/ConfigFile/ConfigFile.ino

Accéder rapidement aux autres parties du projet

Voici les liens pour accéder aux autres parties du projet

A LIRE AUSSI :
Projet station météo ESP8266 (Partie 1). Créer l'interface HTML, stockage SPIFFS
A LIRE AUSSI :
Projet station météo ESP8266 (Partie 2). Piloter le code Arduino depuis l'interface HTML
A LIRE AUSSI :
Projet station météo ESP8266 (Partie 3). Récupérer l'heure avec NTPClient et stockage SPIFFS

Vous êtes ici

A LIRE AUSSI :
Projet station météo ESP8266 (Partie 4). ArduinoJson, charger, enregistrer des fichiers (SPIFFS)
A LIRE AUSSI :
Projet station météo ESP8266 (Partie 5). Afficher des jauges et graphiques Google Charts
Avez-vous aimé cet article ?
[Total: 0 Moyenne: 0]
Partager sur facebook
Partager sur twitter
Partager sur linkedin
Partager sur pinterest
Partager sur email
Partager sur telegram

Vous avez aimé ce projet ? Ne manquez plus aucun projet en vous abonnant à notre lettre d’information hebdomadaire!

quel modèle esp8266 choisir
Quel modèle d'ESP8266EX choisir en 2020 ?
guide choix esp32 development board
Quel ESP32 choisir en 2020 ?

Vous rencontrez un problème avec ce sujet ?

Peut-être que quelqu’un a déjà trouvé la solution, visitez le forum avant de poser votre question

6 commentaires sur l'article "Projet station météo ESP8266 (Partie 4). ArduinoJson, charger, enregistrer des fichiers (SPIFFS)"
  1. Bonjour,
    Je charge correctement le fichier lors d’un reboot. Par contre, à l’enregistrement de la première mesure, le fichier history.json est réinitialisé: je n’ai plus qu’une seule mesure dans le fichier.
    Comment faire pour rajouter les mesures à la suite du fichier même en cas de reboot de l’ESP ?

    • Bonsoir Julien. Il doit y avoir une booléen dans le code qui commande la réinitialisation du fichier de données au démarrage de l’ESP. Pourriez-vous m’envoyer le code pour analyse ? Bonne soirée

      • Bonjour,
        Apparemment mon code a été considéré comme SPAM 🙂
        J’utilise exactement mot pour mot votre code. N’avez vous pas le même fonctionnement ?
        J’initialise l’objet JSON :

        https://uploads.disquscdn.com/images/e91141baa6d67e9e048a345034f90be30b39133e5cb31e5931b5aebfc8103ad1.png

        J’ appel la fonction loadHistory dans la boucle SETUP
        https://uploads.disquscdn.com/images/9cd121e0089b52fadc8a3a89a9c526c43103a627092c013220528869fec826aa.png

        Le fichier est bien chargé et le JSON s’affiche dans la console.

        Par contre si je demande à afficher le JSON (avec root.prettyPrintTo(Serial); ) dans la boucle LOOP, celui-ci est vide.

        Pour info, au premier appel de saveHistory, le JSON s’affiche bien dans la boucle LOOP.

        Merci de votre aide, j’avoue être vraiment bloqué…

        • Bonjour. De mon côté, même problème en ayant réutilisé le code tel quel.
          Je n’ai pas encore regardé dans le détail l’algorithme car je me suis contenté de recopier pour l’instant…

      • Bonjour,

        Merci pour votre réponse, j’utilise le code que vous avez donné dans ce tuto:

        
        #include 
        #include 
        #include 
        #include        // Gestion de la zone SPIFFS
        #include 
        #include 
        #include 
        
        #define ssid      "####"                  // WiFi SSID
        #define password  "####" // WiFi password
        
        #define HISTORY_FILE "/history.json"
        int         sizeHist = 10 ;                   // Taille historique
        const long  intervalHist = 1000 * 60 ;     
        unsigned long previousMillis = intervalHist;  // Dernier point enregistré dans l'historique
        
        // Data wire is plugged into pin D1 on the ESP8266 12-E - GPIO 5
        #define ONE_WIRE_BUS 5
        
        // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
        OneWire oneWire(ONE_WIRE_BUS);
        
        // Pass our oneWire reference to Dallas Temperature. 
        DallasTemperature DS18B20(&oneWire);
        float   tempC = 0 ;
        
        StaticJsonBuffer jsonBuffer;                 // Buffer static contenant le JSON courant
        JsonObject& root = jsonBuffer.createObject();
        JsonArray& timestamp = root.createNestedArray("timestamp");
        JsonArray& hist_t = root.createNestedArray("t");
        char json[10000];                                   // Buffer pour export du JSON
        
        /* --------------------   FONCTIONS   -------------------- */
        void getTemperature() {
          do {
            DS18B20.requestTemperatures(); 
            tempC = DS18B20.getTempCByIndex(0); 
            delay(100);
          } while (tempC == 85.0 || tempC == (-127.0));
        }
        
        void loadHistory(){
          File file = SPIFFS.open(HISTORY_FILE, "r");
          if (!file){
            Serial.println("No History file");
          } else {
            size_t size = file.size();
            if ( size == 0 ) {
              Serial.println("History file empty !");
            } else {
              // On charge le contenu du fichier dans un buffer temporaire (buf)
              std::unique_ptr buf (new char[size]);
              file.readBytes(buf.get(), size);
              JsonObject& root = jsonBuffer.parseObject(buf.get());
              if (!root.success()) { // La méthode success permet de vérifier que l’objet JSON est correct.
                Serial.println("Impossible to read JSON file");
              } else {
                Serial.println("History loaded");
                root.prettyPrintTo(Serial);  //Affichage du JSON dans la console
              }
            }
            file.close();
          }
        }
        
        void addPtToHist(){
          unsigned long currentMillis = millis();
          
          Serial.println(currentMillis - previousMillis);
          if ( currentMillis - previousMillis > intervalHist ) {
            long int tps = NTP.getTime();
            previousMillis = currentMillis;
            //Serial.println(NTP.getTime());
            if ( tps > 0 ) {
              timestamp.add(tps);
              hist_t.add(tempC);
        
              //root.printTo(Serial);
              if ( hist_t.size() > sizeHist ) {
                //Serial.println("Efface les anciennes mesures");
                timestamp.removeAt(0);
                hist_t.removeAt(0);
              }
              //Serial.print("size hist_t ");Serial.println(hist_t.size());
              //calcStat();
              delay(100);
              /* Sauvegarde dans le fichier historyFile */
              File historyFile = SPIFFS.open(HISTORY_FILE, "w");
              root.printTo(historyFile); // Exporte et enregsitre le JSON dans la zone SPIFFS
              historyFile.close();  
              
              //root.printTo(Serial);  
            }  
          }
        }
        
        /* --------------------   INITIALISATION PROGRAMME   -------------------- */
        void setup() {
          /* Fonction CALLBAK 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()));
            }
          });
          NTP.begin("pool.ntp.org", 1, true);   //URL, +1h (fuseau horaire), Heure d'été activé)
          NTP.setInterval(3600000);             // Interval de rafraichissement en milli-seconde
          delay(500);
           
          Serial.begin ( 115200 );
        
          DS18B20.begin(); // IC Default 9 bit. If you have troubles consider upping it 12.
        
          /* Connexion WIFI */
          WiFi.begin ( ssid, password );
          int tentativeWiFi = 0;
          while ( WiFi.status() != WL_CONNECTED ) { // Attente de la connexion au réseau WiFi 
            delay ( 500 ); Serial.print ( "." );
            tentativeWiFi++;
            if ( tentativeWiFi > 20 ) {
              ESP.reset();
              while(true)
                delay(1);
            }
          }
          // Connexion WiFi établie
          Serial.println ( "" );
          Serial.print ( "Connected to " ); Serial.println ( ssid );
          Serial.print ( "IP address: " ); Serial.println ( WiFi.localIP() );
        
          /* Accès à la zone SPIFFS */
          if (!SPIFFS.begin()) {
            Serial.println("SPIFFS Mount failed");  // Problème avec le stockage SPIFFS 
          } else { 
            Serial.println("SPIFFS Mount succesfull");
            loadHistory();
          }
          delay(50);
          
          
        }
        
        /* --------------------   BOUCLE PROGRAMME   -------------------- */
        void loop() {
          //NTP.getTime();
          //Serial.print ( "Puissance signal: " ); Serial.print(WiFi.RSSI()); Serial.println ( " dBm" );
          getTemperature();
          Serial.print("temperature:");
          Serial.println(tempC);
          
          //addPtToHist();
          //root.printTo(Serial);
          delay(1000);
        }
        

        Cela ne se passe pas au redémarrage, le fichier est bien chargé dans le setup. C’est lorsque j’écris dedans que le fichier s’efface. Il semblerait que l’objet JSON ne passe pas dans JsonObject& root ???

  2. Bonjour,
    Grace à votre site j’ apprend beaucoup sur l’ESP 8266. un grand merci.
    Sur cette partie du projet, la compilation du code arduino ne se fait pas car il me signale l’erreur suivante:’HISTORY_FILE’ was not declared in this scope. Merci de m’indiquer comment y remédier .

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

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