ESP8266 (Web Serveur – Partie 2) : Interaction entre le code Arduino et l’interface HTML

Voici la seconde partie des tutoriels consacrés à la création d’une interface HTML stockée dans la zone SPIFFS d’un ESP8266 fonctionnant comme un serveur web. Dans ce tutoriel, nous allons ajouter le code nécessaire à la mise en place des interactions entre l’interface Web et le code Arduino. Nous allons mettre en place l’actualisation automatique de la table des mesures et des afficheurs. Nous allons gérer les commandes pour activer et désactiver des GPIO. Enfin, certaines ressources seront placées dans la zone SPIFFS (fichiers CSS et JS de Bootstrap et jquery) pour diminuer le temps de chargement.

Lire les autres articles du projet

Partie Sujets abordés Dépôt GitHub Article
Partie 1
  • Comment préparer le code HTML de interface Web
    • Pour cela, nous utiliserons le langage Pug (auparavant appelé Jade) qui permet de simplifier l’écriture
    • Nouvelles notions d’HTML : menu de navigation, image, fixer le bas de page (footer), meilleure gestion “responsive” pour les petits écrans
  • Comment changer le thème de l’interface
    • Comment stocker le choix pour recharger le thème au prochain chargement de l’interface
  • (1) Comment préparer et utiliser la zone SPIFFS pour stocker les fichiers HTML, JS, CSS, images
    • et les envoyer sur l’ESP8266
    • Premier test de l’interface WEB sur l’ESP8266
 
Partie 3 Comment récupérer l’heure depuis internet. Lire l’article
Partie 4 Comment créer un historique de mesures. Lire l’article

  • Manipuler les données à l’aide de la librairie ArduinoJSON
  • Enregistrer des données (historique de mesure) dans un fichier sur la zone SPIFFS
  • Recharger le fichier historique au démarrage de l’ESP8266
Partie 5 Comment ajouter des graphiques et des jauges Google Charts. Lire l’article

  • Evolution de la température et de l’humidité moyenne sur les 7 dernières heures (histogramme en barre)
  • Affichage de la mesure courante sous la forme d’une jauge thématique : thermomètre (température), goutte d’eau (humidité), jauge (pression atmosphérique)

Un peu de JSON avant de commencer

JSON est un méthode pour structurer des données pour le stockage et le transfert. Il remplace très avantageusement le XML surtout dans le langage Javascript ou il est supporté nativement. Il est également très simple de créer une structure JSON dans le code Arduino. Sans trop rentrer dans les détails, voici comment ça fonctionne :

  • Les données sont stockées sous la forme clé:valeur
  • Chaque couple doit être séparé par une virgule, sauf le dernier couple
  • Plusieurs types de valeurs existent
    • une chaine
    • un tableau []
    • une structure de données libre à condition de respecter le formaliste JSON clé:valeur, clé:{}, clé:[]

Ce qui donne par exemple

{
  "cle1":"valeur1",
  "cle2":"[1,2,3,4]",
  "cle3":{
    "clea":"valeura",
    "cleb":"['a','b','c']"
  }
}

Ou un tableau

[{
    "temperature": "18",
    "humidite": "52"
}, {
    "temperature": "19",
    "humidite": "52"
}]

Pour tester vos JSON, je vous conseille http://jsonlint.com/. C’est suffisant pour réaliser de nombreux projets, mais pour en savoir plus, vous pouvez consulter cet article en français de nos amis d’alsacreation.

Actualisation automatique de la table et des afficheurs de mesures

Autant laisser le navigateur actualiser régulièrement les mesures faites à l’aide de l’ESP8266. Nous allons reprendre le code de l’interface développée dans le tutoriel précédent et le faire évoluer pour que toutes les interactions soient gérées coté client (sur le navigateur internet).

Code HTML

