L'essentiel sur ESP8266 / NodeMCU : séquence programmation (exemple du capteur de niveau d'eau)




Cet article fait suite au précédent (quelle logique !) dans lequel je décrivais cette petite merveille de microcontrôleur wifi qu’est l’esp8266 et notamment celui dans sa version NodeMCU.

Dans ce nouvel article j’ai pour ambition de présenter la manière dont il se programme. Ou plutôt (restons humble) de faire profiter les autres des nombreuses heures de recherche qui m’ont été nécessaires pour rassembler les informations permettant de transformer ce microcontrôleur en un véritable objet connecté. 

Note du 07/05/2018 : Cet article va présenter essentiellement le composant ESP-12 dans son intégration en NodeMCU. Si vous recherchez un tutoriel sur l'ESP-01, qui est aussi un module basé sur le microcontrôleur Esp8266 mais intégré sur un module plus petit avec moins de fonctionnalités, vous pouvez consulter l'article "Tous les secrets de l'ESP01 : réalisation d'un contrôle d'état des volets".


Introduction


Sur internet on trouve vraiment tout ce qu’on recherche … mais à peu près … et souvent en recollant les morceaux après avoir parcouru des dizaines de sites …

Lorsque j’ai commencé à programmer mon NodeMCU, j’aurais aimé trouver un site qui m’explique clairement les fonctions à utiliser pour faire telle ou telle chose et également des exemples. Et comme je n’ai pas trouvé j’ai décidé de le faire pour ceux qui, comme moi, ont envie d’exploiter un NodeMCU afin de créer leurs propres objets connectés, d’héberger un serveur web, de faire un point d’accès wifi, un relais wifi, un répéteur wifi,  un snifer wifi, un client web …. Les possibilités sont infinies !

La preuve par l’exemple :



Et comme un bon exemple vaut mieux qu’un long discours, je vais présenter les fonctions et le code pour programmer le NodeMCU, en me servant de l’exemple d’une sonde de mesure de niveau d’eau dans une cuve. 


Le contexte :


Actuellement j’ai une cuve en béton de 5000 litres qui est enterrée à l’extérieur à coté de ma maison et qui collecte toutes l’eau de pluie des gouttières.
Dans la maison j’ai un suppresseur qui pompe cette eau de pluie et alimente les deux toilettes, la machine à laver ainsi que le tuyau d’arrosage du jardin.
Afin d’être sûr qu’il reste toujours de l’eau dans la cuve mais également de surveiller que je ne prends pas les derniers centimètres tout au fond qui sont de l’eau saumâtre, j’ai souhaité superviser le niveau restant dans ma cuve au travers d’un capteur virtuel domoticz. Connaitre le niveau d’eau restant dans la cuve est également un pré requis à tout système automatisé, comme par exemple l’arrosage du jardin en cas d’absence en été.


Ne trouvant pas sur le marché un objet connecté remplissant mon cahier des charges, j’ai donc souhaité le faire moi-même. Dans un premier temps, j’ai acheté des émetteurs/récepteurs 433Mhz low cost et je me suis créé un petit système de communication entre deux arduino. Le premier arduino situé dans la cuve mesure le niveau. Il l’envoi au second arduino toutes les secondes. Le second arduino le transmet par liaison série à mon Raspberry PI sur lequel est hébergé le serveur domotique. Le serveur domotique reçoit toutes les 10 minutes le niveau et l’affiche. 

Suivi niveau d'eau

Cette installation fonctionne assez bien depuis un an mais il y a tout de même des problèmes :


-       Cela utilise deux Arduino.
-       Le RF433 avec des émetteurs récepteurs low cost n’est :
o    pas puissant (galère pour construire des antennes) : distance limitée à quelques mètres.
o    pas sur (beaucoup d’échecs et des périodes où il faut rebooter pour que ça remarche).
o    pas pénétrant : il faut faire ressortir l’antenne de la cuve en beton au lieu d’avoir tout à l’intérieur.
-       Aucun moyen de modifier des données en se « connectant » à distance à la sonde dans la cuve.

Enfin bref, cela n’est pas compatible avec mon objectif de déployer une solution domotique fiable ! Il me fallait autre chose. Et cet autre chose passe je l’ai trouvé en passant par le wifi … et le NodeMCU. 


Mon cahier des charges :


Je souhaite créer une sonde de niveau qui :
-       Mesure le niveau de d’eau dans la cuve et l’envoie à un capteur virtuel domoticz toutes les t secondes. t étant défini par défaut dans le programme mais pouvant également être modifié à distance, sans besoin de modifier le code.
-       Renvois des valeurs le plus fiable possible en écartant les valeurs extrêmes pour ne pas avoir des pics soudain dans les graphes de suivi.
-       Me permet de changer le capteur virtuel domoticz sans devoir changer le code.
-       Me permet de rebooter le microcontrôleur sans avoir besoin d’intervenir physiquement. 
-       Me permet de modifier les identifiants de connexion WiFi sans devoir reprogrammer la sonde (SSID et mot de passe).



Quelques prérequis pour profiter au mieux de cet article :


J’ai programmé mon NodeMCU en utilisant l’IDE Arduino sur mon PC. Donc :

  •       Il faut connaitre un minimum la programmation et essentiellement le C/C++
  •       Il faut avoir installé l’IDE Arduino sur votre PC, y avoir installé les librairies pour ESP8266 et avoir configuré l’environnement pour générer du code pour ce dernier. Vous trouverez tout cela facilement sur internet ou alors en regardant le paragraphe « Programmation » dans mon article précédent .



Pour réaliser le capteur de profondeur de cuve, ll faudra aussi le matériel. 
Je vous donne les liens sur Amazon afin d’être sûr de vous procurer les bons composants. 

