LES SATELLITES AUTONOMES : une nouvelle approche du concept de Satellites Locoduino. (5)

Le logiciel (1/5) - Présentation générale des différents fichiers, le fichier config, les fichiers settings et le fichier main.

. Par : bobyAndCo. URL : https://www.locoduino.org/spip.php?article352

Voyons maintenant dans le moteur, c’est à dire les fichiers et le programme qui fait fonctionner l’ensemble.

Notez que vous pouvez utiliser vos satellites en ignorant totalement cette page (*) à l’exception de la section concernant le fichier "Settings.h" où vous devrez renseigner le SSID de votre réseau internet et le mot de passe de ce réseau.

(*) et toutes les articles suivants concernant le code.

En préambule, observons l’utilisation des « tâches » dans l’ensemble du programme. FreeRTOS, le système d’exploitation de l’ESP32, autorise l’exécution de plusieurs tâches simultanément. (*1)

Les tâches permettent aussi l’utilisation du second cœur de l’ESP32 pour, en principe, doubler la capacité de calcul.

L’intérêt des tâches est de pouvoir exécuter différentes opérations en « quasi » parallèle et de ne pas avoir à attendre qu’une tâche soit terminée pour en exécuter une autre. L’intérêt est aussi que l’on peut suspendre une tâche pour un laps de temps donné sans bloquer les autres tâches. Ainsi, le cycle d’exécution de certaines tâches est répété toutes les deux secondes par exemple alors que d’autres tâches vont s’exécuter à des fréquences qui peuvent être de 100 millisecondes.

Au travers de FreeRTOS, nous allons pouvoir programmer certaines tâches pour s’exécuter sur un cœur du microcontrôleur ou sur l’autre. Il faut rester conscient que la puissance de calcul est limitée mais que, par une gestion assez précise de chaque tâche, on peut améliorer le résultat global.

Une autre fonctionnalité de FreeRTOS peut contribuer à cet objectif, la gestion des priorités des tâches. Selon la priorité que l’on donnera à certaines tâches et en fonction de la puissance de calcul, le système donnera priorité à l’exécution de certaines tâches par rapport à d’autres.

L’utilisation d’un tel mécanisme nécessite un peu de doigté et un certain nombre des tests car certaines tâches (moins prioritaires) peuvent purement et simplement n’être jamais exécutées.

Voici la liste des différents fichiers dans lesquels sont réparties les différentes parties du programme. :

PNG - 139.6 kio
Liste des fichiers de l’ensemble de l’application.

Le cœur du programme est contenu dans les fichiers Node.h et Node.cpp. J’ai choisi Node (nœud en français) car le satellite autonome est avant tout un nœud dans une chaine, un ensemble d’autres satellites avec lesquels il échange et collabore.

Node est un peu le « cœur du satellite ». Les membres de la classe Node sont en quelque sorte la représentation logicielle des objets physiques du canton. Ce même fichier contient une autre classe NodePeriph qui elle "symbolise" de manière simplifiée les satellites "voisins". L’instance de la classe Node initialise huit instances de NodePeriph, nombre maximal de satellites "voisins" possible avec cette version.

Les fichiers Aig, Loco, Sensor, Signal sont les éléments logiciels qui figurent ces objets physiques (Aiguilles, locomotives, capteurs, signaux lumineux) et les méthodes associées à ces objets, les actions de ces objets physiques.

Railcom est similaire en ce sens qu’il apporte des informations sur l’éventuelle présence d’une locomotive sur le canton en nous précisant l’adresse de cette locomotive.

Settings contient tout ce qui est nécessaire au paramétrage du satellite. C’est par l’intermédiaire des fonctions contenues dans ce fichier que l’on lit ou que l’on sauvegarde le fichier json où sont regroupées les données de paramétrage.

Discovery contient tous les éléments logiciels qui permettent le processus de découverte présenté précédemment. Discovery est activé par défaut au premier lancement de la carte satellite. Une fois la configuration globale réalisée, on doit désactiver le mode Discovery pour activer GestionReseau qui maintenant restera activé en permanence jusqu’à ce que l’on active à nouveau Discovery pour une extension par exemple.