Reprenons le code de l’onglet Mesures. On a ajouté 3 boutons de navigations qui permettent de créer 3 afficheurs. Chaque bouton possède un badge repéré par un identifiant (#temperature, #humidite et #pa) qui va nous permettre d’actualiser la mesure.

Ensuite un tableau composé de trois colonnes permet d’afficher le type de mesure, la valeur courante et une valeur précédente. Chaque colonne est associée à un champ de données (data-field) qui sera associé à un JSON envoyé par le code Arduino. Le contenu de chaque colonne est mis en forme par une fonction indiquée dans l’attribut data-formatter.

ul.nav.nav-pills
  li.active
    a(href='#')
      #temperature.span.badge.pull-right -
      |  Température
  li
    a(href='#')
      #humidite.span.badge.pull-right -
      |  Humidité
  li
    a(href='#')
      #pa.span.badge.pull-right -
      |  Pression atmosphérique
table(id='tab_mesures' data-toggle='table' data-show-colunns='true')
  thead
    tr
      th(data-field='mesure' data-align='left' data-sortable='true' data-formatter='labelFormatter') Mesure
      th(data-field='valeur' data-align='left' data-sortable='true' data-formatter='valueFormatter') Valeur
      th(data-field='precedente' data-align='left' data-sortable='true' data-formatter='vpFormatter') Valeur Précédente

Tout est prêt du coté de l’interface Web

Code Javascript

Le plugin Bootstrap-table va nous simplifier la vie pour actualiser les données. En effet, il va nous suffire d’appeler la méthode refresh et d’indiquer l’url de retour des données (toutes les fonctions sont présentée ici). Par contre, il faut s’assurer que la table existe avant de pouvoir commencer,  on doit donc inclure cet appel dans un test $(document).ready(). On en profitera également pour appeler une fonction qui permettra d’actualiser les afficheurs. Voici le code

$(document).ready(function(){
  var Timer_UdpateMesures;
  // Actualise les données dès que la page est chargée - Update data when the page is ready  
  $('#tab_mesures').bootstrapTable('refresh',{silent: true, showLoading: false, url: '/tabmesures.json'})
  updateMesures();
})

Petit problème, cette solution ne fonctionne pas dans le cas ou la table est intégrée dans un onglet (div tab-pane). Heureusement, la solution a été présentée dans l’issue 1760 sur Github. Sur la classe shown.bs.tab de la balise a[data-toggle=’tab’], on test la présence de l’attribut tab_mesures, celui qui correspond à l’onglet contenant la table des mesures. Si c’est le cas, on lance l’actualisation de la table ainsi que les 3 afficheurs.

var Timer_UdpateMesures;
$('a[data-toggle=\"tab\"]').on('shown.bs.tab', function (e) {   
  //On supprime tous les timers lorsqu'on change d'onglet
  clearTimeout(Timer_UdpateMesures);  
  var target = $(e.target).attr("href")  
  console.log('activated ' + target );  
  
  // IE10, Firefox, Chrome, etc.
  if (history.pushState) {
    window.history.pushState(null, null, target);
  } else { 
    window.location.hash = target;
  }        
  if (target=='#tab_mesures')  {
    $('#table_mesures').bootstrapTable('refresh',{silent:true, url:'/tabmesures.json'}); 
    updatesMesures();
  }  
});

Pour actualiser à intervalle régulier les données, on va donc créer un Timer qui va exécuter une fonction au bout d’une minute (6000ms) par exemple. Pour que le Timer soit appelé de manière récurrente, il suffit de le relancer à chaque fois que la table des mesures vient d’être actualisée (on.(‘load-success.bs.table’)). On testera également que l’onglet de la table soit actif. On obtient le code suivant :

// Créé un timer qui actualise les données régulièrement - Create a timer than update data every n secondes
$('#tab_mesures').on('load-success.bs.table',function (e,data){
  //console.log("tab_mesures loaded");
  Timer_UdpateMesures=setTimeout(function(){
    $('#tab_mesures').bootstrapTable('refresh',{silent: true, showLoading: false, url: '/tabmesures.json'});
    updateMesures();
  },6000);                
});

Dans la fonction updateMesures(), on va donc faire un appel $.getJSON qui prend en paramètre une URL, ici /mesures.json .

Remarque. On pourrait extraire les données lors de la mise en forme de la table mais j’ai préféré introduire une nouvelle fonction jquery.

Dès que les données sont réceptionnées, la fonction est exécutée. La méthode jquery html permet d’actualiser le contenu du badge. Comme c’est un objet JSON, on accède à la valeur par data.nom_element. Pour faciliter la mise au point, on peut également appeler la fonction fail. On envoi vers la console l’objet JSON. Pour pouvoir le lire, on utilise la fonction Javascript JSON.stringify.

function updateMesures(){
  $.getJSON('/mesures.json', function(data){
    //console.log("Mesures envoyees : " + JSON.stringify(data) + "|" + data.t + "|" + data.h + "|" + data.pa) ;
    $('#temperature').html(data.t);
    $('#humidite').html(data.h);
    $('#pa').html(data.pa); 
  }).fail(function(err){
    console.log("err getJSON mesures.json "+JSON.stringify(err));
  });
};

Il ne reste plus qu’à mettre en forme l’affichage des colonnes de la table. Pour bien comprendre ce qui se passe, voici le JSON renvoyé par l’ESP8266. Le plugin Bootstrap-table attend un tableau de données. Si ce n’est pas le cas, vous aurez une erreur renvoyée par la fonction .fail()

[{
    "mesure": "Température",
    "valeur": "21.40",
    "unite": "°C",
    "glyph": "glyphicon-indent-left",
    "precedente": "0.00"
}, {
    "mesure": "Humidité",
    "valeur": "32.00",
    "unite": "%",
    "glyph": "glyphicon-tint",
    "precedente": "0.00"
}, {
    "mesure": "Pression Atmosphérique",
    "valeur": "990.48",
    "unite": "mbar",
    "glyph": "glyphicon-dashboard",
    "precedente": "0.00"
}]

Pour chaque ligne, on va donc récupérer :

  • Le libellé de la mesure
  • La valeur actuelle
  • L’unité
  • Le symbole, cela évite un test coté client (navigateur) et le code Arduino est plus vite téléchargé en cas de modification que les fichiers SPIFFS.
  • Une valeur précédente

On va maintenant créer une fonction qui va s’occuper de créer le libellé qui va correspond à chaque colonne. Pour la colonne Mesure, on appel la fonction labelFormatter().

Remarque. On pourrait codifier le type de mesure de manière à aller chercher le libellé dans un tableau ou un fichier JSON pour chaque langue. Ici c’est juste un exemple pour montrer l’architecture du système.

On réalise un test sur le libellé et on construit une chaine qui va contenir le libellé. On y ajoute une balise span qui va ajouter juste avant le libellé (pull-left) un icône (glyphicon) contenu dans le JSON (row.glyph). Vous trouverez tous les glyphicons proposés par Bootstrap ici. Sinon vous pouvez également utiliser la librairie Fontawesome.

function labelFormatter(value, row){
  var label = "";
  if ( value === "Température" ) {
    label = value + "<span class='glyphicon " + row.glyph + " pull-left'></span>";
  } else if ( value === "Humidité" ) {
    label = value + "<span class='glyphicon " + row.glyph + " pull-left'></span>";
  } else if ( value === "Pression Atmosphérique" ) {
    label = value + "<span class='glyphicon " + row.glyph + " pull-left'></span>";
  } else {
    label = value;
  } 
    return label;
}

Passons à la mesure actuelle qui est mise en forme par la fonction valueFormatter(). Comme on dispose du contenu de la ligne, on peut faire un test pour déterminer si la valeur actuelle est supérieure ou inférieure à la précédente. On peut ainsi construire un libellé de la forme : valeur + unité + icône (haut / bas).

function valueFormatter(value, row){
  //console.log("valueFormatter");
  var label = "";
  if ( row.valeur > row.precedente ) {
    label = value + row.unite + "<span class='glyphicon glyphicon-chevron-up pull-right'></span>";
  } else { 
    label = value + row.unite + "<span class='glyphicon glyphicon-chevron-down pull-right'></span>";
  }
  return label;
}

Enfin pour la valeur précédente, on assemble simplement value + unite.

function vpFormatter(value, row){
  //console.log("valueFormatter");
  var label = "";
  if ( row.valeur > row.precedente ) {
    label = value + row.unite
  } else { 
    label = value + row.unite
  }
  return label;
}

Tout est prêt du coté de l’interface Web, passons au code Arduino coté ESP8266.

Code Arduino

On va ajouter des fonctions qui seront appelées lorsque le serveur reçoit une requête sur /mesures.json et tabmesures.json (juste avant de démarrer le serveur).

server.on("/tabmesures.json", sendTabMesures);
server.on("/mesures.json", sendMesures);

Pour les deux fonctions, on va créer une chaine texte en respectant le formaliste JSON. Pour la fonction sendMesures(), ce sera objet simple dont voici un exemple

{“t”:”21.50″,”h”:”31.80″,”pa”:”990.53″}

ou sous une forme plus lisible

{
  "t":"21.50",
  "h":"31.80",
  "pa":"990.53"
}

Comme vous pouvez le voir dans le code, chaque clé et valeur doit être encadrée par un double guillemet (“). Il faut donc le faire précéder du caractère échappatoire pour que la chaîne puisse être compilée (\”). On appel ensuite la méthode server.send() pour envoyer l’objet JSON au client.

void sendMesures() {
  String json = "{\"t\":\"" + String(t) + "\",";
  json += "\"h\":\"" + String(h) + "\",";
  json += "\"pa\":\"" + String(pa) + "\"}";

  server.send(200, "application/json", json);
  Serial.println("Mesures envoyees");
}

Pour la table, on doit envoyer un objet constitué d’un tableau, chaque ligne étant un objet contenant les informations nécessaires (libellé, valeur actuelle, unité, symbole, valeur précédente). La chaine est plus délicate à construire. Pour le moment on envoi 0 pour la valeur précédente.

void sendTabMesures() {
  double temp = 0;      // Récupère la plus ancienne mesure (temperature) - get oldest record (temperature)
  String json = "[";
  json += "{\"mesure\":\"Température\",\"valeur\":\"" + String(t) + "\",\"unite\":\"°C\",\"glyph\":\"glyphicon-indent-left\",\"precedente\":\"" + String(temp) + "\"},";
  temp = 0;             // Récupère la plus ancienne mesure (humidite) - get oldest record (humidity)
  json += "{\"mesure\":\"Humidité\",\"valeur\":\"" + String(h) + "\",\"unite\":\"%\",\"glyph\":\"glyphicon-tint\",\"precedente\":\"" + String(temp) + "\"},";
  temp = 0;             // Récupère la plus ancienne mesure (pression atmospherique) - get oldest record (Atmospheric Pressure)
  json += "{\"mesure\":\"Pression Atmosphérique\",\"valeur\":\"" + String(pa) + "\",\"unite\":\"mbar\",\"glyph\":\"glyphicon-dashboard\",\"precedente\":\"" + String(temp) + "\"}";
  json += "]";
  server.send(200, "application/json", json);
  Serial.println("Tableau mesures envoyees");
}

Petit conseil. N’hésitez pas à bien découper votre chaîne et allez dans le navigateur pour récupérer le JSON envoyé. Une virgule en trop ou manquante et rien ne fonctionne. Pensez également à jsonlint pour vérifier vos JSON.

esp8266 web server http post json console google chrome bootstrap

Piloter le GPIO depuis l’interface Web

Il n’y a rien de plus compliqué pour piloter le GPIO depuis l’interface WEB, on va envoyer une requête HTTP de type POST à l’aide de la commande jsquery $.post(). En retour, on actualisera l’affichage du GPIO (On ou Off) en fonction de la réponse envoyée par le serveur.

Code HTML

On va profiter de la puissance de la librairie jquery pour intercepter un click sur le bouton de commande (ON, OFF)  de chaque GPIO. Le code HTML devient très simple.

.col-xs-6.col-md-4
  #D5_On.button.btn.btn-success.btn-lg(type='button') ON
.col-xs-6.col-md-4
  #D5_Off.button.btn.btn-danger.btn-lg(type='button') OFF

Code Javascript

Le code Javascript va se dérouler en deux temps. Tout d’abord on intercepte un click sur le bouton à l’aide de la commande jquery $(‘#Dx_Etat’). Sur un click, on appel la fonction setBouton et on lui passe les paramètres (GPIO, Etat). L’état prendra la valeur 1 pour activer la sortie et 0 pour l’arrêter !

$('#D5_On').click(function(){ setBouton('D5','1'); });
$('#D5_Off').click(function(){ setBouton('D5','0'); });

Ensuite la fonction setBouton envoi une requête HTTP de type POST. Pour cela on utilise la fonction $.post de jquery (documentation officielle). Voici sont fonctionnement :

  • On construit d’URL qui contient les paramètres et les valeurs. On pourrait aussi passer un JSON mais c’est plus facile de la gérer du coté Arduino avec la méthode server.arg(“xx”). L’URL prendra la forme gpio?id=id_gpio@etat=etat_gpio
  • La fonction done permet d’exécuter du code dès qu’une réponse valide est réceptionnée du serveur
  • La fonction fail permet de réaliser un traitement en cas d’erreur. Pratique pour la mise au point

Le traitement est très simple, par convention, le code Arduino renvoi la demande complétée par un drapeau (flag) success qui prend la valeur 1 si l’ESP8266 a correctement réalisée la requête demandée, 0 si ce n’est pas le cas. On pourrait imaginer d’autres états et modifier l’affichage en conséquence ou faire apparaître une notification…

function setBouton(id, etat){
  $.post("gpio?id=" + id + "&etat=" + etat).done(function(data){
    //console.log("Retour setBouton " + JSON.stringify(data)); 
    var id_gpio = "#" + id + "_etat";
    //console.log(id_gpio);
    if ( data.success === "1" ) {
      if ( data.etat === "1" ) {
        $(id_gpio).html("ON");
      } else {
        $(id_gpio).html("OFF");
      }  
    } else {
      $(id_gpio).html('!');
    }      
  }).fail(function(err){
    console.log("err setButton " + JSON.stringify(err));
  });
}

Code Arduino

C’est exactement la même chose que pour les mesures. On appel la fonction updateGpio() lorsque le serveur reçoit une demande sur /gpio.

server.on("/gpio", updateGpio);

On va donc récupérer les paramètres passés au serveur (gpio, etat) à l’aide de la méthode server.arg(“id”). Ensuite, c’est de la cuisine classique. On doit juste faire attention car on récupère une chaine de caractère, il faut donc faire une petite conversion pour pointer vers la bonne broche (Pin). Peu importe le traitement, ce qui importe, c’est de répondre au client, car il attend sa réponse avec impatience. On construit un JSON dans lequel on recopie (par exemple) la demande et on ajoute un succès (success) qui eut prendre 1s si OK, 0 si KO, une autre valeur en cas d’erreur… et on envoi le tout avec un server.send().

void updateGpio(){
  String gpio = server.arg("id");
  String etat = server.arg("etat");
  String success = "1";
  int pin = D5;
 if ( gpio == "D5" ) {
      pin = D5;
 } else if ( gpio == "D7" ) {
     pin = D7;
 } else if ( gpio == "D8" ) {
     pin = D8;  
 } else {   
      pin = D5;
  }
  Serial.println(pin);
  if ( etat == "1" ) {
    digitalWrite(pin, HIGH);
  } else if ( etat == "0" ) {
    digitalWrite(pin, LOW);
  } else {
    success = "1";
    Serial.println("Err Led Value");
  }
  
  String json = "{\"gpio\":\"" + String(gpio) + "\",";
  json += "\"etat\":\"" + String(etat) + "\",";
  json += "\"success\":\"" + String(success) + "\"}";
    
  server.send(200, "application/json", json);
  Serial.println("GPIO mis a jour");
}

Ce qui donne par exemple pour activer la broche D7 l’objet JSON suivant

{"gpio":"D7","etat":"1","success":"1"}

Ajout des ressources dans le système de SPIFFS

Il est possible d’éviter d’aller chercher les ressources sur internet (jquery, bootstrap, bootstrap-table…) en embarquant toutes les resources dans le répertoire Data. Après avoir fait quelques tests, il n’y a pas vraiment de gain en terme de performance. L’ESP8266 ayant une puissance assez limitée, il ne délivre pas les ressources au navigateur plus rapidement que le CDN.

Par contre si vous ne souhaitez pas que l’ESP8266 puisse accéder à internet et y accéder en mode AP (point d’accès), il est nécessaire de tout embarquer dans la zone SPIFFS.

esp8266 firefow temps chargement ressources webserver

Pour récupérer les ressources, collez l’URL dans un navigateur et collez le texte obtenu dans un fichier texte. Il est préférable de placer les fichiers dans des dossiers séparés pour faciliter la maintenant. Attention à ce que le chemin complet (repertoire/fichier.ext) ne dépasse pas 31 caractères (limitation SPIFFS)

Ressource Répertoire Nom du fichier
 bootstrap.min.css css bootstrap.min.css
 bootstrap-table.min.css css bootstrap-table.min.css

Il ne reste plus qu’à modifier les chemins dans le Head, ce qui donne par exemple.

script(src="js/jquery.min.js")
script(src="js/bootstrap.min.js")
script(src="js/bootstrap-table.min.js")
link(rel="stylesheet" href="css/bootstrap-table.min.css")

Assembler le tout

Comme d’habitude, voici le code Pug permettant de générer le fichier de l’interface HTML. Vous pouvez le modifier en fonction de vos besoins et pour faire vos tests.

Template Pug de l’interface

html(charset='UTF-8')
    head
      meta( name='viewport')
      script(src='js/jquery.min.js')
      script(src='js/bootstrap.min.js')
      script(src='js/bootstrap-table.min.js')
      link(rel="stylesheet" href="css/bootstrap-table.min.css")
      link(href='https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/superhero/bootstrap.min.css', rel='stylesheet' title="main")
      title Demo ESP8266 SPIFFS + Boostrap - www.projetsdiy.fr
    body
      .container-fluid
        h1 Demo Webserver ESP8266 + Bootstrap
        ul#tab.nav.nav-tabs
          li.active
            a(href="#tab_mesures" data-toggle="tab") Mesures
          li
            a(href="#tab_graphs" data-toggle="tab") Graphiques
          li
            a(href="#tab_gpio" data-toggle="tab") GPIO  
          li
            a(href="#tab_configuration" data-toggle="tab") Configuration
        div.tab-content
        
          div#tab_mesures.tab-pane.fade.in.active          
            
            h2 Mini station m&eacute;t&eacute;o (DHT22 + BMP180)
            ul.nav.nav-pills
              li.active
                a(href='#')
                  #temperature.span.badge.pull-right -
                  |  Temp&eacute;rature
              li
                a(href='#')
                  #humidite.span.badge.pull-right -
                  |  Humidit&eacute;
              li
                a(href='#')
                  #pa.span.badge.pull-right -
                  |  Pression atmosph&eacute;rique
            br
            table(id='table_mesures' data-toggle='table' data-show-colunns='true')
              thead
                tr
                  th(data-field='mesure' data-align='left' data-sortable='true' data-formatter='labelFormatter') Mesure
                  th(data-field='valeur' data-align='left' data-sortable='true' data-formatter='valueFormatter') Valeur
                  th(data-field='precedente' data-align='left' data-sortable='true' data-formatter='vpFormatter') Valeur Pr&eacute;c&eacute;dente

          div#tab_graphs.tab-pane.fade
            h2 Graphs
            
          div#tab_gpio.tab-pane.fade
            h2 GPIO
            .row
                .col-xs-6.col-md-4
                  h4.text-left
                    | D5
                    #D5_etat.span.badge OFF
                .col-xs-6.col-md-4
                  #D5_On.button.btn.btn-success.btn-lg(type='button') ON
                .col-xs-6.col-md-4
                  #D5_Off.button.btn.btn-danger.btn-lg(type='button') OFF
                .col-xs-6.col-md-4
                  h4.text-left
                    | D6
                    #D6_etat.span.badge OFF
                .col-xs-6.col-md-4
                  #D6_On.button.btn.btn-success.btn-lg(type='button') ON
                .col-xs-6.col-md-4
                  #D6_Off.button.btn.btn-danger.btn-lg(type='button') OFF
                .col-xs-6.col-md-4
                  h4.text-left
                    | D7
                    #D7_etat.span.badge OFF
                .col-xs-6.col-md-4
                  #D7_On.button.btn.btn-success.btn-lg(type='button') ON
                .col-xs-6.col-md-4
                  #D7_Off.button.btn.btn-danger.btn-lg(type='button') OFF
                .col-xs-6.col-md-4
                  h4.text-left
                    | D8
                    #D8_etat.span.badge OFF
                .col-xs-6.col-md-4
                  #D8_On.button.btn.btn-success.btn-lg(type='button') ON
                .col-xs-6.col-md-4
                  #D8_Off.button.btn.btn-danger.btn-lg(type='button') OFF
          div#tab_configuration.tab-pane.fade
            h2 Configuration        

            .btn-group
              button#labelTheme.btn.btn-default Theme
              button.btn.btn-default.dropdown-toggle(data-toggle='dropdown')
                span.caret
              ul.dropdown-menu
                li
                    a.change-style-menu-item(href='#' rel='bootstrap') Boostrap
                li
                    a.change-style-menu-item(href='#' rel='cerulean') Cerulean
                li
                    a.change-style-menu-item(href='#' rel='cosmo') Cosmo
                li
                    a.change-style-menu-item(href='#' rel='cyborg') Cyborg
                li
                    a.change-style-menu-item(href='#' rel='darkly') Darkly
                li
                    a.change-style-menu-item(href='#' rel='flatly') Flatly
                li
                    a.change-style-menu-item(href='#' rel='journal') Journal
                li
                    a.change-style-menu-item(href='#' rel='lumen') Lumen
                li
                    a.change-style-menu-item(href='#' rel='paper') Paper
                li
                    a.change-style-menu-item(href='#' rel='readable') Readable
                li
                    a.change-style-menu-item(href='#' rel='sandstone') Sandstone
                li
                    a.change-style-menu-item(href='#' rel='simplex') Simplex
                li
                    a.change-style-menu-item(href='#' rel='slate') Slate
                li
                    a.change-style-menu-item(href='#' rel='spacelab') Spacelab
                li
                    a.change-style-menu-item(href='#' rel='superhero') Superhero
                li
                    a.change-style-menu-item(href='#' rel='united') United
                li
                    a.change-style-menu-item(href='#' rel='yeti') Yeti  
        .row(style="position:absolute; bottom:0; width:100%")
          .col-xs-2.col-md-2
            img(src="img/logo.png" width="30" height="30")
          .col-xs-5.col-md-5
            p
              a(href='https://www.projetsdiy.fr') Version francaise : www.projetsdiy.fr
          .col-xs-5.col-md-5
            p
              a(href='http://www.diyprojects.io') English version : www.diyprojects.io
    
      //script(src='js/script.js')
    
      script().
        var Timer_UdpateMesures;
        
        $('a[data-toggle=\"tab\"]').on('shown.bs.tab', function (e) {   
          //On supprime tous les timers lorsqu'on change d'onglet
          clearTimeout(Timer_UdpateMesures);  
          var target = $(e.target).attr("href")  
          console.log('activated ' + target );  

          // IE10, Firefox, Chrome, etc.
          if (history.pushState) 
            window.history.pushState(null, null, target);
          else 
            window.location.hash = target;
          
          if (target=='#tab_mesures')  {
            $('#table_mesures').bootstrapTable('refresh',{silent:true, url:'/tabmesures.json'}); 
          }  
        });
        
        // Créé un timer qui actualise les données régulièrement - Create a timer than update data every n secondes
        $('#tab_mesures').on('load-success.bs.table',function (e,data){
          console.log("tab_mesures loaded");
          if ($('.nav-tabs .active > a').attr('href')=='#tab_mesures') {
            Timer_UdpateMesures=setTimeout(function(){
              $('#table_mesures').bootstrapTable('refresh',{silent: true, showLoading: false, url: '/tabmesures.json'});
              updateMesures();
            },5000);
          }                 
        });   
            
        function updateMesures(){
          $.getJSON('/mesures.json', function(data){
            //console.log("Mesures envoyees : " + JSON.stringify(data) + "|" + data.t + "|" + data.h + "|" + data.pa) ;
            $('#temperature').html(data.t);
            $('#humidite').html(data.h);
            $('#pa').html(data.pa); 
          }).fail(function(err){
            console.log("err getJSON mesures.json "+JSON.stringify(err));
          });
        };

        function labelFormatter(value, row){
          //console.log("labelFormatter");
          //console.log(value);
          console.log(row);
          var label = "";
          if ( value === "Température" ) {
            label = value + "<span class='glyphicon " + row.glyph + " pull-left'></span>";
          } else if ( value === "Humidité" ) {
            label = value + "<span class='glyphicon " + row.glyph + " pull-left'></span>";
          } else if ( value === "Pression Atmosphérique" ) {
            label = value + "<span class='glyphicon " + row.glyph + " pull-left'></span>";
          } else {
            label = value;
          } 
          return label;
        }
        function valueFormatter(value, row){
          //console.log("valueFormatter");
          var label = "";
          if ( row.valeur > row.precedente ) {
            label = value + row.unite + "<span class='glyphicon glyphicon-chevron-up pull-right'></span>";
          } else { 
            label = value + row.unite + "<span class='glyphicon glyphicon-chevron-down pull-right'></span>";
          }
          return label;
        }
        function vpFormatter(value, row){
          //console.log("valueFormatter");
          var label = "";
          if ( row.valeur > row.precedente ) {
            label = value + row.unite
          } else { 
            label = value + row.unite
          }
          return label;
        }  
        
        // Commandes sur le GPIO - GPIO change
        $('#D5_On').click(function(){ setBouton('D5','1'); });
        $('#D5_Off').click(function(){ setBouton('D5','0'); });
        $('#D6_On').click(function(){ setBouton('D6','1'); });
        $('#D6_Off').click(function(){ setBouton('D6','0'); });
        $('#D7_On').click(function(){ setBouton('D7','1'); });
        $('#D7_Off').click(function(){ setBouton('D7','0'); });
        $('#D8_On').click(function(){ setBouton('D8','1'); });
        $('#D8_Off').click(function(){ setBouton('D8','0'); });
  
        function setBouton(id, etat){
          $.post("gpio?id=" + id + "&etat=" + etat).done(function(data){
            //console.log("Retour setBouton " + JSON.stringify(data)); 
            var id_gpio = "#" + id + "_etat";
            //console.log(id_gpio);
            if ( data.success === "1" ) {
              if ( data.etat === "1" ) {
                $(id_gpio).html("ON");
              } else {
                $(id_gpio).html("OFF");
              }  
            } else {
              $(id_gpio).html('!');
            }      
          }).fail(function(err){
            console.log("err setButton " + JSON.stringify(err));
          });
        } 
        
        // Changement du thème - Change current theme
        // Adapté de - Adapted from : https://wdtz.org/bootswatch-theme-selector.html
        var supports_storage = supports_html5_storage();
        if (supports_storage) {
          var theme = localStorage.theme;
          console.log("Recharge le theme " + theme);
          if (theme) {
            set_theme(get_themeUrl(theme));
          }
        }
        
        // Un nouveau thème est sélectionné - New theme selected
        jQuery(function($){
          $('body').on('click', '.change-style-menu-item', function() {
            var theme_name = $(this).attr('rel');
            console.log("Changement de theme " + theme_name);
            var theme_url = get_themeUrl(theme_name);
            console.log("URL theme : " + theme_url);
            set_theme(theme_url);
          });
        });
        // Recupere l'adresse du theme - Get theme URL
        function get_themeUrl(theme_name){
          $('#labelTheme').html("Th&egrave;me : " + theme_name);
          var url_theme = "";
          if ( theme_name === "bootstrap" ) {
            url_theme = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css";
          } else {
            url_theme = "https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/" + theme_name + "/bootstrap.min.css";
          }
          if (supports_storage) {
            // Enregistre le theme sélectionné en local - save into the local database the selected theme
            localStorage.theme = theme_name;
          }
          return url_theme;
        }
        // Applique le thème - Apply theme
        function set_theme(theme_url) {
          $('link[title="main"]').attr('href', theme_url);
        }
        // Stockage local disponible ? - local storage available ?
        function supports_html5_storage(){
          try {
            return 'localStorage' in window && window['localStorage'] !== null;
          } catch (e) {
            return false;
          }
        }

Code HTML généré

Sinon voici le fichier HTML

<!DOCTYPE html>
<html charset="UTF-8">
  <head>
    <meta name="viewport">
    <!--script(src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js')-->
    <script src="js/jquery.min.js"></script>
    <!--script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js')-->
    <script src="js/bootstrap.min.js"></script>
    <!--script(src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.js")-->
    <script src="js/bootstrap-table.min.js"></script>
    <!--link(rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.css")-->
    <link rel="stylesheet" href="css/bootstrap-table.min.css">
    <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/superhero/bootstrap.min.css" rel="stylesheet" title="main">
    <title>Demo ESP8266 SPIFFS + Boostrap - www.projetsdiy.fr</title>
  </head>
  <body>
    <div class="container-fluid">
      <h1>Demo Webserver ESP8266 + Bootstrap</h1>
      <ul class="nav nav-tabs" id="tab">
        <li class="active"><a href="#tab_mesures" data-toggle="tab">Mesures</a></li>
        <li><a href="#tab_graphs" data-toggle="tab">Graphiques</a></li>
        <li><a href="#tab_gpio" data-toggle="tab">GPIO  </a></li>
        <li><a href="#tab_configuration" data-toggle="tab">Configuration</a></li>
      </ul>
      <div class="tab-content">
        <div class="tab-pane fade in active" id="tab_mesures">         
          <h2>Mini station m&eacute;t&eacute;o (DHT22 + BMP180)</h2>
          <ul class="nav nav-pills">
            <li class="active"><a href="#">
                <div class="span badge pull-right" id="temperature">-</div> Temp&eacute;rature</a></li>
            <li><a href="#">
                <div class="span badge pull-right" id="humidite">-</div> Humidit&eacute;</a></li>
            <li><a href="#">
                <div class="span badge pull-right" id="pa">-</div> Pression atmosph&eacute;rique</a></li>
          </ul><br>
          <table id="table_mesures" data-toggle="table" data-show-colunns="true">
            <thead>
              <tr>
                <th data-field="mesure" data-align="left" data-sortable="true" data-formatter="labelFormatter">Mesure</th>
                <th data-field="valeur" data-align="left" data-sortable="true" data-formatter="valueFormatter">Valeur</th>
                <th data-field="precedente" data-align="left" data-sortable="true" data-formatter="vpFormatter">Valeur Pr&eacute;c&eacute;dente</th>
              </tr>
            </thead>
          </table>
        </div>
        <div class="tab-pane fade" id="tab_graphs">
          <h2>Graphs</h2>
        </div>
        <div class="tab-pane fade" id="tab_gpio">
          <h2>GPIO</h2>
          <div class="row">
            <div class="col-xs-6 col-md-4">
              <h4 class="text-left">D5
                <div class="span badge" id="D5_etat">OFF</div>
              </h4>
            </div>
            <div class="col-xs-6 col-md-4">
              <div class="button btn btn-success btn-lg" id="D5_On" type="button">ON</div>
            </div>
            <div class="col-xs-6 col-md-4">
              <div class="button btn btn-danger btn-lg" id="D5_Off" type="button">OFF</div>
            </div>
            <div class="col-xs-6 col-md-4">
              <h4 class="text-left">D6
                <div class="span badge" id="D6_etat">OFF</div>
              </h4>
            </div>
            <div class="col-xs-6 col-md-4">
              <div class="button btn btn-success btn-lg" id="D6_On" type="button">ON</div>
            </div>
            <div class="col-xs-6 col-md-4">
              <div class="button btn btn-danger btn-lg" id="D6_Off" type="button">OFF</div>
            </div>
            <div class="col-xs-6 col-md-4">
              <h4 class="text-left">D7
                <div class="span badge" id="D7_etat">OFF</div>
              </h4>
            </div>
            <div class="col-xs-6 col-md-4">
              <div class="button btn btn-success btn-lg" id="D7_On" type="button">ON</div>
            </div>
            <div class="col-xs-6 col-md-4">
              <div class="button btn btn-danger btn-lg" id="D7_Off" type="button">OFF</div>
            </div>
            <div class="col-xs-6 col-md-4">
              <h4 class="text-left">D8
                <div class="span badge" id="D8_etat">OFF</div>
              </h4>
            </div>
            <div class="col-xs-6 col-md-4">
              <div class="button btn btn-success btn-lg" id="D8_On" type="button">ON</div>
            </div>
            <div class="col-xs-6 col-md-4">
              <div class="button btn btn-danger btn-lg" id="D8_Off" type="button">OFF</div>
            </div>
          </div>
        </div>
        <div class="tab-pane fade" id="tab_configuration">
          <h2>Configuration        </h2>
          <div class="btn-group">
            <button class="btn btn-default" id="labelTheme">Theme</button>
            <button class="btn btn-default dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
            <ul class="dropdown-menu">
              <li><a class="change-style-menu-item" href="#" rel="bootstrap">Boostrap</a></li>
              <li><a class="change-style-menu-item" href="#" rel="cerulean">Cerulean</a></li>
              <li><a class="change-style-menu-item" href="#" rel="cosmo">Cosmo</a></li>
              <li><a class="change-style-menu-item" href="#" rel="cyborg">Cyborg</a></li>
              <li><a class="change-style-menu-item" href="#" rel="darkly">Darkly</a></li>
              <li><a class="change-style-menu-item" href="#" rel="flatly">Flatly</a></li>
              <li><a class="change-style-menu-item" href="#" rel="journal">Journal</a></li>
              <li><a class="change-style-menu-item" href="#" rel="lumen">Lumen</a></li>
              <li><a class="change-style-menu-item" href="#" rel="paper">Paper</a></li>
              <li><a class="change-style-menu-item" href="#" rel="readable">Readable</a></li>
              <li><a class="change-style-menu-item" href="#" rel="sandstone">Sandstone</a></li>
              <li><a class="change-style-menu-item" href="#" rel="simplex">Simplex</a></li>
              <li><a class="change-style-menu-item" href="#" rel="slate">Slate</a></li>
              <li><a class="change-style-menu-item" href="#" rel="spacelab">Spacelab</a></li>
              <li><a class="change-style-menu-item" href="#" rel="superhero">Superhero</a></li>
              <li><a class="change-style-menu-item" href="#" rel="united">United</a></li>
              <li><a class="change-style-menu-item" href="#" rel="yeti">Yeti  </a></li>
            </ul>
          </div>
        </div>
      </div>
      <div class="row" style="position:absolute; bottom:0; width:100%">
        <div class="col-xs-2 col-md-2"><img src="img/logo.png" width="30" height="30"></div>
        <div class="col-xs-5 col-md-5">
          <p><a href="https://www.projetsdiy.fr">Version francaise : www.projetsdiy.fr</a></p>
        </div>
        <div class="col-xs-5 col-md-5">
          <p><a href="http://www.diyprojects.io">English version : www.diyprojects.io</a></p>
        </div>
      </div>
    </div>
    <!--script(src='js/script.js')-->
    <script>
      var Timer_UdpateMesures;
      
      $('a[data-toggle=\"tab\"]').on('shown.bs.tab', function (e) {   
        //On supprime tous les timers lorsqu'on change d'onglet
        clearTimeout(Timer_UdpateMesures);  
        var target = $(e.target).attr("href")  
        console.log('activated ' + target );  
      
        // IE10, Firefox, Chrome, etc.
        if (history.pushState) 
          window.history.pushState(null, null, target);
        else 
          window.location.hash = target;
        
        if (target=='#tab_mesures')  {
          $('#table_mesures').bootstrapTable('refresh',{silent:true, url:'/tabmesures.json'}); 
        }  
      });
      
      // Créé un timer qui actualise les données régulièrement - Create a timer than update data every n secondes
      $('#tab_mesures').on('load-success.bs.table',function (e,data){
        console.log("tab_mesures loaded");
        if ($('.nav-tabs .active > a').attr('href')=='#tab_mesures') {
          Timer_UdpateMesures=setTimeout(function(){
            $('#table_mesures').bootstrapTable('refresh',{silent: true, showLoading: false, url: '/tabmesures.json'});
            updateMesures();
          },5000);
        }                 
      });   
          
      function updateMesures(){
        $.getJSON('/mesures.json', function(data){
          //console.log("Mesures envoyees : " + JSON.stringify(data) + "|" + data.t + "|" + data.h + "|" + data.pa) ;
          $('#temperature').html(data.t);
          $('#humidite').html(data.h);
          $('#pa').html(data.pa); 
        }).fail(function(err){
          console.log("err getJSON mesures.json "+JSON.stringify(err));
        });
      };
      
      function labelFormatter(value, row){
        //console.log("labelFormatter");
        //console.log(value);
        console.log(row);
        var label = "";
        if ( value === "Température" ) {
          label = value + "<span class='glyphicon " + row.glyph + " pull-left'></span>";
        } else if ( value === "Humidité" ) {
          label = value + "<span class='glyphicon " + row.glyph + " pull-left'></span>";
        } else if ( value === "Pression Atmosphérique" ) {
          label = value + "<span class='glyphicon " + row.glyph + " pull-left'></span>";
        } else {
          label = value;
        } 
        return label;
      }
      function valueFormatter(value, row){
        //console.log("valueFormatter");
        var label = "";
        if ( row.valeur > row.precedente ) {
          label = value + row.unite + "<span class='glyphicon glyphicon-chevron-up pull-right'></span>";
        } else { 
          label = value + row.unite + "<span class='glyphicon glyphicon-chevron-down pull-right'></span>";
        }
        return label;
      }
      function vpFormatter(value, row){
        //console.log("valueFormatter");
        var label = "";
        if ( row.valeur > row.precedente ) {
          label = value + row.unite
        } else { 
          label = value + row.unite
        }
        return label;
      }  
      
      // Commandes sur le GPIO - GPIO change
      $('#D5_On').click(function(){ setBouton('D5','1'); });
      $('#D5_Off').click(function(){ setBouton('D5','0'); });
      $('#D6_On').click(function(){ setBouton('D6','1'); });
      $('#D6_Off').click(function(){ setBouton('D6','0'); });
      $('#D7_On').click(function(){ setBouton('D7','1'); });
      $('#D7_Off').click(function(){ setBouton('D7','0'); });
      $('#D8_On').click(function(){ setBouton('D8','1'); });
      $('#D8_Off').click(function(){ setBouton('D8','0'); });
      
      function setBouton(id, etat){
        $.post("gpio?id=" + id + "&etat=" + etat).done(function(data){
          //console.log("Retour setBouton " + JSON.stringify(data)); 
          var id_gpio = "#" + id + "_etat";
          //console.log(id_gpio);
          if ( data.success === "1" ) {
            if ( data.etat === "1" ) {
              $(id_gpio).html("ON");
            } else {
              $(id_gpio).html("OFF");
            }  
          } else {
            $(id_gpio).html('!');
          }      
        }).fail(function(err){
          console.log("err setButton " + JSON.stringify(err));
        });
      } 
      
      // Changement du thème - Change current theme
      // Adapté de - Adapted from : https://wdtz.org/bootswatch-theme-selector.html
      var supports_storage = supports_html5_storage();
      if (supports_storage) {
        var theme = localStorage.theme;
        console.log("Recharge le theme " + theme);
        if (theme) {
          set_theme(get_themeUrl(theme));
        }
      }
      
      // Un nouveau thème est sélectionné - New theme selected
      jQuery(function($){
        $('body').on('click', '.change-style-menu-item', function() {
          var theme_name = $(this).attr('rel');
          console.log("Changement de theme " + theme_name);
          var theme_url = get_themeUrl(theme_name);
          console.log("URL theme : " + theme_url);
          set_theme(theme_url);
        });
      });
      // Recupere l'adresse du theme - Get theme URL
      function get_themeUrl(theme_name){
        $('#labelTheme').html("Th&egrave;me : " + theme_name);
        var url_theme = "";
        if ( theme_name === "bootstrap" ) {
          url_theme = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css";
        } else {
          url_theme = "https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/" + theme_name + "/bootstrap.min.css";
        }
        if (supports_storage) {
          // Enregistre le theme sélectionné en local - save into the local database the selected theme
          localStorage.theme = theme_name;
        }
        return url_theme;
      }
      // Applique le thème - Apply theme
      function set_theme(theme_url) {
        $('link[title="main"]').attr('href', theme_url);
      }
      // Stockage local disponible ? - local storage available ?
      function supports_html5_storage(){
        try {
          return 'localStorage' in window && window['localStorage'] !== null;
        } catch (e) {
          return false;
        }
      }
    </script>
  </body>
</html>

Code Arduino

Et le code Arduino

#include <ESP8266WebServer.h>
#include <DHT.h>
#include <Adafruit_BMP085.h>
#include <FS.h>

#define ssid      "xxxx"      // WiFi SSID
#define password  "xxxx"      // WiFi password
#define DHTTYPE   DHT22       // DHT type (DHT11, DHT22)
#define DHTPIN    D4          // Broche du DHT / DHT Pin
const uint8_t GPIOPIN[4] = {D5,D6,D7,D8};  // Led
float   t = 0 ;
float   h = 0 ;
float   pa = 0;
int     sizeHist = 100;        // Nombre de points dans l'historique - History size

// Création des objets / create Objects
DHT dht(DHTPIN, DHTTYPE);
Adafruit_BMP085 bmp;
ESP8266WebServer server ( 80 );
 
void updateGpio(){
  String gpio = server.arg("id");
  String etat = server.arg("etat");
  String success = "1";
  int pin = D5;
 if ( gpio == "D5" ) {
      pin = D5;
 } else if ( gpio == "D7" ) {
     pin = D7;
 } else if ( gpio == "D8" ) {
     pin = D8;  
 } else {   
      pin = D5;
  }
  Serial.println(pin);
  if ( etat == "1" ) {
    digitalWrite(pin, HIGH);
  } else if ( etat == "0" ) {
    digitalWrite(pin, LOW);
  } else {
    success = "1";
    Serial.println("Err Led Value");
  }
  
  String json = "{\"gpio\":\"" + String(gpio) + "\",";
  json += "\"etat\":\"" + String(etat) + "\",";
  json += "\"success\":\"" + String(success) + "\"}";
    
  server.send(200, "application/json", json);
  Serial.println("GPIO mis a jour");
}

void sendMesures() {
  String json = "{\"t\":\"" + String(t) + "\",";
  json += "\"h\":\"" + String(h) + "\",";
  json += "\"pa\":\"" + String(pa) + "\"}";

  server.send(200, "application/json", json);
  Serial.println("Mesures envoyees");
}

void sendTabMesures() {
  double temp = 0;      // Récupère la plus ancienne mesure (temperature) - get oldest record (temperature)
  String json = "[";
  json += "{\"mesure\":\"Température\",\"valeur\":\"" + String(t) + "\",\"unite\":\"°C\",\"glyph\":\"glyphicon-indent-left\",\"precedente\":\"" + String(temp) + "\"},";
  temp = 0;             // Récupère la plus ancienne mesure (humidite) - get oldest record (humidity)
  json += "{\"mesure\":\"Humidité\",\"valeur\":\"" + String(h) + "\",\"unite\":\"%\",\"glyph\":\"glyphicon-tint\",\"precedente\":\"" + String(temp) + "\"},";
  temp = 0;             // Récupère la plus ancienne mesure (pression atmospherique) - get oldest record (Atmospheric Pressure)
  json += "{\"mesure\":\"Pression Atmosphérique\",\"valeur\":\"" + String(pa) + "\",\"unite\":\"mbar\",\"glyph\":\"glyphicon-dashboard\",\"precedente\":\"" + String(temp) + "\"}";
  json += "]";
  server.send(200, "application/json", json);
  Serial.println("Tableau mesures envoyees");
}

void setup() {
  for ( int x = 0 ; x < 5 ; x++ ) {
    pinMode(GPIOPIN[x], OUTPUT);
  }
  
  Serial.begin ( 115200 );
  // Initialisation du BMP180 / Init BMP180
  if ( !bmp.begin() ) {
    Serial.println("BMP180 KO!");
    while (1);
  } else {
    Serial.println("BMP180 OK");
  }

  WiFi.begin ( ssid, password );
  // Attente de la connexion au réseau WiFi / Wait for connection
  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 ); Serial.print ( "." );
  }
  // Connexion WiFi établie / WiFi connexion is OK
  Serial.println ( "" );
  Serial.print ( "Connected to " ); Serial.println ( ssid );
  Serial.print ( "IP address: " ); Serial.println ( WiFi.localIP() );

  if (!SPIFFS.begin())
  {
    // Serious problem
    Serial.println("SPIFFS Mount failed");
  } else {

    Serial.println("SPIFFS Mount succesfull");
  }

  server.on("/tabmesures.json", sendTabMesures);
  server.on("/mesures.json", sendMesures);
  server.on("/gpio", updateGpio);
  /*HTTP_POST, []() {
    updateGpio();
  });
  */
  server.serveStatic("/js", SPIFFS, "/js");
  server.serveStatic("/css", SPIFFS, "/css");
  server.serveStatic("/img", SPIFFS, "/img");
  server.serveStatic("/", SPIFFS, "/index.html");

  server.begin();
  Serial.println ( "HTTP server started" );

}

void loop() {
  // put your main code here, to run repeatedly:
  server.handleClient();
  t = dht.readTemperature();
  h = dht.readHumidity();
  pa = bmp.readPressure() / 100.0F;
  delay(100);
}

L’interface obtenue

En accédant à l’ESP8266, vous avez maintenant des mesures qui s’affichent
esp8266 webserver dht22 bmp180 bootstrap

ainsi qu’une interaction complète avec le GPIO. Le libellé (On / Off) est mis à jour une fois que l’ESP8266 a exécuté la commande.

esp8266 webserver dht22 bmp180 bootstrap gpio

Dans le prochain tutoriel, nous verrons comment enregistrer les mesures pour créer un historique et créer des graphiques et des gauges.

Le code source complet est disponible sur GitHub ici https://github.com/projetsdiy/ESP8266-Webserver-Tutorials/tree/master/Part2_DHT22WebserverESP8266_SPIFFS
Print Friendly, PDF & Email

Inscrivez-vous à la newsletter hebdomadaire

Aucun spam et aucun autre usage ne sera fait de votre email. Vous pouvez vous désinscrire à tout moment.

Comparateur de prix

Bons plans

Les offres suivantes se terminent bientôt. Utilisez le coupon indiqué pour profiter du prix promo

Domotique et objets connectés à faire soi-même