Capteur temperature humidite wifi_arduino_jeedom

Capteur température humidité WiFi sur pile à moins de 10 euros – Arduino et Jeedom

Le matériel est prêt, le produit ressemble à un produit fini. Il ne reste plus que le fonctionnel et développer une application embarquée pour que le capteur soit enfin vivant. Je ne vous cache pas que dans la réalité, on développe en parallèle du matériel, ne serait-ce que pour tester le fonctionnement des composants du produit. En effet, entre le hardware et le software, l’un peut subvenir au besoin de l’autre…

Dans la suite de l’article, je vais vous présenter le principe du fonctionnement du capteur, de comment il vie et comment le paramétrer.

Enfin, je vous montrerai comment je l’ai intégré à ma domotique. Étant utilisateur de Jeedom, j’ai donc développé un plugin afin de recevoir les données du capteur et interagir avec.

Bien entendu tout sera disponible sur mon github. (D’ailleurs s’il manque des informations n’hésitez pas à poser un commentaire).

Principe de fonctionnement

Comme expliqué précédemment sur la partie hardware, le capteur va communiquer ses valeurs toutes les 2 heures. Pour cela :

  • le Timer va déclencher l’alimentation du capteur
  • Le capteur va récupérer sa configuration pour se connecter au WiFi
  • Le capteur fait ses mesures
  • Le capteur envoie les données en HTTP en appelant une page avec la méthode POST (Oui je sais, y a mieux en matière de communication non baveuse comme MQTT)
  • Le capteur dit au Timer de couper l’alimentation de se remettre à compter

Voici le Schéma de fonctionnement général :

schéma_principe_fonctionnement_espiot
Principe de fonctionnement du capteur

Ce diagramme d’action est assez simple et permet d’optimiser la consommation d’énergie du capteur. Dans le cas le plus défavorable, le temps d’éveil correspond à environ 6-7 secondes.

Ce diagramme ne montre que la partie en production. En réalité, toutes les fonctions du capteur (notamment pour la gestion de la configuration) ne sont pas traitées.

Entrer en mode configuration

Pour entrer en mode configuration, il faut « désactiver le timer« . Pour cela il suffit, de placer un cavalier entre le GND de la pile et le GND du MOSFET comme suit :

capteur T/H wifi mode configuration

Cavalier à placer pour le mode configuration

Une fois que le cavalier est placé, le module ne s’éteindra plus. Lorsque le capteur se connecte (ou pas) à votre borne WiFi, normalement, on demande au Timer de couper l’alimentation. Mais dans ce cas, :

  • L’alimentation n’est pas coupée.
  • On passe alors le module en mode Point d’accès
  • On monte un serveur Web contenant les pages de configuration.

Il suffit alors de:

  • Se connecter (avec un appareil WiFi) au point d’accès ESP-XXXX avec le compte admin/adminXXXX
  • Ouvrir un navigateur à l’adresse : http://192.168.4.1
  • Suivre les instructions.

espiot_ecran_de_configuration_generalespiot_ecran_de_configuration_WiFiespiot_ecran_de_configuration_update_Firmware

Liste d’écran de configuration du capteur T/H WiFi (ESPIOT)

L’apparence est encore sommaire mais totalement fonctionnel. Ca fait partie des points d’améliorations (ne serait-ce qu’une feuille de style ;)).

PS : une fois paramétrée, ne pas oublier de retiré le cavalier

Logiciel embarqué. Sketch Arduino

La suite, c’est du code pure. je vous laisse le lire, le copier ou l’analyser. Il n’est pas du tout optimisé, c’est une première version mais il a le mérite de fonctionner parfaitement. (plusieurs mois de fonctionnement sur cette version)

/**
 * ESPIOT
 *
 *  Created on: 21.10.2016
 *
 * VERSION 1.01a
 * CHANGELOG
 * 2017-01-29
 */
#include <stdint.h>
#include <HardwareSerial.h>
#include <Arduino.h>

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

#include <DHT.h>