GestionReseau contient toutes les fonctions qui vont analyser tous les messages CAN reçus par le satellite. Je rappelle que chaque satellite envoie toutes les 100 millisecondes environ les principales données le concernant : présence d’une locomotive et adresse de cette locomotive, position de ses aiguille, cantons à partir desquels il est possible d’accéder.

Nous verrons plus tard en détail comment chaque satellite traite les données reçues des autres pour adapter son propre comportement.

Enfin, le fichier main.cpp qui correspond au fichier .ino sous l’IDE Arduino. C’est le premier fichier appelé au lancement du microcontrôleur. On y trouve principalement les instances des objets logiciels et aussi l’activation des méthodes de ces différents objets logiciels. (*2)

Je ne m’attarderai pas sur les fichiers Wifi, WebHandler, Config ni même CAN qui ne sont que des programmes « utilitaires » permettant le fonctionnement de l’ensemble.

Voilà pour la présentation globale des fichiers du programme. Nous verrons dans les articles suivants le fonctionnement détaillé des principaux d’entre eux.

Dossier data et fichiers contenus :

A ces fichiers de programme s’ajoutent les fichiers du dossier data, fichiers utilisés par l’application web et le fichier de sauvegarde de la configuration, settings.json. J’ai aussi copié la bibliothèque graphique w3.css qui fournit les styles de la page pour éviter d’avoir à charger cette bibliothèque avec une liaison internet.

PNG - 59 kio
Contenu du dossier "data" qui est enregistré en mémoire flash de l’ESP32.

Le fichier index.html est la page web en elle-même, le fichier scripts.js l’ensemble du code en javascript qui permet entre autres choses la communication entre le programme de l’ESP32 et la page web (champs de valeurs et boutons).

On trouve aussi les images qui vont servir à illustrer la signalisation : cible_0.jpg à cible_3.jpg

La page web permet de visualiser différentes valeurs et également permettre d’opérer certains réglages comme nous avons pu le voir dans le précédent article.

JPEG - 64.2 kio
JPEG - 173.2 kio
Contenu du fichier "Settings.json".

Le fichier Settings.json regroupe un certain nombre de valeurs dont l’application a besoin. Ces informations sont stockées au format json et appelées à chaque démarrage de la carte (ou mises à jour par l’intermédiaire du bouton sauvegarde de la page web). La vue ci-dessus représente le fichier avant la première utilisation.

La première chose à réaliser, et en fait la seule chose à faire, est de renseigner les lignes 4 et 5 les valeurs pour le SSID de votre réseau WiFi et le mot de passe du réseau. On le fait une seule fois et cela sera recopié définitivement sur toutes les cartes.

On remarque que l’ID en ligne 2 a pour valeur 255, ce qui représente la valeur par défaut de tout satellite non initialisé.

En ligne 3 et en ligne 5 les valeurs du mode WiFi et Discovery sont « vraies » par défaut. Ces valeurs sont modifiées ensuite en se servant de la page web de configuration.

Tous les autres champs sont automatiquement renseignés par le programme.

Les fichiers du programme :

Voyons maintenant les fichiers du programme à proprement parler.

Le fichier Config.h regroupe les informations de configuration statiques (contrairement à settings.json pour les données dynamiques).

Vous n’avez rien à modifier dans ce fichier (à moins que vous sachiez ce que vous faites).

/*

  Config.h

  Sur les cartes qui utilisent un module ESP32-WROVER pour avoir plus de RAM,
  les pins GPIO16 et GPIO17 ne sont pas disponibles car ils sont utilisés en interne par la PSRAM.  

  ESP32 datasheet : https://www.espressif.com/sites/default/files/documentation/esp32-wrover-e_esp32-wrover-ie_datasheet_en.pdf

  Pin mapping pour cette application : https://www.locoduino.org/IMG/png/pin_mapping_v7.png
  
*/

#ifndef __CONFIG__
#define __CONFIG__

#include <Arduino.h>

enum : uint8_t // Index des satellites périphériques
{
  p00,
  p01,
  p10,
  p11,
  m00,
  m01,
  m10,
  m11
};

