ESP8266 (Web Serveur – Partie 1) : stocker l’interface Web dans la zone SPIFFS (HTML, CSS, JS) [Màj]

Nous continuons notre série d’articles sur la programmation de la partie Web Server (serveur web) des modules ESP8266. Dans les articles précédents, nous avons vu comment mettre en place le Web Server, puis comment améliorer l’affichage en utilisant le framework Bootstrap (et utiliser les thèmes Bootswatch). Dans les deux premiers épisodes, tout le code HTML était généré directement dans le code Arduino. Ca ne pose aucun problème pour un projet simple mais on ne profite pas totalement des capacités de l’ESP8266. Nous allons aller encore plus loin dans cette nouvelle série d’articles. Nous allons apprendre comment gérer séparément le code de l’interface Web et stocker tous les fichiers dans le système de fichier SPIFFS de l’ESP8266.

Présentation du projet

Nous allons aborder de nombreuses notions dans ce projet d’interface Web pour ESP8266. Il va donc y avoir plusieurs épisodes. Voici un tableau de synthèse qui récapitule les thèmes abordés dans chaque partie. Vous trouverez également un lien vers le dépôt GitHub (s’il existe) et un lien vers l’article.

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
  • 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 2 Comment interagir avec le code Arduino. Lire l’article

  • Intercepter les actions sur les boutons de l’interface et actualiser les affichages lorsque l’action a été réalisée (Javascript + jQuery)
    • Récupérer les requêtes sur l’Arduino, exécuter la demande et envoyer la réponse
  • Actualiser régulièrement (et automatiquement) le tableau de mesures et les afficheurs (Javascript)
    • Mettre à jour les symboles si la valeur actuelle est supérieure ou inférieure à la précédente
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)

Pour continuer les listes, voici les langages et API employés :

  • C++ (code Arduino)
    • Librairies : ESP8266WebServer, DHT, Adafruit_BMP085, ArduinoJSON, FS.h
  • Javascript et jQuey pour les interactions avec la page HTML
  • Pug
  • HTML
  • Bootstrap et thèmes Bootswatch
  • Bootstrap-table (plugin permettant de créer facilement des tables)

De quoi allez vous avoir besoin ?

Avant de rentrer dans le vif du sujet, vous allez avoir besoin d’un peu de matériel et d’une configuration logicielle adéquate.

Matériel utilisé

Coté matériel, nous allons rester dans le classique, l’objectif et d’avoir à disposition des données à afficher et des éléments pour simuler des interactions. J’ai donc opté pour un DHT22 pour récupérer des mesures de température et d’humidité ainsi qu’un BMP180 pour la pression atmosphérique. Pour simuler les sorties, de simples Led feront l’affaire. Pour l’ESP8266, j’ai opté pour la Wemos D1 mini qui dispose d’une zone SPIFFS de 3M.

Dernière mise à jour des prix le 14 novembre 2018 13 h 58 min

Environnement logiciel et plateforme

Comme vous le savez, il est possible de programmer l’ESP8266 à l’aide du langage Lua ainsi qu’en C++ depuis l’IDE Arduino. J’ai opté pour l’IDE Arduino car on trouve beaucoup plus facilement des exemples, informations, librairies, forums et l’accès aux débutants et plus facile. Comme je test les alternatives (bonnes ou mauvaises) au Raspberry Pi, tous les développements ont été fait sur un Orange Pi+ 2e fonctionnant sous Armbian basé sur Ubuntu 16.04 LTS. Mais tout fonctionnera à l’identique quelque soit votre environnement (Windows, macOS, Raspberry Pi…).

Pour l’écriture du code HTML, je vous conseille d’utiliser le langage Pug (ancien Jade) qui permet de simplifier la mise au point du code (on ne gère plus la fermeture des balises). Pug n’est pas vraiment un langage, on se contente d’écrire le balise et on respecte un formalisme d’écriture.

Pour résumé, voici la configuration utilisée et les renvoi vers les tutoriels à consulter s’il vous manque des infos.

Remarque. J’ai essayé de rendre le plus synthétique possible toutes les notions abordées pour que tout le monde puisse se les approprier. Les plus pointus d’entre vous trouveront certainement de meilleures manières d’écrire, n’hésitez pas à utiliser les commentaires pour donner des conseils.

Orange Pi Plus 2e fonctionnant sous Armbian (Ubuntu 16.04 LTS)

Quelques articles utiles : présentation, installer Armbian sur Orange Pi,

Geany : éditeur de texte orienté programmation (pour HTML, Pug, Javascript, jQuery)