#include <ArduinoJson.h>
#include "FS.h"

extern "C" {
#include <user_interface.h>
}

#define FPM_SLEEP_MAX_TIME 0xFFFFFFF

/* DEFINES
 *  
 */
#define DHTTYPE DHT11
#define DHTPIN  5
#define DONEPIN 14
Inclusions
/* 
 *  VARIABLES GLOBALES
 */
  const char* WIFISSIDValue;
  const char* WIFIpassword;
  String urlServer ;
  const char* HTTPUser;
  const char* HTTPPass ;
  const char* jsonIp;
  char ip1,ip2,ip3,ip4;
  char mask1,mask2,mask3,mask4;
  char gw1,gw2,gw3,gw4;
  
  bool connexionOK=false;
  bool configOK=false;

  bool cmdComplete=false;
  String SerialCMD;

  int retryConnexionOk,retryWifi;
  int countConnexion=0;
  bool EndCheck=false;
  bool ToReboot=false;
  char wificonfig[512];
  
  IPAddress ip,gateway,subnet;
  
  uint8_t MAC_array[6];
  char MAC_char[18];
  
  DHT dht(DHTPIN, DHTTYPE); 

  ESP8266WebServer server(80);
  char serverIndex[512] = "<h1><img src='/logo.png'><br>ESPIOT Config</h1><ul><li><a href='params'>Config ESPIOT</a></li><li><a href='update'>Flash ESPIOT</a></li></ul><br><br>Version: 1.0<br><a href='https://github.com/fairecasoimeme/' target='_blank'>Documentation</a>";
  char serverIndexUpdate[512] = "<h1>ESPIOT Config</h1><h2>Update Firmware</h2><form method='POST' action='/updateFile' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
  char serverIndexConfig[4092] = "<h1>ESPIOT Config</h1><h2>Config ESPIOT</h2><form method='POST' action='/config'>SSID : <br><input type='text' name='ssid'><br>Pass : <br><input type='password' name='pass'><br>@IP : <br><input type='text' name='ip'><br>Mask : <br><input type='text' name='mask'><br>@GW : <br><input type='text' name='gw'><br>Http user : <br><input type='text' name='userhttp'><br>Http pass : <br><input type='text' name='passhttp'><br>URL : <br><input type='text' name='url'><br><input type='submit' value='OK'></form>";
  const char* Style = "<style>body {  text-align: center; font-family:Arial, Tahoma;  background-color:#f0f0f0;}ul li { border:1px solid gray;  height:30px;  padding:3px;  list-style: none;}ul li a { text-decoration: none;}input{ border:1px solid gray;  height:25px;}input[type=text] { width:150px;  padding:5px;  font-size:10px;}#url {  width:300px;}</style>";
/* FONCTION POUR CHARGER LA CONFIG
 *  
 *  
 */