/* ----- Options   -------------------*/
#define SAUV_BY_MAIN  // Sauvegardes des paramètres commandées par la carte Main
#define CHIP_INFO
#define RAILCOM
//#define RFID

/* ----- Debug   -------------------*/
#define DEBUG
#ifdef DEBUG
#define debug Serial
#endif

//#define TEST_MEMORY_TASK

/* ---------------------------------*/
#define NO_ID 255
#define NO_PIN 255

/* ----- CAN ----------------------*/
#define CAN_RX GPIO_NUM_22
#define CAN_TX GPIO_NUM_23
#define CAN_BITRATE 1000UL * 1000UL // 1 Mb/s

/* ----- Node ----------------------*/
const uint8_t nodePsize = 8;
const uint8_t aigSize = 6;
const uint8_t sensorSize = 2;
const uint8_t signalSize = 2;

/* ----- Railcom -------------------*/
#define NB_ADDRESS_TO_COMPARE 10 // Nombre de valeurs à comparer pour obtenir l'adresse de la loco
#ifdef RAILCOM
#define RAILCOM_RX GPIO_NUM_0
#define RAILCOM_TX GPIO_NUM_17
#endif

/* ----- Sensors ------------------*/
#define CAPT_PONCT_ANTIHOR_PIN GPIO_NUM_12
#define CAPT_PONCT_HORAIRE_PIN GPIO_NUM_15
#define CAPT_PONCT_TEMPO 10UL

/* ----- Mesure de courant ---------*/
#define MESURE_COURANT GPIO_NUM_33 // ADC1 canal 5

/* ----- Registres a decalage ------*/
#define SHREG_PIN_VERROU GPIO_NUM_4
#define SHREG_PIN_HORLOGE GPIO_NUM_5
#define SHREG_PIN_DATA GPIO_NUM_18

/* ----- Découverte ---------------*/
#define INTER_DEV_1 GPIO_NUM_34    // Broche du dip switch pour inter1 dévié
#define INTER_DEV_2 GPIO_NUM_39    // Broche du dip switch pour inter2 dévié
#define BTN_SAT_PLUS GPIO_NUM_36   // Bouton de validation
#define BTN_SAT_MOINS GPIO_NUM_35  // Bouton de validation
#define LED_PIN_DISCOV GPIO_NUM_32 // Led

/* ----- Aiguilles -----------------*/
#define AIG_PIN_SIGNAL_0 GPIO_NUM_19
#define AIG_PIN_SIGNAL_1 GPIO_NUM_21
#define AIG_PIN_SIGNAL_2 GPIO_NUM_2
#define AIG_PIN_SIGNAL_3 GPIO_NUM_13
#define AIG_SPEED 6000.0000

#endif

Le fichier main.cpp désigne en c++ le fichier que l’application exécute en premier. C’est ce nom qui est utilisé avec l’environnement de développement PlateformIO.

Si vous utilisez l’IDE Arduino, ce fichier devra être renommé par exemple satAutonomeClient.ino, le même nom que le dossier de niveau supérieur dans le répertoire. Ne pas oublier l’extension .ino avec cet environnement de développement en remplacement de .cpp.

On trouve tout d’abord tout un ensemble de fichier que le compilateur a besoin de connaitre pour relier tous les fichiers entre eux ainsi que les bibliothèques utilisées.

//--- Fichiers inclus
#include <Arduino.h>
#include "CanMsg.h"
#include "CanConfig.h"
#include "Config.h"
#ifdef CHIP_INFO
#include "ChipInfo.h"
#endif
#include "Discovery.h"
#include "GestionReseau.h"
#include "Node.h"
#include "Railcom.h"
#include "Settings.h"
#include "SignauxCmd.h"
#include "WebHandler.h"
#include "Wifi_fl.h"
#include "freertos/queue.h"

Sous PlateformIO, il est possible de renseigner les bibliothèques nécessaire à l’aide du fichier plateformeio.ini dans la variable containeur lib_deps.

lib_deps = 
	me-no-dev/AsyncTCP @ ^1.1.1
	https://github.com/me-no-dev/ESPAsyncWebServer.git
	pierremolinaro/ACAN_ESP32@=1.1.2
	bblanchon/ArduinoJson@^6.20.0
	locoduino/RingBuffer@^1.0.5
	roboticsbrno/ServoESP32@=1.0.3 ; ->bug avec la v1.1.1