Article : installation et découverte sur Raspberry Pi 

Pug : langage pour simplifier l’écriture du code HTML

Article : installation et découverte du langage

IDE Arduino 1.8.1

Articles :

Comment transférer des fichiers dans la zone SPIFFS depuis l’IDE Arduino ?

L’ESP8266 dispose d’une zone réservée appelée SPIFFS dont la taille varie d’un modèle à l’autre (certains modèles, les plus anciens, n’en proposent pas). Cette zone mémoire peut être utilisée pour héberger des fichiers. Nous n’allons pas aller plus loin sur le stockage SPIFFS dans cet article, un tutoriel dédié lui sera consacré. Afin de pouvoir télécharger des fichiers dans cette zone mémoire, il est nécessaire d’installer l’outil de téléchargement des fichiers dans la zone SPIFFS  à l’IDE Arduino disponible sur Github.

 

ide arduino spiffs tools esp8266 release page

 

Suivez le lien releases page pour télécharger la dernière version du plugin compilée. Cliquez sur le lien pour lancer le téléchargement du ZIP

ide arduino spiffs tools esp8266 esp8266fs

Ensuite, décompressez et collez le répertoire ESP8266FS qui contient les tools dans le dossier des croquis de l’IE Arduino. En général, le dossier Arduino se trouve :

  • Sur Windows : c:\utilisateurs\<votre nom>\Documents\Arduino
  • Sur macOS (et probablement Linux) : ~/Documents/Arduino/

Copiez l’intégralité du dossier ESP8266FS dans le dossier Arduino. S’il n’existe pas de dossier Arduino, il suffit d’en créer un manuellement. Relancez l’IDE Arduino pour que l’outil de téléchargement soit ajouté au menu outil sous le nom ESP8266 Sketch Data Upload.

ide arduino esp8266fs tools spiffs upload

Préparation de l’interface WEB : code HTML

Nous allons donc utiliser le langage Pug pour préparer le code de l’interface HTML.

Entête de la page (header)

Pour créer notre interface, nous allons avoir besoin de nouvelles ressources. Dans un premier temps, nous allons simplement aller les chercher sur internet. Nous verrons dans le prochain tutoriel comment les ajouter dans la zone SPIFFS. On va donc créer une entête (Head) qui contient indique les ressources que doit charger le navigateur (jquery, bootstrap.js, bootstrap-table.js, bootstrap.css, bootstrap-table.css).

Quelques remarques :

  • L’ordre n’a aucune importance
  • On récupère les versions “minifiées”, plus compactes mais difficile à comprendre
  • La balise name=’viewport’  est réservée à Bootstrap.
  • La balise http-equiv=’refresh’  qui permettait de demander au navigateur d’actualiser l’affichage régulièrement a été supprimée. Nous allons gérer l’actualisation de l’affichage autrement.