bool loadConfig() {
  File configFile = SPIFFS.open("/config.json", "r");
  if (!configFile) {
    Serial.println("Failed to open config file");
    return false;
  }

  size_t size = configFile.size();
  if (size > 1024) {
    Serial.println("Config file size is too large");
    return false;
  }

  // Allocate a buffer to store contents of the file.
  std::unique_ptr<char[]> buf(new char[size]);

  // We don't use String here because ArduinoJson library requires the input
  // buffer to be mutable. If you don't use ArduinoJson, you may as well
  // use configFile.readString instead.
  configFile.readBytes(buf.get(), size);

  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& json = jsonBuffer.parseObject(buf.get());

  if (!json.success()) {
    Serial.println("Failed to parse config file");
    return false;
  }
  
  if (json.containsKey("WIFISSID")) 
  {
    WIFISSIDValue = json["WIFISSID"];
  }
  
  if (json.containsKey("WIFIpass")) 
  {
    WIFIpassword = json["WIFIpass"];
  }
  
  if (json.containsKey("Ip")) 
  {
    jsonIp = json["Ip"];
    ip.fromString(jsonIp);
  }
  
  if (json.containsKey("Mask")) 
  {
    jsonIp = json["Mask"];
    subnet.fromString(jsonIp);
  }
  
  if (json.containsKey("GW")) 
  {
    jsonIp = json["GW"];
    gateway.fromString(jsonIp);
  }
  
  if (json.containsKey("urlServer")) 
  {
     urlServer = json["urlServer"].asString();
  }
  if (json.containsKey("HTTPUser")) 
  {
     HTTPUser = json["HTTPUser"].asString();
  }
  if (json.containsKey("HTTPPass")) 
  {
     HTTPPass = json["HTTPPass"].asString();
  }

  uint8_t tmp[6];
  char tmpMAC[18];
  WiFi.macAddress(tmp);
  for (int i = 0; i < sizeof(tmp); ++i){
    sprintf(tmpMAC,"%s%02x",tmpMAC,tmp[i]);
  }
  json["ID"] =  String(tmpMAC);
  json.printTo(wificonfig,sizeof(wificonfig));

  sprintf(serverIndexConfig,"<html><head></head><body><h1>ESPIOT Config</h1><h2>@MAC : %s</h2><form method='POST' action='/config'>SSID : <br><input type='text' name='ssid' value='%s'><br>Pass : <br><input type='password' name='pass'><br>@IP : <br><input type='text' name='ip' value='%s'><br>Mask : <br><input type='text' name='mask' value='%s'><br>@GW : <br><input type='text' name='gw' value='%s'><br>URL : <br><input type='text' name='url' value='%s' style='width:300px;'><br>Http user : <br><input type='text' name='userhttp' value='%s'><br>Http pass : <br><input type='text' name='passhttp' value='%s'><br><br><input type='submit' value='OK'></form></body></html>",json["ID"].asString(),json["WIFISSID"].asString(),json["Ip"].asString(),json["Mask"].asString(),json["GW"].asString(),json["urlServer"].asString(),json["HTTPUser"].asString(),json["HTTPPass"].asString());
    return true;
}
Chargement de la config
void PowerDown()
{
  digitalWrite(DONEPIN,HIGH);
  delay(1);
  digitalWrite(DONEPIN,LOW);
  Serial.print(" power down ");

}
Fonction qui permet de couper l'alimentation par le Timer
float readADC_avg()
{
  int battery=0;
  for (int i=0;i<10;i++)
  {
      battery = battery + analogRead(A0);
  }
  return (((battery/10)*4.2)/1024);
  
}
Fonction de lecture de la tension de pile
void setupWifiAP()
{
  WiFi.mode(WIFI_AP);

  uint8_t mac[WL_MAC_ADDR_LENGTH];
  WiFi.softAPmacAddress(mac);
  String macID = String(mac[WL_MAC_ADDR_LENGTH - 2], HEX) +
                 String(mac[WL_MAC_ADDR_LENGTH - 1], HEX);
  macID.toUpperCase();
  String AP_NameString = "ESPIOT-" + macID;

  char AP_NameChar[AP_NameString.length() + 1];
  memset(AP_NameChar, 0, AP_NameString.length() + 1);

  for (int i=0; i<AP_NameString.length(); i++)
    AP_NameChar[i] = AP_NameString.charAt(i);

  String WIFIPASSSTR = "admin"+macID;
  char WIFIPASS[WIFIPASSSTR.length()+1];
  memset(WIFIPASS,0,WIFIPASSSTR.length()+1);
  for (int i=0; i<WIFIPASSSTR.length(); i++)
    WIFIPASS[i] = WIFIPASSSTR.charAt(i);

  WiFi.softAP(AP_NameChar,WIFIPASS );
}
Configuration du module en mode point d'accès
void serverWebCfg()
{
  char* host = "espiot";
  Serial.println("HTTP server started");
    MDNS.begin(host);


    server.on("/list", HTTP_GET, handleFileList);
    server.on("/logo.png", handleLogo); 
    server.on("/", HTTP_GET, [](){
      server.sendHeader("Connection", "close");
      server.send(200, "text/html", strcat(serverIndex,Style));
    });
    server.on("/update", HTTP_GET, [](){
      server.sendHeader("Connection", "close");
      server.send(200, "text/html", strcat(serverIndexUpdate,Style));
    });
    server.on("/params", HTTP_GET, [](){
      server.sendHeader("Connection", "close");
      server.send(200, "text/html", strcat(serverIndexConfig,Style));
    });
     server.on("/config", HTTP_POST, [](){
      
       String StringConfig;
       String ssid=server.arg("ssid");
       String pass=server.arg("pass");
       String ip=server.arg("ip");

       String mask=server.arg("mask");

       String gw=server.arg("gw");

       String userhttp=server.arg("userhttp");
       String passhttp=server.arg("passhttp");
       String url=server.arg("url");
       uint8_t tmp[6];
       char tmpMAC[18];
       WiFi.macAddress(tmp);
       for (int i = 1; i < sizeof(tmp); ++i){
        sprintf(tmpMAC,"%s%02x:",tmpMAC,tmp[i]);
       }

       StringConfig = "{\"ID\":\""+String(tmpMAC)+"\",\"WIFISSID\":\""+ssid+"\",\"WIFIpass\":\""+pass+"\",\"Ip\":\""+ip+"\",\"Mask\":\""+mask+"\",\"GW\":\""+gw+"\",\"urlServer\":\""+url+"\",\"HTTPUser\":\""+userhttp+"\",\"HTTPPass\":\""+passhttp+"\"}";       
       
       StaticJsonBuffer<512> jsonBuffer;
       JsonObject& json = jsonBuffer.parseObject(StringConfig);
       File configFile = SPIFFS.open("/config.json", "w");
       if (!configFile) {
        Serial.println("Failed to open config file for writing");
       }else{
         json.printTo(configFile);
         delay(5000);
         ToReboot=true;
      }
      
      server.send(200, "text/plain", "OK");
    });
   
    server.on("/updateFile", HTTP_POST, [](){
      server.sendHeader("Connection", "close");
      server.send(200, "text/plain", (Update.hasError())?"FAIL":"OK");
      ESP.restart();
    },[](){
      HTTPUpload& upload = server.upload();
      if(upload.status == UPLOAD_FILE_START){
        Serial.setDebugOutput(true);
        WiFiUDP::stopAll();
        Serial.printf("Update: %s\n", upload.filename.c_str());
        uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
        if(!Update.begin(maxSketchSpace)){//start with max available size
          Update.printError(Serial);
        }
      } else if(upload.status == UPLOAD_FILE_WRITE){
        if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
          Update.printError(Serial);
        }
      } else if(upload.status == UPLOAD_FILE_END){
        if(Update.end(true)){ //true to set the size to the current progress
          Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
        } else {
          Update.printError(Serial);
        }
        Serial.setDebugOutput(false);
      }
      yield();
    });
    server.begin();
    MDNS.addService("http", "tcp", 80);

}
Gestion du serveur Web
void setup() {

    pinMode(DONEPIN,OUTPUT);
    digitalWrite(DONEPIN,LOW);

    Serial.begin(9600);
    Serial.setDebugOutput(true);
      
    if (!SPIFFS.begin()) {
      Serial.println("Failed to mount file system");
      return;
    }

    if (!loadConfig()) {
      Serial.println("Failed to load config");
    } else {
      configOK=true;
      Serial.println("Config loaded");
    }

    
    if (configOK)
    {

      if (WiFi.status() != WL_CONNECTED) {
        WiFi.begin(WIFISSIDValue, WIFIpassword);
       //  WiFi.config(ip, gateway, subnet); //SI ON UTILISE LA CONFIG POUR ETRE EN IP FIXE
      }
     
      WiFi.macAddress(MAC_array);
      for (int i = 0; i < sizeof(MAC_array); ++i){
        sprintf(MAC_char,"%s%02x:",MAC_char,MAC_array[i]);
      }
      Serial.println(WiFi.localIP());
      dht.begin(); 
      delay(1000);    
    }else{
      setupWifiAP();
      Serial.println("Config not ok");
      
    }
  
     retryWifi=0;  
     retryConnexionOk=0;
     countConnexion=0;
     EndCheck=false;
     ToReboot=false;

}
La fonction de configuration