Contrairement à l’IDE Arduino qui partage ses bibliothèques entre les différents projets, il possible avec PlateformIO d’associer à un projet les bibliothèques utilisées. Elles sont purement et simplement copiées dans un dossier caché du projet évitant ainsi des conflits. Ces bibliothèques sont régulièrement mises à jour par PlateformIO si vous lui demandez par un subtil jeu de code à la fin de chaque ligne comme : @^1.0.3

C’est vraiment très pratique car cela permet aussi d’empêcher la mise à jour avec une nouvelle version quand il y a une incompatibilité avec votre programme. C’est le cas par exemple avec cette bibliothèque dont j’interdit la mise jour au-delà de la version 1.0.3 : roboticsbrno/ServoESP32@=1.0.3 ; ->bug avec la v1.1.1

De même concernant la bibliothèque ACAN_ESP32 pour laquelle j’ai constaté des incompatibilités avec certains ESP32 "anciens". J’ai préféré ne pas prendre de risque en "bridant" une version qui fonctionne bien : pierremolinaro/ACAN_ESP32@=1.1.2

Revenons au fichier main.cpp. Nous réalisons ensuite un certain nombre d’instances de classes. Respectivement de la classe Node, Railcom, éventuellement RFID, puis des classes Fl_Wifi et WebHandler.

// Instances
Node *node = new Node();
Railcom railcom(RAILCOM_RX, RAILCOM_TX);
Fl_Wifi wifi;
WebHandler webHandler;

Les objets crées au travers de ces instances seront utilisés tout au long de l’exécution du programme.

La fonction setup() est assez classique, je ne m’étendrai pas dessus sauf pour préciser que c’est ici que sont démarrées un certain nombre de tâches qui vont ensuite se dérouler sans plus s’arrêter. Nous en reparlerons au fur et à mesure. Ici non plus vous n’avez à faire de modifications de programme.

/*-------------------------------------------------------------
                           setup
--------------------------------------------------------------*/

void setup()
{
  Serial.begin(115200);
  while (!Serial)
    ;
  delay(100);

//--- Infos ESP32 (desactivable)
#ifdef CHIP_INFO
  ChipInfo::print();
#endif

  Serial.printf("\nProject   :    %s", PROJECT);
  Serial.printf("\nVersion   :    %s", VERSION);
  Serial.printf("\nAuteur    :    %s", AUTHOR);
  Serial.printf("\nFichier   :    %s", __FILE__);
  Serial.printf("\nCompiled  :    %s", __DATE__);
  Serial.printf(" - %s\n\n", __TIME__);
  Serial.printf("-----------------------------------\n\n");

  Settings::setup(node);
  vTaskDelay(pdMS_TO_TICKS(100));
  //--- Configure ESP32 CAN
  CanConfig::setup();
  vTaskDelay(pdMS_TO_TICKS(100));
  CanMsg::setup(node);
  vTaskDelay(pdMS_TO_TICKS(100));

  bool err = 0;
  if (err == Settings::begin())
  {
    Serial.printf("-----------------------------------\n");
    Serial.printf("ID Node : %d\n", node->ID());
    Serial.printf("-----------------------------------\n\n");
  }
  else
  {
    Serial.printf("[Settings] : Echec de la configuration\n");
    return;
  }

  Serial.printf(Settings::discoveryOn() ? "[Discovery] : on\n\n" : "[Discovery] : off\n\n");
  Serial.printf("-----------------------------------\n\n");

  if (Settings::discoveryOn()) // Si option validee, lancement de la méthode pour le procecuss de decouverte
  {
    //--- Wifi et web serveur
    if (Settings::wifiOn()) // Si option validee
    {
      wifi.start();
      webHandler.init(node, 80);
    }
    Discovery::begin(node);
  }
  else
  {
    for (byte i = 0; i < signalSize; i++)
    {
      if (node->signal[i] == nullptr)
        node->signal[i] = new Signal;
      node->signal[i]->setup();
    }

    railcom.begin();
    SignauxCmd::setup();
    GestionReseau::setup(node);

    Settings::wifiOn(false);
    Serial.end(); // Desactivation de Serial en exploitation
  }
  Serial.printf(Settings::wifiOn() ? "[Wifi] : on\n" : "Wifi : off\n");
  Serial.printf("[Main %d] : End setup\n\n", __LINE__);
#ifndef debug
  Serial.end(); // Desactivation de Serial
#endif
} // ->End setup