head
  meta(name='viewport')
  script(src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js')
  script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js')
  link(rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.css")
  script(src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.js")
  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

Ajout d’une barre de navigation (body)

Passons au corps de la page (body). En dessous du titre du projet (balise h1), nous allons ajouter une barre de navigation qui permet de regrouper les informations par thème (mesures, graphiques GPIO, configuration). Avec Bootstrap, elle est gérée par les classes .nav et .nav-tabs. Chaque onglet (tab) est composé d’une balise li. L’attribut active permet d’indiquer l’onglet actif par défaut. Une balise a permet d’indiquer le titre de l’onglet et surtout d’indiquer vers quel élément se rendre lorsqu’on clique (href=”#iddestination”). L’attribut data-toggle rend cliquable l’onglet.

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

Ensuite il ne reste plus qu’à créer une div de classe tab-content puis une div tab-pane pour chaque onglet. Ce qui donne par exemple

div.tab-content
  div#tab_mesures.tab-pane
    h1 Onglet Mesures
  div#tab_graphs
    h1 Onglet Graphiques

Pour rendre l’affichage plus sympa, on peut aussi ajouter une animation lorsqu’on passe d’un onglet à l’autre :

div#tab_mesures.tab-pane.fade.in

De même, on indique le premier onglet actif par défaut (pour la première ouverture de la page).

div#tab_mesures.tab-pane.fade.in.active

Il n’y a rien de plus à programmer pour créer une barre de navigation et naviguer d’un onglet à l’autre !

Ajout d’une table Bootstrap-table

Bootstrap-table est un plugin Bootstrap très connu et très puissant pour créer des tables et réaliser des opérations (page du projet sur GitHub). Elle permet entre autre d’être actualisée très facilement). Pour le moment, on va juste la poser dans l’onglet Mesures. On créé un champ table. On va simplement placer une balise th pour chaque colonne. On affecte à chaque colonne une source de données (data-field) qui sera associé à une clé du JSON envoyé par le code Arduino. Enfin on attribue un data-formatter, c’est une fonction qui est appelée à chaque fois qu’une ligne est ajoutée au tableau et qui permet de mettre en forme le contenu. Nous verrons dans le prochain tutoriel comment remplir cette table.

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&eacute;c&eacute;dente

Voici ce qu’on va obtenir (avec le thème Bootstrap par défaut).

esp8266 bootstrap web server nav tab barre navigation onglets

Bas de page (footer)

Fonction gadget, mais utile pour apprendre d’autres notions d’HTML, un bas de page (footer) fixé. Le bas de page ne bouge pas lorsqu’on passe d’un onglet à l’autre. Vous pouvez utiliser cette zone pour indiquer toutes sortes d’informations (date, heure, qualité de réception Wi-Fi, charge CPU…) Pour fixer le bas de page, on va juste ajouter un ligne (row) et lui donner les paramètres dans l’attribut HTML style  :

  • position:absolute, la ligne (row) est positionnée de manière absolue
  • bottom:0, en bas de la page à 0 pixels du bords
  • width:100%, et cette ligne prend toute la largeur du navigateur disponible

Dans cette ligne on va ajouter 3 colonnes. La première va contenir une image (mon logo  🙂 ), et deux liens. Le contenu n’est pas important, ce qui est important est de voir comment rendre le contenu responsive, c’est à dire qu’il s’adapte à tous les écrans et comment on charge une image depuis la zone SPIFFS.

Pour afficher une image, on ajoute une balise img, dans la source (src), on indique le chemin sous la forme repertoire/image.jpg. On peut aussi imposer la taille width=”30″(largeur de 30 pixels) et height=”30″ (hauteur).

Bootstrap utilise un système de grilles. Si on diminue la taille du navigateur, les cellules vont commencer à se réduire puis et se mettre les unes sous les autres. Pour cela, on utilise les classes col-md-x. La somme des x ne doit pas dépasser 12 (l’écran est divisé en 12 colonnes). Pour s’adapter aux plus petits écrans (smartphone notamment), on peut par exemple combiner avec une classe col-xs-x. C’est une approche très simpliste mais ça fonctionne et ce n’est pas l’objectif principal de l’article.

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

Voici le rendu obtenu pour différentes largeurs d’écran. Taille normale

Taille réduite.

 footer bas de page ecran reduit

Smartphone (simulée)

Changer le thème Bootstrap dynamiquement

Dans le tutoriel précédent, il était très facile de changer le thème car il suffisait de passer le thème sélectionné lorsqu’on construisait le code HTML de la page. Ici il va falloir introduire un peu de javascript (et de jquery) pour y arriver. Je me suis inspiré du code proposé par wdfz dans ce tutoriel.

On va commencer par modifier la liste de sélection. Le champ caché qui servait à stocker le thème sélectionné puis envoyé au code Arduino à l’aide d’une requête HTTP de type POST n’est plus utile. On va intercepter un clic sur la classe change-style-menu-item et on va récupérer le style sélectionné contenu dans l’attribut rel. Le code HTML de la boite de sélection devient donc le suivant.

.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
    ....

Maintenant on ajoute un script. Lorsqu’on intercepte un clic sur la classe change-style-menu-item à l’aide d’une commande jQuery. On récupère la valeur de l’attribut rel avec la commande $(this).attr(‘rel’). This contient l’objet qui vient d’être cliqué. On accède à l’attribut avec la méthode attr()

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);
  });
});

Pour comprendre et voir ce qui se passe, vous pouvez ouvrir les outils pour développeurs de votre navigateur et mettre un point d’arrêt.

esp8266 web server change theme bootstrap recupere attribut rel

Maintenant qu’on connait le thème sélectionné, on construit l’URL qui pointe vers le site internet correspondant. On en profite pour actualiser le libellé du bouton en y incluant le nom du nouveau thème. Enfin, si le stockage dans la base de données locale est disponible (cela va dépendre du navigateur utilisé, pas de l’ESP8266), en enregistre le thème.

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;
}

Maintenant il ne reste plus qu’à reconstruire la page avec ce thème. Pour que cela puisse fonctionner, il faut ajouter un attribut title=”main” dans le lien vers le CDN Bootstrap

