Base commune pour les modules
Code commun
Base de code commum à chaque module capteur.
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.
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 uncode_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");
}
});
}
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.