Durant cette phase de setup(), un certain nombre d’informations seront retournées sur le moniteur série qui nous renseignent sur la bonne exécution du programme au cours de cette première phase jusqu’à l’apparition de End setup qui nous informe que tout c’est bien déroulé.

PNG - 390.9 kio

Avec Infos ESP32, vous vous assurerez que votre ESP32 possède bien les propriétés requises : 240 MHz pour les CPU, 2 cœurs, au moins 4 MB de mémoire flash.

Comme nous l’avons déjà vu, c’est ici également que sera renseigné l’adresse IP du satellite.

Pour la fonction loop(), nous vérifions si le paramètre wifiOn du fichier Settings est vrai ou faux. Dans ce dernier cas, on n’exécute pas les méthodes liées au serveur web qui ne sera de toutes façons pas opérationnel. On économise ainsi de la puissance de calcul autrement utilisée par le serveur web et le wifi.

En exploitation, la fonction loop() ne servira qu’à la mise à jour de l’adresse de la locomotive au travers de Railcom©.

/*-------------------------------------------------------------
                           loop
--------------------------------------------------------------*/

void loop()
{
  //******************** Ecouteur page web **********************************

  if (Settings::wifiOn()) // Si option validée
    webHandler.loop();    // ecoute des ports web 80 et 81

  if (!Settings::discoveryOn()) // Si option non validée
  {
    //************************* Railcom ****************************************

    if (railcom.address())
    {
      node->busy(true);
      node->loco.address(railcom.address());
#ifdef debug
      debug.printf("[Main %d ] Railcom - Numero de loco : %d\n", __LINE__, node->loco.address());
      debug.printf("[main %d ] Railcom - this node busy : %d\n", __LINE__, node->busy());
#endif
    }
    else
    {
      node->loco.address(0);
#ifdef debug
      debug.printf("[Main %d ] Railcom - Pas de loco.\n", __LINE__);
#endif
    }
    //**************************************************************************
  }
  vTaskDelay(pdMS_TO_TICKS(100));
} // ->End loop

Intéressons nous maintenant au fichier Settings et en particulier à la fonction Settings::setup(node) qui sera exécutée en tout premier dans le setup(). C’est une méthode static qui s’exécute sans qu’il y ait besoin d’instancier un objet. On lui passe en paramètre l’instance node de la classe Node. Ou plus exactement un pointeur sur l’adresse de l’objet en mémoire puisque qu’il a été instancié avec new : Node *node = new Node();

L’instance, l’objet node représente virtuellement le satellite physique avec ses extensions, capteurs, signaux, aiguilles...

La fonction Settings::setup() est la première exécutée qui cherche à se connecter à la mémoire flash pour lire les fichiers qui y sont stockés.

/*-------------------------------------------------------------
                           setup
--------------------------------------------------------------*/

void Settings::setup(Node *nd)
{
  node = nd;
  if (!SPIFFS.begin(true))
  {
    debug.printf("[Settings %d] : An Error has occurred while mounting SPIFFS\n\n", __LINE__);
    return;
  }
  else
    debug.printf("[Settings %d] : SPIFFS ok mounting\n", __LINE__);
  readFile();
}

C’est ensuite la fonction readFile() qui a en charge d’ouvrir le fichier settings.json et de copier chacune des valeurs qui y sont contenues dans les attributs de l’objet node. Le satellite est automatiquement re configuré avec les paramètres enregistrés lors de la dernière sauvegarde.

/*-------------------------------------------------------------
                           readFile
--------------------------------------------------------------*/