link(href='https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/superhero/bootstrap.min.css', rel='stylesheet' title="main")

On modifie le href du link[title=”main”].

function set_theme(theme_url) {
  $('link[title="main"]').attr('href', theme_url);
}

Il nous reste encore deux fonctions à écrire, la première retourne vrai si le stockage en ligne est disponible

function supports_html5_storage(){
  try {
    return 'localStorage' in window && window['localStorage'] !== null;
  } catch (e) {
    return false;
  }
}

Et enfin la dernière fonction qui permet de recharger le thème s’il a été enregistré dans la base locale.

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));
    }
}

Voilà, maintenant tout se passe directement sur le navigateur, l’ESP8266 n’est plus chargé de reconstruire la page à chaque changement d thème, ce rôle est dévolu au navigateur.

Tout assembler

Voici les codes Arduino et HTML. Rien n’est encore fonctionnel à ce stade du développement (à l’exception du changement dynamique du thème).

Code Arduino/ESP8266

Créez un nouveau projet et collez le code suivant. Voici les différences par rapport au tutoriel précédent :

  • On appel la librairie FS.h qui permet d’accéder à la zone SPIFFS
  • Pour pouvoir accéder aux fichiers stockés dans la zone SPIFFS, il faut initialiser la librairie en appelant la fonction SPIFFS.begin()  dans le setup().
  • On indique au serveur les pages et les ressources statiques qui doivent être misent à disposition du navigateur, pour le moment /img et la racine du site (/) qui pointe vers la page index.html.

N’oubliez pas de changer le ssid et le password du réseau WiFi.

#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;

// Création des objets / create Objects
DHT dht(DHTPIN, DHTTYPE);
Adafruit_BMP085 bmp;
ESP8266WebServer server ( 80 );

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.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);
}

Sélectionnez l’ESP8266 (ici Wemos D1 mini) puis téléversez le projet dans l’ESP8266.

Générer le code HTML du projet

Allez dans le répertoire du projet et créez un nouveau dossier data. Ajouter un sous dossier img

Créer un nouveau document sous Geany et collez le code suivant. Enregistrez le fichier en lui donnant le nom index.pug à la racine du dossier data. Compilez le code HTML à l’aide de pug-cli (F8).

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='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js')
      link(rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.css")
      script(src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.js")
      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 ESP8266 Webserver + SPIFFS + 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
            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&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().     
        // 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;
          }
        }

Sinon, créez un nouveau document avec un simple éditeur de texte et collez le code HTML du projet obtenu. Enregistrez le fichier en lui donnant le nom index.html dans le répertoire data du projet Arduino.

<!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>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.js"></script>
    <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>
          <table id="tab_mesures" data-toggle="table" data-show-colunns="true">
            <thead>
              <tr>
                <th data-field="mesure" data-align="left" datsortable="true" data-formatter="labelFormatter">Mesure</th>
                <th data-field="valeur" data-align="left" datsortable="true" data-formatter="valueFormatter">Valeur</th>
                <th data-field="precedente" data-align="left" datsortable="true" data-formatter="vpFormatter">ValeuPr&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>    
      // 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;
        if ( typeof theme != 'undefined' ) {
          console.log("Recharge le theme " + 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>

Si le moniteur série est ouvert, fermez le. Allez dans le menu Outils et sélectionnez la vitesse d’upload speed de 921600 bauds. Le téléchargement est assez long, alors autant aller le plus vite possible. Enfin, allez dans le menu Outils puis cliquez sur ESP8266 Sketch Data Upload. L’opération démarre immédiatement.

esp8266 upload spiffs sketch data ide arduino

Une fois le téléchargement achevé, repassez la vitesse d’upload à 115200 baud et ouvrez le terminal pour vérifier que l’ESP8266 est correctement connecté au réseau WiFi. Récupérez l’adresse IP. Ouvrez un navigateur et saisissez l’adresse IP de l’ESP8266. Bravo, ça fonctionne !

esp8266 spiffs wemos d1 mini web server bootstrap dht22 bmp180

En résumé, nous avons vu comment créer une interface HTML de toute pièce à l’aide du langage Pug. On peut changer dynamiquement le thème Bootstrap et stocker le choix dans la base de données du navigateur pour le prochain chargement. On a mis en place une barre de navigation qui permet de regrouper les éléments par thèmes. On sait comment utiliser la zone SPIFFS pour y stocker tous les fichiers nécessaires au fonctionnement de l’interface. Il reste encore beaucoup à faire !

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