De plus, si vous commandez en utilisant mon lien, vous me permettrez de gagner quelques centimes. A l’heure où j’écris j’ai cumulé la somme pharaonique de 56 centimes en 8 mois ! Hé bien croyez-le ou pas, cela me fait plaisir et m’encourage à rédiger des articles de qualité pour ceux d’entre vous qui ont eu la gentillesse de faire leur achat sur Amazon en cliquant sur un de mes liens.

  •       Un NodeMCU (j’en conseil au moins deux : un qui rester en prototype et un autre qui sera utilisé réellement) : http://amzn.to/2CF4C6O
  •       Des breadboard 170 points (il en faut deux pour le NodeMCU) : http://amzn.to/2AHR7RT
  •        Des câbles mâle-mâle pour breadboard : http://amzn.to/2AHRKed
  •        Des LED et des résistances. Je conseille de prendre un kit pour avoir un peu de stock. D’autant plus que les résistances utilisées seront entre 220ohm et 2000ohm : http://amzn.to/2AITqVa
  •        Un capteur de distance à ultrason HR-SR04. Je conseil de prendre le kit suivant avec 5 capteurs et deux châssis de montage qui seront très pratiques : http://amzn.to/2rsTj0w
Sur ebay les délais de livraison seront plus long mais les composants seront légèrement moins chers : 
Le nodeMCU : ICI
Les breadboard : ICI
Les modules HC-SR04 : ICI
Les cables de connexion DUPOND pour breadboard: ICI


Avant le soft, soyons hard :


Pour réaliser la sonde, avant de passer à la programmation, il va falloir assembler tous les composants ci-dessus.




J’ai fait un petit schéma et j’ai mis des numéros pour l’expliquer :


 est le NodeMCU

 est le capteur de distance ultrason HC-SR04.

③ et ④ sont des LED. La bleue s’allume lorsqu’un cycle de mesure de distance débute. Elle s’éteint lorsque le cycle est terminé. Un cycle correspond à prendre un certain nombre de mesures, d’en faire la moyenne en ne prenant pas en compte les deux mesures les plus extrêmes et à envoyer le résultat au serveur domotique. La présence de cette LED me permet de valider que le programme tourne correctement. La LED verte indique que le composant est bien alimenté.

⑤ est un pont diviseur de tension. Je ne décrirais pas ici comment cela fonctionne car c’est largement documenté sur internet. Ce pont est nécessaire car le HC-SR04 fonctionne en 5V. Donc le résultat de la mesure, qui est envoyé sur le PIN nommé Echo, est émis avec un courant 5V. Hors le NodeMCU est en 3,3V. Recevoir du 5V sur ses entrées digitales peut l’abimer. Il faut donc transformer la sortie Echo de 5V en 3.3V. C’est à cela que sert le pont diviseur de tension. Il est constitué par deux résistances. Par contre, le PIN Trig qui sert à déclencher une mesure fonctionne en recevant un signal en 3.3V. Le HC-SR04 pourrait être alimenté en 3.3V mais dans ce cas il se révèle beaucoup moins précis.

 est un fil qui raccorde la sortie digitale D0 au PIN RST. Quand RST  passe à « low », alors le NodeMCU effectue un reset. D0 est à HIGH par défaut. C’est pour cela qu’il faut utiliser cette PIN.

Pour finir sur la partie raccordement, durant mes essais j’ai connecté l’ESP à mon PC via son interface micro USB. Cette interface permet au PC de simuler une liaison série avec le composant et permet au composant d’être alimenté. J’ai placé tous les composants sur deux breadboard et j’ai utilisé des câbles « dupond ». Mais lorsque tout sera validé je compte souder tous les composants sur une plaque d’essai et les installer dans un boitier hermétique qui sera placé dans la cuve. 

Lors de la version finale, j’utiliserai certainement un transformateur raccordé sur VIN comme alimentation. Dans ce cas, je n’aurais plus les 5V sur le PIN VU (qui correspond à la tension de l’alimentation USB). Le schéma sera donc un peu à revoir. 



Voilà, j’espère que tout cela est clair. Maintenant, après une petite page de pub, nous allons pouvoir programmer.



Code code code comme dit la poule :

Algorithme globale :


Avant d’expliquer le code et les fonctions utilisées, je vais décrire de manière simplifiée l’algorithme du programme.

-       Déclarer les constantes et variables globales
-       Dans la fonction setup() :
o    Initialiser la liaison série
o    Initialiser les variables, soit par leurs valeurs précédentes si elles existent, soit par leurs valeurs par défaut.
o    Etablir la liaison Wifi, si échec alors faire un reset.
o    Initialiser les PIN
o    Initialiser la fonction serveur web.
-       Dans la fonction loop() :
o    Ecoute si une requête http est reçue et si oui alors la requête sera traitée.
o    Démarrage d’un cycle de mesure.
o    Effectue n mesures et les mémorise dans un tableau. Attendre le temps demandé entre chaque mesure.
o    Supprime la mesure la plus petite et la plus grande puis calcul la moyenne des mesures restantes.
o    Construit l’URL d’appel au serveur domoticz et envois la requête JSON.
o    Finalise le cycle de mesures.
o    Attend le temps demandé entre chaque cycle de mesure tout en traitant les requetes http qui seraient reçues.

Nous allons détailler chacune de ces parties.

Déclaration des variables :


Au début du programme il faudra les includes ci-dessous. Ils seront expliqués ultérieurement. 

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>

La première chose à faire dans un programme est de déclarer les variables et les constantes.