void Settings::readFile()
{
  File file = SPIFFS.open("/settings.json", "r");
  if (!file)
  {
    debug.printf("[Settings %d] : Failed to open settings.json\n\n", __LINE__);
    return;
  }
  else
  {
    debug.printf("\nInformations du fichier \"settings.json\" : \n\n");
    DynamicJsonDocument doc(4 * 1024);
    DeserializationError error = deserializeJson(doc, file);
    vTaskDelay(pdMS_TO_TICKS(100));
    if (error)
      debug.printf("[Settings %d] Failed to read file, using default configuration\n\n", __LINE__);
    else
    {
      // ---
      node->ID(doc["idNode"] | NO_ID);

      debug.printf("- ID node : %d\n", node->ID());

      Discovery::comptAig(doc["comptAig"]);
      node->masqueAig(doc["masqueAig"]);
      WIFI_ON = doc["wifi_on"];
      DISCOVERY_ON = doc["discovery_on"];
      ssid_str = doc["ssid"].as<String>();
      password_str = doc["password"].as<String>();
      strcpy(ssid, ssid_str.c_str());
      strcpy(password, password_str.c_str());
      node->maxSpeed(doc["maxSpeed"]);
      node->sensMarche(doc["sensMarche"]);

      // Nœuds
      const char *index[] = {"p00", "p01", "p10", "p11", "m00", "m01", "m10", "m11"};
      for (byte i = 0; i < nodePsize; i++)
      {
        if (doc[index[i]] != "null")
        {
          if (node->nodeP[i] == nullptr)
            node->nodeP[i] = new NodePeriph;
          node->nodeP[i]->ID(doc[index[i]]);
          debug.printf("- node->nodeP[%s]->id : %d\n", index[i], node->nodeP[i]->ID());
        }
        else
          debug.printf("- node->nodeP[%s]->id : NULL\n", index[i]);
      }
      debug.printf("---------------------------------\n");

      // Aiguilles
      for (byte i = 0; i < aigSize; i++)
      {
        // debug.printf("valeur de aig %d : %s%c\n", i, doc["aig" + String(i)]);
        if (doc["aig" + String(i)] != "null")
        {
          if (node->aig[i] == nullptr)
            node->aig[i] = new Aig;
          node->aig[i]->ID(doc["aig" + String(i) + "id"]);
          node->aig[i]->posDroit(doc["aig" + String(i) + "posDroit"]);
          node->aig[i]->posDevie(doc["aig" + String(i) + "posDevie"]);
          node->aig[i]->speed(doc["aig" + String(i) + "speed"]);
          node->aig[i]->pin(doc["aig" + String(i) + "pin"]);
          node->aig[i]->setup();
          debug.printf("- Creation de l'aiguille %d\n", i);
        }
      }
      debug.printf("---------------------------------\n");

      // Signaux
      for (byte i = 0; i < signalSize; i++)
      {
        if (doc["sign" + String(i)] != "null")
        {
          if (node->signal[i] == nullptr)
            node->signal[i] = new Signal;
          node->signal[i]->type(doc["sign" + String(i) + "type"]);
          node->signal[i]->position(doc["sign" + String(i) + "position"]);
          debug.printf("- Creation du signal %d\n", i);
        }
      }
      debug.printf("---------------------------------\n");
    }
    file.close();
  }
} //--- End readFile

Cela nous permet de renseigner les identifiants des satellites périphériques, les aiguilles présentes avec tous leurs attributs et d’autres informations comme le mot de passe wifi.

En tout premier lieu, cette fonction lit la valeur du l’identifiant de la carte qui est enregistré dans le fichier settings.json. Si l’ID est 255, cela veut dire que la carte n’est pas initialisée.

Dans ce cas, la fonction Settings::begin() va interroger la carte Main qui lui retourne un identifiant que la carte va maintenant conserver de façon définitive.

Notez que le programme attend une réponse de la carte Main avant de poursuivre le programme.

/*-------------------------------------------------------------
                           begin
--------------------------------------------------------------*/