void loop() 
{
   server.handleClient();
   if (ToReboot)
   {
     ESP.restart();
   }

   if (!EndCheck)
   {
     if (WiFi.status() != WL_CONNECTED) 
     {
        
        countConnexion=0;
        if (retryWifi <40)
        {
          delay(500);
          retryWifi++;
          
        }else{
          EndCheck=true;
          PowerDown();
          setupWifiAP();
          serverWebCfg();
          Serial.println("délai dépassé"); 
        }
      }else{
         connexionOK=true;
         EndCheck=true;
         serverWebCfg();
        Serial.println("Connexion OK"); 
      }
   }
     
    
   if ((connexionOK) && (countConnexion==0))
   {

       float humidity, temp, tension;   
        for (int i = 0; i < 3; ++i){
          humidity = dht.readHumidity();          // Read humidity (percent)
          temp = dht.readTemperature();  
          tension=readADC_avg();
          Serial.print("temp:");
          Serial.print(temp);
          Serial.print("hum:");
          Serial.print(humidity);
          if (String(temp)!="nan")
          {
            break;  
          }
          delay(1000);
        }
        
        if (retryConnexionOk<3)
        {
            connexionOK=false;
            HTTPClient http;
           
            // configure traged server and url
            http.begin(urlServer); //HTTP
            Serial.print("[HTTP] begin...\n");
            //Serial.print(urlServer);
            
            if ((HTTPUser != "") || (HTTPPass != ""))
            {
              http.setAuthorization(HTTPUser, HTTPPass);
            }
            Serial.print("[HTTP] GET...\n");
            // start connection and send HTTP header
            http.addHeader("Content-Type", "application/x-www-form-urlencoded");
            int httpCode = http.POST("id="+String(MAC_char)+"&temp="+String(temp)+"&hum="+String(humidity)+"&v="+String(tension));
        
            // httpCode will be negative on error
            if(httpCode > 0) {
                if(httpCode == HTTP_CODE_OK) {

                    
                }
                Serial.println("HTTP OK");
                PowerDown();
            } else {
              Serial.println("HTTP NOK");
              PowerDown();
              // Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
            }
           
            http.end();
            retryConnexionOk++;
        }else{
          PowerDown();
        }
        countConnexion++;
    }

    cmdComplete=false;
    while (Serial.available()>0 && cmdComplete ==false)
    {
       char car;
        car=Serial.read();
        if (car==0x02)
        {
          SerialCMD="";
        }else if (car==0x03)
        {
          cmdComplete=true;
        }else{
           SerialCMD +=car;
        }     
    }

    if (cmdComplete)
    {
      cmdComplete=false;
      if (SerialCMD=="reset")
      {
         ESP.restart();
      }else if (SerialCMD=="loadconfig")
      {  
        Serial.print(wificonfig);
      }
      else{
        StaticJsonBuffer<512> jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject(SerialCMD);
        if (!json.success()) {
          Serial.println("Failed to parse config file");
        }else{
          Serial.println("OK");
          File configFile = SPIFFS.open("/config.json", "w");
          if (!configFile) {
            Serial.println("Failed to open config file for writing");
          }else{
            json.printTo(configFile);
            delay(2000);
            
          }
          
        }
      }
    }
}
Boucle principale

