Domotique

Ma maison pilotée

Base commune pour les modules

Code commun

Code commun

Base de code commum à chaque module capteur.

far fa-bell

Le code présenté ici est à titre informatif. Je programme sur mon temps perso avec des connaissances acquises au gré de mes lectures sur le net. Le code n'est pas optimal et ne respecte pas forcément les standards, je ne reste qu'un amateur.

Les besoins

Pour facilité l'accès aux configurations de chaque module créé pour ma domotique, je souhaitais :

  • Une base de code commune à chaque module capteurs/commandes.
  • La même base commune pour les différents types d'ESP utilisés (ESP8266 ou ESP32).
  • Une interface Web commune pour les configurations de base (réseau, nom du module, communication...).
  • Une mise à jour des modules par le Wifi (OTA : Over The Air).
  • Sécuriser la mise à jour.
  • Sécuriser la configuration.

Le code

Le programme est divisé en différentes parties pour plus de lisibilité et facilité la maintenance.

En-tête

Dans l'en-tête du code, j'aime bien marquer le nom du module et la configuration requise (modèle d'ESP) ainsi, je n'oublie pas la configuration de L'IDE Arduino pour ce module.

Suivent ensuite toutes les bibliothèques nécessaires au bon fonctionnement du module et de ses composants environnants.

Je mets une partie "A MODIFIER", elle comprend le nom du firmware et le numéro de version de celui-ci, ils seront retransmis au serveur Home assistant et sur l'interface WEB, toujours pour facilité la maintenance et les mises à jour.

Autre élément très important "MDP_ESP[]", il est le mot de passe par défaut pour :

  • Le Wifi local au module (pour la première configuration).
  • Pour la mise à jour via OTA (il faudra rajouter le numéro du module.).
  • Pour se connecter aux pages web de configuration.
/********************************************************************************/
/*                           MODULE "NOM_DU_MODULE"                             */
/*              Configuration IDE ARDUINO LOLIN(WEMOS) D1 mini Pro              */
/********************************************************************************/

// Libraries

#include 			// Gestion du Wifi
#include 
#include 

// Recupérer l'heure sur serveur NTP by Fabrice Weinberg
// https://github.com/arduino-libraries/NTPClient
#include    
// Gérer les différent format d'heure https://github.com/PaulStoffregen/Time        
#include "TimeLib.h"                

#include 
#include 			// Mise à jour OTA

#include 		// Création d'un serveur Web	

#include 			// Lecture/Ecriture dans l'EEPROM de l'ESP

// Librairie des capteurs
#include "Adafruit_SHT31.h"
#include 

// Ajouter ici les bibliothèques nécessaire

//!!!!!!!! VALEUR A MODIER !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!/

//--------------------------------------------------------------//
#define NOM_SOFT "Firmware base"
#define VERSION "0.1.1"         // Version du programme
//--------------------------------------------------------------//
// MDP de connexion Wifi local de l'ESP et en partie maj OTA
static char MDP_ESP[]= "motdepasse";    
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!/

Data config

Ici, on aura toutes les données et fonctions nécessaires à la sauvegarde des données de configuration du module :

  • Les constantes permettant soit de sauvegarder les données soit de les remettre à leur valeur d'usine après redémarrage.
  • La structure "sConfig" avec les données a sauvegarder.
  • L'adresse de départ et le nombre d'octets alloués pour la sauvegarde dans l'EEPROM.
  • La fonction void sauvegardeEEPROM(long code_verif) sauvegarde simplement la structure "config_esp". Elle prend comme paramètre un code_verif, qui permettra la remise à l'état usine de l'EEPROM avec un redémarrage.
/************************************** Structure config ********************/
const unsigned long CODE_VERIF=44332211;    // Code de verif
const unsigned long CODE_RESET=87654321;    // Code pour faire un reset

struct sConfig {
  // Variables générales de config
  unsigned long codeV;          // Code de verification
  uint8_t mon_id;               // Numéro du module
  char nom[20];                 // Nom du module
  int refresh_data;             // Périodicité du refresh des data capteurs
  bool trans_HA;                // Transmission à Home Assistant
  char ssid[2][32];             // Nom SSID des 2 wifi possible
  char mdp_wifi[2][32];         // Mot de passe des 2 wifi
  int refresh_rssi;             // Periodicité du refresh des data wifi
  bool connect_wifi;            // Tentative de connexion wifi
  uint8_t IP_WIFI[2][4];        // Adresse IP de début des 2 wifi
  uint8_t Mask_WIFI[2][4];      // Masque des 2 wifi
  uint8_t Gateway_WIFI[2][4];   // Passerelle des 2 wifi
  bool deux_wifi;               // Si on a le choix de 2 wifi
  uint8_t IP_MQTT[4];           // Adresse IP du serveur MQTT
  uint8_t nb_connect_mqtt;      // Tentative de connexion au serveur
  char user_mqtt[32];           // Login MQTT
  char mdp_mqtt[32];            // Mot de passe MQTT
  // Variables spécifiques pour ce module
  // Mettre ici les autres variables à sauvegarder
  // xxxxxxx
};

sConfig config_esp;
int eeAddress = 0;        //Adresse de départ pour enregistrement sur EEPROM
int taille_eeprom=620;    // Taille de l'EEPROM necessaire pour la sauvegarde

/** Sauvegarde en mémoire EEPROM le contenu actuel de la structure */
void sauvegardeEEPROM(long code_verif) {      
  Serial.println("Sauvegarde eeprom");
  Serial.println(code_verif);
  config_esp.codeV = code_verif;
  EEPROM.begin(taille_eeprom);
  EEPROM.put(eeAddress, config_esp);	
  if (EEPROM.commit()) {
    Serial.println("Data successfully committed");
  } else {
    Serial.println("ERROR! Data commit failed");
  }
  EEPROM.end();                         
}


 
 








 

void chargeEEPROM(void) charge les valeurs stockées dans l'EEPROM dans notre structure "config_esp".

Puis vérifie si la valeur de config_esp.codeV est bien le code de vérification.

Si le code de vérification est mauvais, on charge la structure avec des valeurs usine, puis on sauvegarde le tout dans l'EEPROM.

Donc, au tout premier démarrage du module, lorsque l'EEPROM comporte des valeurs aléatoires, la fonction permet d'inscrire les données par défaut, ou de réinitialiser notre module aux valeurs par défaut dès que l'on souhaite.

/** Charge le contenu de la mémoire EEPROM dans la structure */
void chargeEEPROM() {
  // charge X octets memoires eeprom en RAM
  EEPROM.begin(taille_eeprom);
  // Lit la mémoire EEPROM
  EEPROM.get(eeAddress,config_esp);  
  // Détection d'une mémoire non initialisée
  Serial.println("config_esp.codeV: ");
  Serial.println(config_esp.codeV);
  // Verificatioin du code verif, si pas ok alors on charge les valeurs par defaut
  if (config_esp.codeV != CODE_VERIF) {       
    Serial.println("reinit eeprom");
    // Valeurs par défaut pour les variables de config
    config_esp.codeV = CODE_VERIF;
    Serial.println("INIT config_esp.codeV: ");
    Serial.println(config_esp.codeV);
    config_esp.mon_id=70;
    strcpy(config_esp.nom, "Non configure");
    config_esp.refresh_data=5;
    config_esp.trans_HA=false;
    //WIFI
    strcpy(config_esp.ssid[0], "mon_wifi1");
    strcpy(config_esp.ssid[1], "mon_wifi2");
    strcpy(config_esp.mdp_wifi[0], "mdpwifi1");
    strcpy(config_esp.mdp_wifi[1], "mdpwifi1");
    config_esp.refresh_rssi=15;
    config_esp.connect_wifi=true;
    config_esp.IP_WIFI[0][0]=192;
    config_esp.IP_WIFI[0][1]=168;
    config_esp.IP_WIFI[0][2]=0;
    config_esp.IP_WIFI[0][3]=0;
    config_esp.IP_WIFI[1][0]=192;
    config_esp.IP_WIFI[1][1]=168;
    config_esp.IP_WIFI[1][2]=0;
    config_esp.IP_WIFI[1][3]=0;
    config_esp.Mask_WIFI[0][0]=255;
    config_esp.Mask_WIFI[0][1]=255;
    config_esp.Mask_WIFI[0][2]=255;
    config_esp.Mask_WIFI[0][3]=0;
    config_esp.Mask_WIFI[1][0]=255;
    config_esp.Mask_WIFI[1][1]=255;
    config_esp.Mask_WIFI[1][2]=255;
    config_esp.Mask_WIFI[1][3]=0;
    config_esp.Gateway_WIFI[0][0]=192;
    config_esp.Gateway_WIFI[0][1]=168;
    config_esp.Gateway_WIFI[0][2]=0;
    config_esp.Gateway_WIFI[0][3]=0;
    config_esp.Gateway_WIFI[1][0]=192;
    config_esp.Gateway_WIFI[1][1]=168;
    config_esp.Gateway_WIFI[1][2]=0;
    config_esp.Gateway_WIFI[1][3]=0;
    config_esp.deux_wifi=false;
    //MQTT
    config_esp.IP_MQTT[0]=192;
    config_esp.IP_MQTT[1]=168;
    config_esp.IP_MQTT[2]=0;
    config_esp.IP_MQTT[3]=0;
    config_esp.nb_connect_mqtt=10;
    strcpy(config_esp.user_mqtt, "loginMQTT");
    strcpy(config_esp.mdp_mqtt, "mdpMQTT");
    // Mettre ici les autres variables à sauvegarder
    
    EEPROM.put(eeAddress, config_esp);    // Envoie des données 
    if (EEPROM.commit()) {
    Serial.println("Data successfully committed");
    } else {
    Serial.println("ERROR! Data commit failed");
    }
  }
  EEPROM.end();                           // Libere la mémoire 
}

OTA

La fonction maj_OTA() permet de mettre à jour via le wifi le module. Il suffit de sélectionner le nom du module dans la partie "Port" de l'IDE arduino.

La création du nom se fait par la concaténation du nom du module et de son numéro, tous les deux données dans la configuration web [config_esp.nom-config_esp.mon_id].

Pour sécuriser la mise à jour, et pour éviter la mise à jour du mauvais module, on créée un mot de passe unique à chaque module, composé du mot de passe général et du numéro de module [MDP_ESP+config_esp.mon_id], il sera demandé pour chaque téléversement du programme vers le module.

On lance ensuite le service ArduinoOTA.begin(). 

La fonction maj_OTA() est appelée dans le setup après la connexion wifi.

 

/**********************************************************************************/
/*                                        MAJ OTA                                 */
/**********************************************************************************/

void maj_OTA(){
  // Création du nom de module pour la maj OTA
  char nom_esp[25]; 
  char nummod[]="-%d";
  sprintf(nummod,nummod,config_esp.mon_id);                     
  strcpy(nom_esp,config_esp.nom);
  // On concatènele nom du module avec son numero (id)
  strcat(nom_esp,nummod);
  // on transmet le nom
  ArduinoOTA.setHostname((const char *)nom_esp);          

  
  // Pour la création du mot de passe de la maj OTA
char mdp_OTA[25]; char num_mod[]="%d"; sprintf(num_mod,num_mod,config_esp.mon_id); strcpy(mdp_OTA,MDP_ESP); // On concatène MDP_ESP avec le numéro du module strcat(mdp_OTA,num_mod); // On parametre le mdp ArduinoOTA.setPassword((const char *)mdp_OTA); // initialisation de l'OTA ArduinoOTA.begin(); ArduinoOTA.onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) { type = "sketch"; } else { // U_FS type = "filesystem"; } // NOTE: if updating FS this would be the place to unmount FS using FS.end() Serial.println("Start updating " + type); }); ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) { Serial.println("Auth Failed"); } else if (error == OTA_BEGIN_ERROR) { Serial.println("Begin Failed"); } else if (error == OTA_CONNECT_ERROR) { Serial.println("Connect Failed"); } else if (error == OTA_RECEIVE_ERROR) { Serial.println("Receive Failed"); } else if (error == OTA_END_ERROR) { Serial.println("End Failed"); } }); }
fas fa-book-reader

Vous pouvez configurer l'IDE arduino 1.8.x en suivant les infos données sur projetsdiy.fr. Pour la nouvelle version 2.x, il semblerait qu'elle prenne automatiquement l'OTA sans installer python, seule les librairies ESP8266 et ESP32 sont à installer.

Image

Article en cours de rédaction