La chose la plus intéressante dans cette partie est la définition de la structure « structEepromDomoticz » ainsi que la variable « eepromDomoticz » de ce type.
La nécessité de cette structure est due au fait que nous souhaitons pouvoir retrouver après un reset ou une coupure de courant, des valeurs qui ont été modifiées durant l’exécution du programme.
L’ESP contient une mémoire appelée EEPROM qui est persistante. Toute valeur écrite dans cette mémoire se retrouvera après une coupure de courant.
On peut lire autant de fois que l’on souhaite dans cette mémoire mais il faut éviter de trop nombreuses écritures. Toutefois cela reste relatif car on parle de 100 000 écritures par octet ! Donc si utilise cette mémoire correctement il y a peu de chance d’arriver à la corrompre.

La structure « structEepromDomoticz » représente la manière dont je vais structurer les données dans cette mémoire. Dans un premier temps je vais m’en servir pour mémoriser l’identifiant du capteur virtuel domoticz utilisé. Je vais faire des tests sur un capteur et une fois en version finale, je passerai sur un capteur final.


Voici ma structure C : 

#define MEMORY_CONTROL_START 127
#define MEMORY_CONTROL_END 255

struct structEepromDomoticz {
  int iMemoryControlStart;
  int iIdxDomoticzCuve;
  int iMemoryControlEnd;
};

structEepromDomoticz eepromDomoticz; //Variable pour récupérer les données stockées dans epprom.
int index_eepromDomoticz;
int iTaille_eeprom;

IPAddress permet de manipuler des addresses IP.

ESP8266WebServer permet d’instancier une variable qui représentera la fonctionnalité serveur de l’ESP. Une fois connecté en wifi, l’ESP écoutera les requêtes http qui lui seront faites sur le port ESP_SERVER_PORT. Pour chaque requête une fonction sera appelée afin d’y répondre.


HTTPClient permet de déclarer un client http : l’ESP va pouvoir envoyer une requête HTTP. En l’occurrence, ici, la requête qui sera à envoyer sera la requête JSON à l’adresse du serveur domoticz sur le LAN.

Fonction setup() :


Cette fonction est appelée une fois au démarrage de l’ESP.

Initialisation de la liaison série :


Je présente ce code que vous devez connaitre uniquement pour faire un rappel sur la valeur particulière à utiliser qui est de 74 880 bps. Vous pouvez utiliser toute autre valeur. Mais cette dernière permettra de voir en clair les caractères qui sont émis au reboot de l’ESP.

  Serial.begin(74880);
  delay( 100 );
  Serial.println( "\nInitialisation liaison serie : OK" );

Récupération des valeurs dans la mémoire EEPROM :



L’include #include <EEPROM.h> permet d’accéder à un objet « EEPROM ». C’est cet objet et les méthodes associées qui permettent d’accéder et de manipuler la mémoire EEPROM.

EEPROM.begin( iTaille_eeprom );
EEPROM.get( index_eepromDomoticz, eepromDomoticz );


begin déclare la quantité (en octets) de mémoire EEPROM utilisée.
get( offset, var)  va placer la variable « var » à l’offset « offset » dans la mémoire EEPROM. Ainsi, si la variable var est une structure, nous pourrons accéder aux données de l’EEPROM au travers des champs de cette structure.


J’ai donc le code suivant : 

////////////////////////////////////////////////////////Récupère les valeur de la mémoire EEPROM
  iTaille_eeprom = sizeof( structEepromDomoticz ); // Ajouter la taille des autres structures
  EEPROM.begin( iTaille_eeprom );
 
  //Recupere les données relatives à domoticz
  index_eepromDomoticz = 0; //Index variable eepromDomoticz dans l'eeprom
  
  EEPROM.get( index_eepromDomoticz, eepromDomoticz );
  Serial.println( "Read from EEPROM : " );
  if ( ( eepromDomoticz.iMemoryControlStart != MEMORY_CONTROL_START ) or 
       ( eepromDomoticz.iMemoryControlEnd != MEMORY_CONTROL_END ) ){
        //On a pas la bonne valeur de controle. Donc on prend la valeur par défaut
        eepromDomoticz.iIdxDomoticzCuve = iIndexCuveDomoticzDefault;
        Serial.println( "Valeur par defaut pour iIdxDomoticzCuve" );
       }
       
  Serial.println( eepromDomoticz.iMemoryControlStart );
  Serial.println( eepromDomoticz.iIdxDomoticzCuve );
  Serial.println( eepromDomoticz.iMemoryControlEnd );

Pour l’exemple je n’ai stocké qu’une seule variable dans l’EEPROM mais je compte en ajouter d’autres dans la version finale (SSID, temps entre chaque mesure …. ).

Etablir la liaison Wifi, si échec alors faire un reset :

Il faut maintenant connecter l’ESP au wifi. Cela va se faire avec les méthodes de l’objet WiFi déclarée dans le module #include <ESP8266WiFi.h>

Il n’y a rien de compliqué dans cette séquence. La chose à voir, c’est que j’ai utilisé WiFi.disconnect(1); car lors de reboot fréquent de l’ESP, la connexion WiFi ne voulait plus se faire.




////////////////////////////////////////////////////////// Initialisation Wifi 
  //WiFi.hostname( sHostname ); Ne fonctionne pas.
  //WiFi.hostname( "esp1" ); Ne fonctionne pas. 
  WiFi.config(oipIpEsp, oipGateway, oipSubnet, oipDns );
  delay(100);

  Serial.print( "Local IP avant connect = " );
  Serial.println( WiFi.localIP().toString() );
  
  WiFi.disconnect(1);

  Serial.print( "Local IP apres disconnect = " );
  Serial.println( WiFi.localIP().toString() );
  
  WiFi.begin( sSsid, sPassword );

  delay( 1000 );
  
  int iCompteur = 0;
  Serial.printf("Connection status: %d\n", WiFi.status());
  Serial.print( "Connecting WiFi ." );
  while (WiFi.status() != WL_CONNECTED ){
     delay( 1000 ); 
     Serial.print( "." );
     if ( iCompteur++ > MAX_TIME_WAIT_WIFI ) vFonctionReboot();
     //WiFi.begin( sSsid, sPassword );
     //???ajouter ici le compte du nombre d'essai de connection et mettre en sommeil si temps trop long
  }

  String stRes = "\nConnected to : " + String( sSsid );
  stRes += "\nAdresse IP : ";
  stRes += WiFi.localIP().toString() ; //Type IPAddress = tableau de 4 entiers.
  
  WiFi.macAddress( tbMac ); //récupère l'adresse MAC
  stRes += "\nAdresse MAC : ";
  for (int i = 0; i<=5; i++ ) {
    if ( i == 0 ) stRes += String( tbMac[ i ], HEX );
    else stRes += "." + String( tbMac[ i ], HEX );
  }
  
  Serial.println( stRes );

Initialisation des PIN :


Du classique !

A noter qu’il faut utiliser les définitions Dx pour les PIN digitales plutôt que d’essayer de trouver le numéro du port GPIO. 

pinMode( D1, OUTPUT );
  digitalWrite( D1, LOW) ; //LED qui indique un cycle de mesure.

  pinMode( TRIG, OUTPUT); //pin TRIG de la sonde HC-SR04
  digitalWrite( TRIG, LOW );
  
  pinMode( ECHO , INPUT ); //pin  ECHO de la sonde HC-SR04

Initialiser le serveur web :


Afin de pouvoir communiquer avec l’utilisateur en cours de fonctionnement, l’ESP va activer sa fonction serveur WEB. Nous allons envoyer des commandes au travers des paramètres d’une requête http. Pour chaque paramètre le serveur va implémenter une fonction qui effectuera l’action demandée et retournera le résultat sous la forme d’une page web.


Nous avions déclaré une variable de type serveur : ESP8266WebServer oEspServer( ESP_SERVER_PORT );

L’association d’une fonction avec une requête http se fait avec la méthode « on ».

// Routage des requêtes HTTP
  oEspServer.on("/", vFonctionRoot);
  oEspServer.on("/action",vFonctionAction);
  oEspServer.on("/set",vFonctionSet);
   
  oEspServer.begin();
  Serial.println ( "HTTP server started" );

J’ai fixé l’adresse IP du serveur ESP à 192.168.1.60 (en donnant une adresse IP fixe au niveau du DHCP de ma box).

Donc, lorsque le serveur sera appelé par : http://192.168.1.60/ le programme exécutera la fonction vFonctionRoot(). Quand le serveur recevra http://192.168.1.60/action c’est la fonction vFonctionAction() qui sera exécutée. Quand le serveur recevra la requête http://192.168.1.60/set c’est la fonction « vFonctionSet() » qui sera exécutée.

Et quand c’est l’URL http://192.168.1.60/ qui est appelée c’est la fonction « vFonctionRoot() » qui sera exécutée

Fonction loop() :


Activation du mode serveur web :


C’est la méthode handleClient() de l’objet de classe ESP8266WebServer qui va permettre à l’ESP de répondre aux requêtes http qu’il aura reçu.
Il ne suffit pas d’instancier un objet de type ESP8266WebServer pour que l’ESP déclenche automatiquement des actions lorsqu’il reçoit une requête sur son port d’écoute. Il faut appeler la fonction handleClient qui va inspecter si une requête est reçue et si oui, exécuter la fonction définie par les méthodes « on » dans le setup.
L’ESP ne pourra traiter les requêtes reçues que lorsqu'on fera appel à handleClient. Je place donc cette ligne de code en début de la fonction loop, mais je la placerai aussi dans la boucle d’attente entre deux cycles de mesures. 


//Ecoute les requetes faites par des clients.
  //Appel les fonctions associées à l'url reçu.
  oEspServer.handleClient();

Par exemple si depuis votre PC (connecté au même LAN que l’ESP évidement) vous envoyez la requête http://192.168.1.60/ depuis votre navigateur, alors l’appel à handleClient va déclencher l’appel de la fonction vFonctionRoot().

Je rappelle que l’adresse IP de l’ESP dépend de votre environnement et est attribuée par votre box ou votre serveur DHCP. En ce qui me concerne j’ai fixé cette adresse en 1.60 mais il faudra l’adapter dans votre cas.

Lancement d’un cycle de mesures :


Comme je l’ai expliqué, pour une question de fiabilité, la mesure qui sera envoyée au serveur domotique correspond à la moyenne de plusieurs mesures.
Un cycle de mesure correspond donc à prendre n mesures, en extraire la plus petite et la plus grande, puis en faire la moyenne.

Etant donné que cette partie de code est essentiellement de la programmation C, je ne vais pas tout détailler. Je ne commenterai que les lignes intéressantes. 


//Allume la LED indiquant qu'un cycle de mesure commence
  Serial.println( "\nOn debute un nouveau cycle de mesures ..................." );
  digitalWrite( D1, HIGH );



Ici on allume la LED bleue du schéma pour visualiser qu’on débute un cycle de mesures.

//Controle que le nombre de mesures à prendre ne depasse pas ce qui est possible
  if ( iNbMesuresParCycle >= NB_MESURES_MAX_PAR_CYCLE ){
      Serial.println( "Nombre de mesure demandee trop grand. On retablit au nombre MAX" );
      iNbMesuresParCycle = NB_MESURES_MAX_PAR_CYCLE;
  }

  //On va prendre les mesures et les memoriser dans le tableau.
  Serial.print( "Nombre de mesure a prendre = " );
  Serial.println( iNbMesuresParCycle );
  
  for ( int i = 1; i <= iNbMesuresParCycle; i++ ){ // on va faire iNbMesuresParCycle mesures.
    //Lecture du niveau de la cuve
    iNiveauCuve = iLectureNiveauCuve();
    Serial.print( "Mesure numero " );
    Serial.print( i );
    Serial.print( " = " );
    Serial.println( iNiveauCuve );

Dans la déclaration des variables iNbMesuresParCycle a été initialisée a 5. Mais j’ai pour objectif de stocker le nombre de mesures à prendre pour chaque cycle dans la mémoire EEPROM. C’est pour préparer cette évolution que je test que cette variable ne dépasse pas un seuil (NB_MESURES_MAX_PAR_CYCLE qui est 100).


La fonction iLectureNiveauCuve() remonte le niveau d’eau dans la cuve en interrogeant le capteur HC-SR04. Je décrirai cette dernière après. 

//Memorise le niveau dans le tableau.
    if ( iNiveauCuve < 0 ) iNiveauCuve = 0;
    tiTableauDesMesures[ i ] = iNiveauCuve;

    //Recherche le min et le max
    if ( i == 1 ){ //On a qu'une seule mesure 
      iValeurMin = iNiveauCuve;
      iIndexValeurMin = i;
      iValeurMax = iNiveauCuve;
      iIndexValeurMax = i;
      
      Serial.print( "iValeurMin = " );
      Serial.print( iValeurMin );
      Serial.print( " iIndexValeurMin = " );
      Serial.println( iIndexValeurMin );
      Serial.print( "iValeurMax = " );
      Serial.print( iValeurMax );
      Serial.print( " iIndexValeurMax = " );
      Serial.println( iIndexValeurMax );
    }
    else {
      // On regarde si on est plus grand que la valeur max
      if ( iNiveauCuve > iValeurMax ){
        // Oui : on a un nouveau plus grand
        iValeurMax = iNiveauCuve;
        iIndexValeurMax = i;
      } else { // On regarde si on est plus petit que la valeur min
        if ( iNiveauCuve < iValeurMin ){ 
          // Oui : on a trouvé un nouveau plus petit
          iValeurMin = iNiveauCuve;
          iIndexValeurMin = i;         
        }
      }
    Serial.print( "iValeurMin = " );
    Serial.print( iValeurMin );
    Serial.print( " iIndexValeurMin = " );
    Serial.println( iIndexValeurMin );
    Serial.print( "iValeurMax = " );
    Serial.print( iValeurMax );
    Serial.print( " iIndexValeurMax = " );
    Serial.println( iIndexValeurMax );
    }
    
      
    // Attend le temps demandé entre deux mesures
    delay( iSecondesEntreDeuxMesures * 1000 );
  }

Rien de particulier à signaler ici. On se contente de mémoriser toutes les mesures prises et de trouver la plus petite et la plus grande. 

//Calcul la moyenne sans prendre en compte les valeurs Min et Max
  int iNbMesuresValides = iNbMesuresParCycle - 2;
  //Si iIndexValeurMax = iIndexValeurMin alors la somme se fera sur iNbMesuresParCycle - 1 seulement
  //Donc je rajoute 1 pour corriger
  if ( iIndexValeurMax == iIndexValeurMin ) iNbMesuresValides++;
  
  int iSommeDesMesuresValides = 0;
  int iValeurMoyenneDuCycle = 0;
  
  if ( iNbMesuresValides >= 1 ){
    for ( int i = 1; i<= iNbMesuresParCycle; i++ ){
       // Si on a pas la valeur Min, ni la valeur Max 
      int iValAAjouter = tiTableauDesMesures[ i ];
      if ( ( i != iIndexValeurMin ) and ( i != iIndexValeurMax ) ){
         Serial.print( "On ajoute la valeur suivante : " ); Serial.println( iValAAjouter );
         iSommeDesMesuresValides += iValAAjouter;
         Serial.print( "La somme est de : " ); Serial.println( iSommeDesMesuresValides );
      } else {
        Serial.print( "On ignore la valeur min ou max suivante : " ); Serial.println( iValAAjouter );
      }
    }
    // On calcul sur des float pour ajouter 0.5 afin d'avoir l'arrondi à l'entier le plus proche.
    iValeurMoyenneDuCycle = int ( ( float( iSommeDesMesuresValides ) / float( iNbMesuresValides ) ) + 0.5 );
   }
  Serial.print( "Nombre de mesures valides du cycle = " ); Serial.println( iNbMesuresValides );
  Serial.print( "Valeur Moyenne du cycle = " ); Serial.println( iValeurMoyenneDuCycle );

Dans cette partie on calcul la moyenne en excluant les valeurs min et max.
On compte le nombre de mesures valides car si toutes les mesures sont identiques le min et le max sont les mêmes. Cet algorithme est certainement largement améliorable.

A noter une petite astuce pour avoir l’entier le plus proche de la valeur réelle pour iValeurMoyenneDuCycle : on fait la division sur des réels (float) et avant de prendre la partie entière on ajoute 0.5. Ainsi, la partie entière de 12.1 sera 12 : partie entière (12.1+0.5) = 12. Et la patie entière de 12.6 sera bien 13 : partie entière (12.6+0.5) = partie entière (13.1) = 13. CQFD !


Ensuite on va construire une URL compatible avec le serveur domotique pour mettre à jour ce dernier. Dans mon cas il s’agit d’une URL compatible avec l’API JSON domoticz mais je pense que c’est facilement adaptable à tout système domotique. 

//Met à jour domoticz
  osUrl = "/json.htm?type=command&param=udevice&idx=";
  osUrl += String( eepromDomoticz.iIdxDomoticzCuve );
  osUrl += "&nvalue=0&svalue=";
  osUrl += String( iValeurMoyenneDuCycle );
  
  Serial.print( "Url d'appel domoticz = " ); 
  Serial.println( osUrl );


  //////////////////// Appel domoticz pour mettre a jour le capteur
  int iResCallDomo = iEnvoiDomoticz( iValeurMoyenneDuCycle );
  switch ( iResCallDomo ){
    case 0 :
      // L'appel de la fonction c'est bien passé
      Serial.println( "iEnvoiDomoticz OK" );
      break;
     default :
      // On a un code <> 0 donc il y a une erreur.
      // Les codes erreurs sont 1,2.
      Serial.println( "iEnvoiDomoticz NOK" );
      break;
  }
    

   
   //Attente entre deux cycles : on fait plusieurs fois délais pour ne pas bloquer le code
  digitalWrite( D1, LOW );
  for ( int i = 1; i<= iSecondesEntreDeuxCycles; i++ ) {
    oEspServer.handleClient();
    delay( 1000 );
  }
}

La fonction « iEnvoiDomoticz » sera détaillée plus bas. Elle se contente de faire une requete http avec l’URL construite au serveur domotique.

Le cycle de mesure étant terminé, on éteint la LED bleue.

A la fin de la fonction loop() on va attendre le temps demandé entre deux cycles de mesures.
Le temps d’attente pourrait être placé en mémoire EEPROM. Ainsi il serait modifiable à la demande sans devoir flasher l’ESP.
Pour que le serveur http soit toujours à l’écoute, au lieu de faire un delay sur le temps total, je fais n fois un delay d’une seconde. Et chaque seconde appel à la fonction handleClient.


Fonction de lecture du niveau de la cuve :


Cette fonction active la lecture du composant HC-SR04.

Pour rappel, ce dernier est un composant ultra son qui remonte le temps mis par un son émis pour aller jusqu’à la surface directement en face et revenir.
En connaissant la vitesse du son, on pourra donc déduire la distance qui sépare le composant avec la surface de l’eau.
Mais dans mon cas, ce que je cherche à avoir, c’est le niveau d’eau dans la cuve. J’ai donc mesuré avec un long morceau de bois, la distance qui sépare le capteur avec le fond de la cuve. Cette distance est de 164cm. Si x est la distance entre le capteur et la surface de l’eau, j’en déduis donc que la profondeur d’eau dans la cuve, en cm, est de 164-x.

Le composant HC-SR04 a une précision de 0.5cm environ et peut prendre des mesures entre 1 cm et 10m. Mais pour ma part j’ai remarqué qu’au-delà de 5m c’est moins précis. 

int iLectureNiveauCuve(){
  // definition des constantes
  const float vitesse_son = 340; // en m/s 
  const unsigned long timeout = 29500UL ; // 29.5 ms = 10m à 340m/s

  //Variables
  int iNiveauLu = 0;
  long mesure = 0;
  float distance = 0;

  Serial.println( "Debut fonction iLectureNiveauCuve" );

  // Envoi le signal de 10us sur TRIG pour déclancher la mesure 
  digitalWrite( TRIG, HIGH );
  delayMicroseconds( 10 );
  digitalWrite( TRIG, LOW );

  // Mesure le temps entre l'envoi de l'impulsion et le retour du son
  mesure = pulseIn( ECHO, HIGH, timeout );

  // Calcul la distance d = t*v/2 (t en us ) 
  distance = mesure * vitesse_son / 20000 ; // conversion pour avoir des cm 

  //Converti la distance entre le capteur et la surface en profondeur d'eau.
  if ( distance > PROFONDEUR_CUVE ){ 
      distance = PROFONDEUR_CUVE;
  }
  
  iNiveauLu = int( PROFONDEUR_CUVE - ( distance + 0.5 ) ); //On ajoute 0.5 pour avoir l'arondi à l'entier le plus proche
  
  Serial.print( "Niveau lu dans fonction iLectureNiveauCuve = " );
  Serial.println( iNiveauLu );
  
  return iNiveauLu;
}

Je ne décrirai pas plus cette fonction car il s’agit de mathématiques.
Ce qui est important à voir c’est l’utilisation des fonctions digitalWrite, delayMicroseconds, pulseIn. Mais ces dernières sont parfaitement documentées dans la librairie Arduino


Fonctions du serveur WEB :


Comme nous l’avons vu dans la fonction setup, nous avons configuré l’ESP pour qu’il fonctionne comme un serveur web. Lorsqu’il reçoit une requête, une fonction va être appelée.

Dans mon exemple j’ai prévu 3 fonction :

vFonctionRoot : qui est appelée lorsque le serveur reçoit une URL « vide ». Le « / ». Cette fonction va simplement retourner une page d’aide pour rappeler le formalisme des différentes requêtes reconnues.

image de la page d’aide

vFonctionAction : cette fonction se déclanche pour réaliser des actions. Par exemple faire un reboot de l’ESP. Le formalisme de cette requete est : /action ?commande=valeur.
Avec commande et valeur qui sont des variables. Par exemple, dans mon cas, j’ai prévu une action qui demande au NodeMCU de rebooter après X secondes. Si je veux rebooter après 10 secondes la requête que j’envoie est : http://192.168.1.60/action?reboot=10