bool Settings::begin()
{
  //--- Test de la présence de la carte Main
  while (!isMainReady)
  {
    CanMsg::sendMsg(0, 0xB2, node->ID(), 254, 0);
    if (!isMainReady)
      debug.printf("[Settings %d] : Attente de reponse en provenance de la carte Main.\n", __LINE__);
    vTaskDelay(pdMS_TO_TICKS(100));
  }

  //--- Identifiant du Node
  while (node->ID() == NO_ID) // L'identifiant n'est pas en mémoire
  {
    //--- Requete identifiant
    debug.printf("[Settings %d] : Le satellite ne possede pas d'identifiant.\n", __LINE__);
    CanMsg::sendMsg(0, 0xB4, node->ID(), 254, 0);
    vTaskDelay(pdMS_TO_TICKS(100));
  }

  writeFile();

  debug.printf("[Settings %d] : End settings\n", __LINE__);
  debug.printf("-----------------------------------\n\n");

  return 0;
} //--- End begin

Une fois l’identifiant obtenu, il est aussitôt sauvegardé dans le fichier settings.json au travers de la fonction writeFile();. On peut dire de la fonction writeFile() qu’elle fait un peu en sens inverse ce que fait readFile() ! Elle permet la sauvegarde en mémoire flash des paramètres du satellite.

/*-------------------------------------------------------------
                           writeFile
--------------------------------------------------------------*/

void Settings::writeFile()
{
  File file = SPIFFS.open("/settings.json", "w");
  if (!file)
  {
#ifdef DEBUG
    debug.println("Failed to open settings.json\n\n");
#endif
    return;
  }
  else
  {
    DynamicJsonDocument doc(4 * 1024);

    doc["idNode"] = node->ID();
    doc["comptAig"] = Discovery::comptAig();
    doc["masqueAig"] = node->masqueAig();
    doc["wifi_on"] = WIFI_ON;
    doc["discovery_on"] = DISCOVERY_ON;
    doc["ssid"] = ssid;
    doc["password"] = password;
    doc["maxSpeed"] = node->maxSpeed();
    doc["sensMarche"] = node->sensMarche();

    // Nœuds
    const String index[] = {"p00", "p01", "p10", "p11", "m00", "m01", "m10", "m11"};
    for (byte i = 0; i < nodePsize; i++)
    {
      if (node->nodeP[i] == nullptr)
        doc[index[i]] = "null";
      else
        doc[index[i]] = node->nodeP[i]->ID();
    }

    // Aiguilles
    for (byte i = 0; i < aigSize; i++)
    {
      if (node->aig[i] == nullptr)
        doc["aig" + String(i)] = "null";
      else
      {
        doc["aig" + String(i) + "id"] = node->aig[i]->ID();
        doc["aig" + String(i) + "posDroit"] = node->aig[i]->posDroit();
        doc["aig" + String(i) + "posDevie"] = node->aig[i]->posDevie();
        doc["aig" + String(i) + "speed"] = node->aig[i]->speed();
        doc["aig" + String(i) + "pin"] = node->aig[i]->pin();
      }
    }

    // Signaux
    for (byte i = 0; i < signalSize; i++)
    {
      if (node->signal[i] == nullptr)
        doc["sign" + String(i)] = "null";
      else
      {
        doc["sign" + String(i) + "type"] = node->signal[i]->type();
        doc["sign" + String(i) + "position"] = node->signal[i]->position();
      }
    }

    String output;
    serializeJson(doc, output);
    file.print(output);
    file.close();
    debug.print("Sauvegarde des datas en FLASH\n");
  }
} //--- End writeFile

C’est tout pour les fonctions contenues dans ce fichier Settings.cpp.

Concernant le fonctionnement de Railcom, fichiers Railcom.h et Railcom.cpp, je ne l’aborderai pas ici car il a déjà fait l’objet d’un article très détaillé sur le site : Détection RailCom© avec ESP32

Pas plus que CanConfig.h et CanConfig.cpp qui sont des configurations classiques. On notera cependant que je n’utilise aucun filtre. Chaque satellite reçoit donc l’ensemble des messages échangés sur le bus CAN et, par programmation rejette ou retient et traite les messages qui l’intéresse.

Nous poursuivrons dans les prochains articles la présentation du code pour ces satellites autonomes ainsi que pour les cartes Main et Watchdog.

(*1) – FreeRTOS est également le système d’exploitation de l’Arduino
(*2) - Certains constatent que PlateformIO est souvent privilégié à contrarion de l’IDE Arduino et s’interrogent