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"}
Sommaire
Téléverser les ressources dans le système de fichier SPIFFS pour diminuer la vitesse de chargement
Il est possible d’éviter d’aller chercher les ressources sur internet (jquery, bootstrap, bootstrap-table…) à chaque fois qu’on se connecte à l’interface WEB de l’ESP8266 en embarquant toutes les resources dans le répertoire Data.
Remarque. 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 (connexion internet limitée ou réduction des data consommée d’un forfait 4G), il est nécessaire de tout embarquer dans la zone SPIFFS.
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")
Code complet du projet intermédiaire
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étéo (DHT22 + BMP180)
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
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écé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='http://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 + "";
} else if ( value === "Humidité" ) {
label = value + "";
} else if ( value === "Pression Atmosphérique" ) {
label = value + "";
} else {
label = value;
}
return label;
}
function valueFormatter(value, row){
//console.log("valueFormatter");
var label = "";
if ( row.valeur > row.precedente ) {
label = value + row.unite + "";
} else {
label = value + row.unite + "";
}
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è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é
Le fichier HTML généré qu’il vous suffit de téléverser sur la zone SPIFFS de l’ESP8266
Demo ESP8266 SPIFFS + Boostrap - www.projetsdiy.fr
Demo Webserver ESP8266 + Bootstrap
Mini station météo (DHT22 + BMP180)
Mesure | Valeur | Valeur Précédente |
---|
Graphs
GPIO
D5 OFF
ON OFF
D6 OFF
ON OFF
D7 OFF
ON OFF
D8 OFF
ON OFF
Configuration
Theme
Version francaise : www.projetsdiy.fr
English version : www.diyprojects.io
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 + ""; } else if ( value === "Humidité" ) { label = value + ""; } else if ( value === "Pression Atmosphérique" ) { label = value + ""; } else { label = value; } return label; } function valueFormatter(value, row){ //console.log("valueFormatter"); var label = ""; if ( row.valeur > row.precedente ) { label = value + row.unite + ""; } else { label = value + row.unite + ""; } 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è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 Arduino compatible ESP8266
Le code Arduino qu’il vous suffit de téléverser après avoir modifié les paramètres de connexion au réseau Wi-Fi.
#include
#include
#include
#include
#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);
}
Interface WEB obtenue
Depuis un navigateur internet, saisissez l’adresse IP de l’ESP8266.
Vous avez maintenant des mesures qui s’affichent
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.
Dans le prochain tutoriel, nous verrons comment enregistrer les mesures pour créer un historique et créer des graphiques et des gauges.
Accéder rapidement aux autres parties du projet
Voici les liens pour accéder aux autres parties du projet
Vous êtes ici
Avez-vous aimé cet article ?