Le code précédent est destiné au capteur de type DHTXX. il est encore en mode debug (crade) mais il va évoluer rapidement quand j’aurai fini tous les tests fonctionnels. D’ailleurs en enlevant les écritures sur la COM à 9600bauds, je risque de gagner du temps en mode éveillé et d’économiser de la batterie.

Vous pourrez retrouver toutes les nouvelles versions sur :

Télécharger le code Arduino

Plugin Jeedom

Ça y est, mon capteur communique. Il ne reste plus qu’à développer un petit plugin Jeedom pour l’intégrer à ma domotique. Comme pour le plugin sur la domobase et parce que j’aime bien apprendre par l’exemple, je suis parti d’un plugin déjà existant. Celui du capteur virtuel.

Dans un premier temps, j’ai développé le plugin en mode synchronisé. C’est à dire que dès que le capteur envoie ses données, je traite les informations directement à réception. Mais je me suis rendu compte que le temps de traitement rallongeait de quelques secondes « l’état éveillé » de mon capteur. (du coup, une consommation plus importante). Je suis donc passé à une autre méthode.

J’ai corrigé mon code pour passer en mode désynchronisé. Désormais, lorsque mon capteur envoie les données, je les stocke dans un fichier (un fichier par capteur) sous format JSON. Jeedom viendra analyser les données dans un second temps pour les enregistrer.