vFonctionSet : cette fonction se déclenche quand la requête reçue est « /set ». De la même manière qu’avec action on peut donner des arguments : /set ?id=valeur&id2=valeur2 …
J’ai prévu cette fonction pour changer des valeurs mémorisées en EEPROM.
Par exemple, nous avons vu dans le code que je mémorise l’identifiant du capteur virtuel domotique à mettre à jour à chaque cycle de mesures. Si je veux changer cet identifiant, je vais envoyer la requete : http://192.168.1.60/set?idx_cuve=666. 666 est un exemple. Il faut prendre la valeur donnée par la colone « Idx » dans l’onglet Dispositifs de domoticz.

Je vais décrire la fonction vFonctionSet.


Fonctions de gestion des requêtes du serveur ESP8266WebServer :


La classe ESP8266WebServer apporte un certain nombre de méthodes pour gérer les requêtes que le serveur reçoit.

Le code ci-dessous est celui qui sera exécuté lorsque la requête «  http://192.168.1.60/set?idx_cuve=666 » sera envoyée. L’action que sera réalisée sera la mise à jour de la mémoire EEPROM pour changer l’identifiant domoticz associé au capteur de niveau. 


void vFonctionSet(){
  Serial.println( "Entre dans fonction vFonctionSet" );
  String osRes = "";
  
  //Analyse les arguments
  if ( oEspServer.hasArg("idx_cuve") ){
    Serial.print( "-- Change idx du capteur de cuve domoticz = " );
    // récupère l'argument associé 
    int iIdx = oEspServer.arg( "idx_cuve" ).toInt();
    if ( ( iIdx <= 0 ) or ( eepromDomoticz.iIdxDomoticzCuve == iIdx ) ){
      Serial.println( "Pas de changement( idx identique ou 0)" );
      osRes += "set idx_cuve : Pas de changement\n";
    } else {
      Serial.println( iIdx );
      vFonctionSetIdxCuve( iIdx );
      osRes += "set idx_cuve : set to " + String( iIdx ) + "\n";
    }
  }

  if ( osRes == "" ) 
    //renvoi sur la page d'aide
    vFonctionRoot();
  else {
    osRes += "\n\n";
    oEspServer.send(200, "text/html", osRes );  
 }

 return;
 }

Les méthodes utilisées pour gérer la requête reçue par le serveur sont :
  • hasArg( « chaine » ) : qui détecte la présence d’un argument nommé « chaine ».
  • arg( « chaine » ) : qui récupère la valeur (sous forme de chaine) associée à l’argument. On applique la fonction « toInt() » pour récupérer l’entier.


On termine la fonction en envoyant une page HTML à l’utilisateur avec la fonction send.
-       200 est le code d’erreur http, ou plutôt de non erreur !
-       « text/html » est le type de résultat qui sera retourné.
-       osRes contient le texte HTML. Ici un simple texte.
  

Ecriture dans la mémoire EEPROM :



Pour mettre à jour l’identifiant domoticz dans la mémoire EEPROM c’est la fonction vFonctionSetIdxCuve qui est appelée.


/*******************************************************************************************************************
* vFonctionSetIdxCuve( int idx )
 * Met à jour l'iIdxDomoticzCuve dans l'eeprom 
 * Si idx == 0 alors cela efface toutes les données (remise à zero de la structure)
 * ****************************************************************************************/
 void vFonctionSetIdxCuve( int idx ){
    
    Serial.print( "--Fonction vFonctionSetIdxCuve de mise à jour eeprom pour Idx Cuve. Valeur = " );
    Serial.println( idx );

    if ( idx < 0 ) return;
    if ( ( eepromDomoticz.iMemoryControlStart == MEMORY_CONTROL_START ) and 
        ( eepromDomoticz.iMemoryControlEnd != MEMORY_CONTROL_END ) and 
        ( eepromDomoticz.iIdxDomoticzCuve == idx ) ){
          // Tout est déjà à jour
           Serial.print( "--Fonction vFonctionSetIdxCuve : pas de mise à jour a faire" );
           return;
        } else {
          if ( idx == 0 ){
            //On efface eeprom
           // On efface pas eepromDomoticz.iIdxDomoticzCuve pour qu'il soit encore utilisable 
           eepromDomoticz.iMemoryControlStart = 0;
           eepromDomoticz.iMemoryControlEnd = 0;      
          } else {
           eepromDomoticz.iIdxDomoticzCuve = idx ;
           eepromDomoticz.iMemoryControlStart = MEMORY_CONTROL_START;
           eepromDomoticz.iMemoryControlEnd = MEMORY_CONTROL_END;
          }
        }
        
      EEPROM.begin( iTaille_eeprom );
      EEPROM.put( index_eepromDomoticz, eepromDomoticz );
      Serial.println( "Write to EEPROM : " );
      Serial.println( eepromDomoticz.iMemoryControlStart );
      Serial.println( eepromDomoticz.iIdxDomoticzCuve );
      Serial.println( eepromDomoticz.iMemoryControlEnd );
      EEPROM.end();
      
      return;
 }

Ce qu’il faut voir dans cette fonction c’est la manière de mettre à jour l’EEPROM. Il faut toujours faire : 


EEPROM.begin( iTaille_eeprom );
      EEPROM.put( index_eepromDomoticz, eepromDomoticz );
      EEPROM.end();

Attention car le nombre d’écritures dans cette mémoire est limitée. En usage normal cela ne pose pas de problème mais si par erreur ce code est placé dans une boucle sans fin cela va rapidement saturer les capacités d’écriture de cette mémoire.

Conclusion :


J’espère que cet article vous aura donné suffisamment d’éléments pour vous lancer dans l’aventure NodeMCU.
Il me reste encore de nombreuse améliorations à apporter, notamment dans les variables mémorisées dans le mémoire persistante pour pouvoir piloter de manière plus fine le comportement de la sonde au travers de commandes http.

Je compléterai cet article par un tableau qui résume les fonctions utiles des différentes classes utilisées. Je peux également vous envoyer le code complet du programme. 

Si vous comptez vous équiper sur Amazon, n’hésitez pas à cliquer sur les liens ci-dessous avant de faire vos achats. Cela n’aura aucun impact pour vous mais en me faisant gagner quelques centimes sur ma cagnotte cela m’encourage à partager le fruit de mon travail de recherche. 
Sur ebay les délais de livraison seront plus long mais les composants seront légèrement moins chers : 

  • Le nodeMCU : ICI
  • Les breadboard : ICI
  • Les modules HC-SR04 : ICI
  • Les cables de connexion DUPOND pour breadboard: ICI

Annexes : 

Fonctions utiles : 


Assemblage de la sonde : 

Une fois le prototype terminé sur breadboard, vous pourrez le mettre tel quel dans un boitier étanche que vous placerez au dessus de votre cuve, ou alors vous pourrez, comme je viens de le faire, vous amuser à souder le tout sur une plaque d'essais afin d’obtenir un montage plus durable.

La première chose à faire est de créer un boitier qui pourra recevoir le circuit avec les composants en faisant ressortir les détecteurs ronds du capteur de distance HC-SR04.

En ce qui concerne les composants, j'ai utilisé les résistances dont j'ai déjà parlé, mais comme la sonde ne sera pas alimentée par USB dans son mode de fonctionnement nominal, j'ai prévu un système d'alimentation qui me permettra d'utiliser un vieux chargeur dont la tension délivrée se site entre 30V et 9V. Ce montage est basé sur un composant qui transforme un courant de 9V à 30V (environ) en un courant stable de 5V : le L7805CV .
Le NodeMCU sera dont alimenté par la sortie 5V de ce composant, directement raccordé sur l'entrée VIN de l'ESP.

Le boitier : 

Pour fabriquer le boitier, comme mon objectif est de faire une sonde qui sera placée en milieu humide, je me suis servi d'une boite électrique étanche. J'ai placé dedans de quoi fixer la sonde sur un bras et aussi de quoi faire ressortir vers le bas les capteurs ultrason du HC-SR04.

Voici en photos ce que cela donne : 





Souder les composants : 


Je n'avais jamais soudé sur une plaque d'essais. Et franchement j'ai galéré. Une chance que lorsque j'ai commandé mon fer à souder, j'ai pris un kit complet avec une pompe à dessouder !
J'ai passé plusieurs soirées les yeux rivés sur ma loupe à essayer de souder sans déborder et sans faire de faux contacts tous ces composants.
Mais après plusieurs essais, j'ai réussi.
Pour les petits câbles qui servent de pont, j'ai pris les fils d'un câble ethernet.
Pour relier plusieurs "trous" de la plaque avec de la soudure, sans déborder, c'est juste une question de patience et de coup de main. J'ai quand même grillé deux L7805cv !
La meilleures méthode que j'ai trouvé c'est de souder chaque composant et de tester que ça marche, quitte à tout recâbler de manière temporaire. Dans un premier temps j'ai essayé de faire toutes les soudures. Et évidement, une fois l'alimentation électrique activée, rien n'a fonctionné ! Et là, impossible de détecter ce qui ne marche pas.
Donc mon conseil si vous ne maîtrisez pas la soudure, c'est d'y aller composant par composant et de tester à chaque fois le montage.

Voici au final ce que cela a donné :






J'ai commencé par fixer un connecteurs pour raccorder les câbles d'alimentation.
Ensuite on raccorde le L7805cv.  



Et pour finir on cable le tout tel que sur le schéma que j'ai indiqué dans l'article ci-dessus. La différence est que pour l'alimentation 5V, au lieu de la prendre sur la borne VU de l'ESP, on va la chercher sur la sortie 5V du L7805cv.
 Pour info, sur ce dernier, la broche centrale correspond au -.






Placer le tout dans le boitier : 


Une fois toute ces étapes terminées, il s'agit de placer le tout dans le boitier.

Voila ce à quoi cela ressemble : 




Les premières mesures : 


Premier tests réalisés sur une petite table :

Dans l'attente d'être installé dans ma cuve.



Données reçues 5/5 par domoticz ! Youpiii ! 





The End 


Soutenez la blogoculture ...


Le plus simplement du monde, si vous avez un achat à faire sur Amazon, accédez au site à partir de ce lien (que vous pouvez ajouter dans vos favoris)https://amzn.to/2nbe4sm




Soutenez la blogoculture ...


Le plus simplement du monde, si vous avez un achat à faire sur Amazon, accédez au site à partir de ce lien (que vous pouvez ajouter dans vos favoris)https://amzn.to/2nbe4sm


... mais aussi ...


Vous appréciez les articles frais et vitaminés de ce blog et vous voulez faire un geste pour encourager ce partage, saluer le travail, ou parce que vous y avez trouvé des choses utiles ( et que vous êtes sympa ) ?

... c'est possible et vous avez le choix !
Si vous avez un compte Paypal et quelques euros à offrir sans vous mettre sur la paille, subventionnez la culture domotique à l'ancienne !
Vous ne dépenserez pas un radis de plus en faisant un achat sur eBay à partir de ce lien.
Economisez du blé avec Amazon Prime ! Offre d'essais 1 mois gratuit (et renouvelable).
Soyez chou et aidez les petits producteurs de blog à se faire connaitre auprès de vos amis facebook !

Merci

Commentaires