Exemple de données en JSON collectées:

{"Temperature":"2.90","Humidity":"77.42","Pression":"998.33","Tension":"2.86"}

Le traitement des données est plus rapide (simple écriture dans un fichier), le capteur se rendort plus rapidement et je gagne en consommation d’énergie.

Ne maitrisant pas encore tous les rouages de Jeedom, n’hésitez pas à me donner des conseils sur une meilleure utilisation des plugins. Je dois dire que la documentation est assez restreinte ou alors je ne sais pas chercher où il faut …

Voici à quoi ressemble l’affichage des capteurs dans Jeedom :

Affichage des mesures de capteurs ESPIOT dans Jeedom

jeedom_pluginsListe des plugins dans mon Jeedom

jeedom_plugin_espiotListe de mes capteurs ESPIOT

jeedom_plugin_espiot_generaljeedom_plugin_espiot_commandesConfiguration d’un capteur ESPIOT

La configuration d’un capteur est très simple : il suffit de donner un nom au capteur puis de rentrer son adresse MAC. Les commandes sont normalement créées. Si ce n’est pas le cas, les noms de commandes sont:

  • Temperature
  • Humidity
  • Tension
  • Pression (uniquement pour le BME280)

Téléchargement du plugin Jeedom

Conclusion

Enfin, cette partie signe la fin du projet de mon capteur température/humidité/(pression) sur pile communiquant en WiFi. J’avais vraiment à cœur de réussir un objet abouti. Ce projet m’a permis de jouer avec plein de concept, allant de l’électronique jusqu’à de la mécanique en passant par du logiciel embarqué et du logiciel  » haut niveau ».

J’ai déjà en tête plein de points d’amélioration, mais je vais laisser vivre pendant un petit moment le projet pour me consacrer à d’autres projets tout aussi intéressant. J’espère obtenir de votre part des retours sur mon travail, des critiques (constructives), des remarques…. bref avoir une interaction avec vous. C’est ce que j’attends de ce genre de projet partagé sur le web. Je pense que c’est le meilleur moyen de progresser.

Une fois toutes les remarques synthétisée, j’appliquerai certaines modifications pour que le capteur soit optimisé. Je mettrai, bien entendu, les modifications sur mon Github.

Les tests que j’ai effectué sont déjà prometteurs sur la durée :

jeedom_espiot_stats

Historiques des mesures de température

Cela fait déjà un peu plus de 3mois que le capteur envoie des données sans interruption. La consommation d’énergie suit les prédictions théoriques. La tension des piles est de 3.99v. Y a encore plein d’énergies !!!

Vous avez été nombreux à visiter et parcourir ce projet et je vous en remercie. Voici le fil d’Ariane de tout le projet :

Lien github

 

[Total : 7    Moyenne : 4/5]

One comment

  1. Bonjorur,

    je suis stupéfait de votre développement. Pourquoi ne pas avoir utilisé le logiciel ESP easy?
    Est-ce que vous vendez ce capteur ?
    Si oui, je suis intéressé.

    Merci

Leave a Reply

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *