ESP8266. Comprendre le code Arduino d'un serveur web avec interface HTML • Domotique et objets connectés à faire soi-même

L’ESP8266 peut se programmer avec du code Arduino en C++ mais son principal intérêt reste sa connexion Wi-Fi. On pourra l’utiliser pour publier des données sur un serveur ou un Dashboard en ligne (ThingSpeak, Freeboard.io), créer facilement des objets connectés que l’on pourra piloter depuis un serveur domotique ou une application mobile (développée avec Blynk ou Cayenne par exemple).

Tutoriel actualisé le 28 août 2020

Dans ce tutoriel, nous allons apprendre comment ajouter un serveur web à du code Arduino pour ESP8266. Le serveur Web permettra d’afficher des données collectées ou stockée dans la mémoire flash de l’ESP8266 et d’interagir depuis une interface WEB. On pourra par exemple piloter un relai connecté au GPIO…

Installer le SDK ESP8266 sur l’IDE Arduino

Si le SDK ESP8266 est déjà installé sur votre environnement, vous pouvez passer au paragraphe suivant.

Avant de commencer, vous aurez besoin d’installer sur l’IDE Arduino le SDK ESP8266 du fabricant Espressif qui permet de développer et compiler du code Arduino pour les ESP8266.

Tout est expliqué en détail dans ce tutoriel

Débuter avec la librairie ESP8266WiFi

La librairie ESP8266WiFi est une adaptation (un portage) complète de la librairie WiFi pour Arduino faite par Espressif.

C’est très pratique car – en principe – le portage d’un code Arduino existant vers un ESP8266 ne nécessite presque aucun changement.

On va utiliser l’exemple WiFiWebServer installé avec le SDK pour expliquer comment on met en place un serveur WEB sur un ESP8266. One ne va pas détailler ici toutes les méthodes disponibles, tout est disponible ici.

Le mieux est de prendre un exemple pour comprendre les bases de la librairie.

Créer un nouveau croquis et coller le code suivant.

Ce petit programme créé un serveur web qui sera accessible depuis n’importe quel navigateur internet en saisissant l’adresse IP du module ESP8266. On pourra modifier l’état d’une sortie (GPIO) de l’ESP8266 en saisissant directement dans la barre d’adresse un requête HTTP de cette forme pour allumer la LED

IP_ESP8266/LED=ON

ou pour éteindre la LED

IP_ESP8266/LED=OFF

Avant de téléverser, n’oubliez pas de modifier le nom du réseau WiFi (SSID) et le mot de passe.

#include 
// Load Wi-Fi library
#include 

// Replace with your network credentials
const char* SSID = "REPLACE_WITH_YOUR_SSID"; 
const char* PASSWORD = "REPLACE_WITH_YOUR_PASSWORD";

// Set web server port number to 80
WiFiServer server(80);

// Assign output variables to GPIO pins
const int output = 4;

void setup() {
  Serial.begin(115200);
  // Initialize the output and set it to LOW
  pinMode(output, OUTPUT);
  digitalWrite(output, LOW);

  // Connect to Wi-Fi network with SSID and PASSWORD
  Serial.print("Connecting to ");
  Serial.println(SSID);
  WiFi.begin(SSID, PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address 
  Serial.println("");
  Serial.println("WiFi connected at IP address:");
  Serial.println(WiFi.localIP());

  // Start Web Server
  server.begin();
}

// Main loop
void loop(){
  // Create a client and listen for incoming clients
  WiFiClient client = server.available();   
  
  // Do nothing if server is not available
  if (!client) {
     return;
  }
  
  // Wait a client 
  while(!client.available()){}
  
  // A new client is connected, get the request
  String request = client.readStringUntil('\r');
  Serial.println(request);
  client.flush();

  int value = LOW;
  if (request.indexOf("/LED=ON") != -1) 
  {
    digitalWrite(output, HIGH);
    value = HIGH;
  } 
  if (request.indexOf("/LED=OFF") != -1)
  {
    digitalWrite(output, LOW);
    value = LOW;
  }
  
  // Display GPIO status
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println(""); 

  client.println("");
  client.println("");
  client.print("GPIO status: "); 

  if(value == HIGH) {
    client.print("ON");  
  } else {
    client.print("OFF");
  }

  client.println("

"); client.println("Switch manually GPIO state"); client.println("

"); client.println("Turn ON
"); client.println("Turn OFF
"); client.println(""); Serial.println(""); }

Comment fonctionne ce code ?

Pour créer un serveur Web, on déclare la librairie ESP8266WiFi

#include 

On créé une instance (un objet C++) qui contiendra le serveur web sur le port 80. Le port 80 est le port standard des pages internet.

WiFiServer server(80);

Vous pouvez connecter une LED sur une sortie de l’ESP8266 pour confirmer le fonctionnement des commandes.

const int output = D4;

Le démarrage du serveur web se fait lors de l’exécution après que l’ESP8266 soit connecté au réseau WiFi

WiFi.begin(SSID, PASSWORD); 
while (WiFi.status() != WL_CONNECTED) { 
   delay(500); 
   Serial.print("."); 
} 
server.begin();

Maintenant, dans la boucle loop, il suffit d’attendre la connexion d’un utilisateur depuis un navigateur Web

Pour cela, on vérifie que le serveur soit bien démarré. Si ce n’est pas le cas, on ne fait rien de plus.

WiFiClient client = server.available();
if (!client) {
  return;
}

Que se passe-t-il dans la boucle loop ?

On fait quelque chose uniquement si un client est connecté, c’est à dire qu’on réalise une requête HTTP sur l’ESP8266 depuis un navigateur internet

WiFiClient client = server.available();
if (!client) {
  return;
}

Dès qu’un client se connecte au serveur web – en saisissant l’adresse IP de l’ESP8266 dans la barre d’adresse du navigateur – on récupère la requête HTTP dans une variable de type String (chaîne)

String request = client.readStringUntil('\r'); 
Serial.println(request); 
client.flush();

Ouvrez le moniteur série et saisissez cette commande dans la barre d’adresse du navigateur en remplaçant par l’adresse IP de l’ESP8266

IP_ESP8266/LED=ON

Vous devriez obtenir un message de ce type sur le moniteur série, c’est la requête que l’on vient de récupérer sur l’ESP8266

GET /LED=OFF HTTP/1.1

Comme c’est une simple chaîne de caractère, il suffira de tester les différents cas avec la commande indexOf sur la variable req.

if (request.indexOf("/LED=ON") != -1) { 
   digitalWrite(output, HIGH); 
   value = HIGH; 
} if (request.indexOf("/LED=OFF") != -1) { 
   digitalWrite(output, LOW); 
   value = LOW; 
}

Il ne reste plus qu’à créer un affichage en retour. Les méthodes client.println() et client.print() permettent d’envoyer sur le navigateur du texte comme on le ferait sur le moniteur série

client.println("HTTP/1.1 200 OK"); 
client.println("Content-Type: text/html"); 
client.println(""); 
client.println(""); 
client.println(""); 
client.print("GPIO status: "); 
if(value == HIGH) { 
  client.print("ON"); 
} else { 
  client.print("OFF"); 
}

Voilà, vous pouvez maintenant piloter à distance tout matériel relié au GPIO (un relai, une led, un moteur, un servomoteur….) à laide d’une simple requête HTTP. Vous pouvez par exemple très simplement exécuter une commande depuis un logiciel domotique. Voici deux exemples, le premier pour Domoticz, le second pour Jeedom.

Astuce, comment ajouter une favicon

Il est très facile d’ajouter une favicon, la petite icône affichée à coté du titre de la page Web sur le navigateur internet

Tout d’abord, il faut récupérer l’image que l’on souhaite afficher sous la forme d’une chaine de caractère encodée en base64. Depuis votre moteur de recherche, trouvez un convertisseur d’image vers une chaîne en base64. Par exemple png to base64 converter pour convertir une image au format PNG.

Vous pouvez par exemple utiliser base64-image.de qui supporte de nombreux formats d’images. Il suffit de déposer l’image à convertir en base64 dans le champ drag & drop images anywhere.

Il suffit de coller l’image pour la récupérer sous la forme d’une chaine encodée en base64.

Il suffit ensuite d’ajouter une section head en collant. Remplacer base64_image_string par la chaine base64. Modifiez le type de l’image (jpg, png…)

client.println("");
  client.println("");
  client.println("");
  client.println("");
  client.println("");

Et voilà, la page possède maintenant une favicon dans la barre d’adresse

Ajouter un interface HTML au projet ESP8266 avec du code Arduino

Maintenant, vous voudriez certainement pouvoir réaliser une petite interface pour vos projets ESP8266. Pour cela, nous avons besoin de connaître quelques rudiments d’HTML. Nous n’allons pas aller très loin dans l’apprentissage de l’HTML, juste apprendre les éléments importants pour démarrer et avoir un projet fonctionnel. Si vous avez besoin de plus d’éléments d’interface, je vous conseille w3schools qui est une référence dans l’apprentissage de l’HTML. Le site est en anglais mais il est très clair et très simple d’accès.

Dans un projet ESP8266, on peut créer des pages HTML en dynamique, c’est à dire qu’on construit une chaine texte qui contient le code de la page qu’on va ensuite afficher. C’est ce que nous allons faire. Mais l’ESP8266 est capable également de fonctionner comme un vrai site internet, c’est à dire qu’on peut installer sur la mémoire flash les pages HTML, du code javascript, les feuilles de style CSS… Nous n’irons pas jusque là dans ce premier tutoriel.

Mini station météo (DHT22 + BMP180) pour tester

Je vous propose de créer une petite station météo pour avoir des données à actualiser régulièrement et créer un bouton pour activer / désactiver une sortie GPIO (juste une Led, pour l’exemple). Vous pouvez utiliser le matériel suivant

Circuit

Voici un tableau de repérage et de correspondance des broches entre Arduino et ESP8266.

Composant Broches Equivalence ESP8266 (Wemos D1 mini)
DHT22 VCC 5V
GND G
Data G5
BMP180 VCC 5V
GND G
SDA D2

On commence par déclarer les librairies nécessaires. N’oubliez pas d’installer les librairies depuis le gestionnaire de bibliothèques (DHT et BMP085).

Vous risquez de rencontrer une erreur à la compilation adafruit_Sensor.h : No such file or directory.

Dans ce cas téléchargez et décompressez la librairie manuellement depuis GitHub dans le dossier Arduino -> Librairie, puis relancez l’IDE pour quelle soit prise en compte.

#include 
#include 
#include 
#include 
#include 

On définit les variables du programme. Modifiez le réseau WiFi sur lequel vous allez vous connecter et le mot de passe de ce dernier.

#define ssid      "ssid"       // WiFi SSID
#define password  "password"  // WiFi password
#define DHTTYPE   DHT22       // DHT type (DHT11, DHT22)
#define DHTPIN    D4          // Broche du DHT / DHT Pin
#define LEDPIN    D3          // Led
float   t = 0 ;
float   h = 0 ;
float   p = 0;
String  etatLed = "OFF";

On créé les objets dht, bmp et server

DHT dht(DHTPIN, DHTTYPE);
Adafruit_BMP085 bmp;
ESP8266WebServer server ( 80 );

Cette première fonction permet de construire le code HTML de la page principale du programme. C’est une simple chaine de caractère. C’est un assemblage de chaînes. On peut facilement y inclure la valeur ou l’état d’une variable (par exemple l’état d’une sortie). En retour, la fonction renvoie une chaine contenant le code HTML de la page.

String getPage(){
  String page = "";
  page += "ESP8266 Demo - www.projetsdiy.fr";
  page += " body { background-color: #fffff; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }";
  page += "

ESP8266 Demo

";
  page += "

DHT22

";
  page += "
  • Temperature : "; page += t; page += "°C
  • « ; page +=  »

  • Humidite : "; page += h; page += "%

BMP180

";
  page += "
  • Pression atmospherique : "; page += p; page += " mbar
";
  page += "

GPIO

";
  page += "";
  page += "
  • D3 (etat: "; page += etatLed; page += ")ON"; page += "OFF
";
  page += "";
  page += "


www.projetsdiy.fr"; page += ""; return page; }

Voyons un peu mieux comment est construit le code

Code HTML Explication

  
  
  ESP8266 Demo - www.projetsdiy.fr
   body { background-color: #fffff; 
  font-family: Arial, Helvetica, Sans-Serif; 
  Color: #000088; }
lang permet de définir la langue de la page

head c’est l’entête de la page. Il contient différentes meta (paramètres)

  • http-equiv=’refresh’ c’est une page que le navigateur devra rafraichir. Pour plus de types, allez ici
  • content=’10’ toutes les 10 secondes

title le titre de la page affiché dans la barre du navigateur

style un style pour la page (couleur de fond, font à utiliser, couleur du texte

C’est le contenu de la page affiché

ESP8266 Demo

Un titre affiché en haut de la page

DHT22

h3 balise HTML pour afficher un titre plus petit pour le capteur DHT22
  • Température : xx°C
  • Humidité : xx%
Le bloc ul permet d’afficher sous forme de liste les informations.

  • D3 (etat: xx) ON OFF

Pour actualiser le GPIO, on utilise un formulaire form.

On utilise ici un bouton radio pour changer l’état (On/Off) puis on envoi submit le contenu du formulaire avec un bouton.

L’option name permet de nommer la variable qui contiendra l’état que l’on souhaite récupérer dans le code Arduino. Ici LED.

Toute balise ouverte doit être refermée (c’est mieux !)

La fonction handleRoot permet de surveiller si on reçoit un demande d’actualisation du GPIO en surveillant si l’argument LED est renvoyé par la page. Si c’est le cas, on exécute la fonction handleSubmit. A nous de créer

void handleRoot(){ 
  if ( server.hasArg("LED") ) {
    handleSubmit();
  } else {
    server.send ( 200, "text/html", getPage() );
  }  
}

La fonction handleSubmit traite l’actualisation du GPIO. On récupère l’état de la variable LED. Attention, c’est une chaine de caractère, on doit donc tester “1” et non pas 1. On en profite pour affecter l’état du GPIO dans la variable etatLed sous la forme d’une chaine, c’est plus sympa à lire. Enfin on actualise l’affichage de la page HTML avec server.send. On récupère la page actualisée en appelant la fonction getPage().

void handleSubmit() {
  // Actualise le GPIO / Update GPIO 
  String LEDValue;
  LEDValue = server.arg("LED");
  Serial.println("Set GPIO "); Serial.print(LEDValue);
  if ( LEDValue == "1" ) {
    digitalWrite(LEDPIN, 1);
    etatLed = "On";
    server.send ( 200, "text/html", getPage() );
  } else if ( LEDValue == "0" ) {
    digitalWrite(LEDPIN, 0);
    etatLed = "Off";
    server.send ( 200, "text/html", getPage() );
  } else {
    Serial.println("Err Led Value");
  }
}

Maintenant que toutes les fonctions sont créées, on peut appeler la fonction setup(). Elle initialise le BMP180, la connexion WiFi, branche la fonction qui s’occupe de la page principale et enfin on lance le serveur web

void setup() {
  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() );

  // On branche la fonction qui gère la premiere page / link to the function that manage launch page 
  server.on ( "/", handleRoot );

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

Il ne reste plus qu’à exécuter la fonction loop() pour relever régulièrement les mesures sur les capteurs. Contrairement à l’exemple précédent basé sur la librairie ESP8266WiFi, ici la librairie ESP8266WebServer nécessite de brancher la fonction callback server.handleClient() qui surveille la présence d’un client et délivre la page HTML demandée.

void loop() {
  server.handleClient();
  t = dht.readTemperature();
  h = dht.readHumidity();
  p = bmp.readPressure() / 100.0F;
  delay(1000);
}

Voici le code source assemblé du projet qu’il vous suffit de coller dans un nouveau projet puis le téléverser

#include 
#include 
#include 
#include 

#define ssid      "****"      // WiFi SSID
#define password  "****"      // WiFi password
#define DHTTYPE   DHT22       // DHT type (DHT11, DHT22)
#define DHTPIN    D4          // Broche du DHT / DHT Pin
#define LEDPIN    D3          // Led
float   t = 0 ;
float   h = 0 ;
float   p = 0;
String  etatLed = "OFF";

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

String getPage(){
  String page = "";
  page += "ESP8266 Demo - www.projetsdiy.fr";
  page += " body { background-color: #fffff; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }";
  page += "

ESP8266 Demo

";
  page += "

DHT22

";
  page += "
  • Temperature : "; page += t; page += "°C
  • « ; page +=  »

  • Humidite : "; page += h; page += "%

BMP180

";
  page += "
  • Pression atmospherique : "; page += p; page += " mbar
";
  page += "

GPIO

";
  page += "";
  page += "
  • D3 (etat: "; page += etatLed; page += ")"; page += "ON"; page += "OFF
";
  page += "";
  page += "


www.projetsdiy.fr"; page += ""; return page; } void handleRoot(){ if ( server.hasArg("LED") ) { handleSubmit(); } else { server.send ( 200, "text/html", getPage() ); } } void handleSubmit() { // Actualise le GPIO / Update GPIO String LEDValue; LEDValue = server.arg("LED"); Serial.println("Set GPIO "); Serial.print(LEDValue); if ( LEDValue == "1" ) { digitalWrite(LEDPIN, 1); etatLed = "On"; server.send ( 200, "text/html", getPage() ); } else if ( LEDValue == "0" ) { digitalWrite(LEDPIN, 0); etatLed = "Off"; server.send ( 200, "text/html", getPage() ); } else { Serial.println("Err Led Value"); } } void setup() { 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() ); // On branche la fonction qui gère la premiere page / link to the function that manage launch page server.on ( "/", handleRoot ); server.begin(); Serial.println ( "HTTP server started" ); } void loop() { server.handleClient(); t = dht.readTemperature(); h = dht.readHumidity(); p = bmp.readPressure() / 100.0F; delay(1000); }

Récupérez l’adresse IP de la Wemos en ouvrant le moniteur série puis connectez vous à celle-ci depuis un navigateur internet pour accéder à l’interface de la mini station météo.

Voilà, nous savons maintenant comment créer un serveur web à l’aide d’un ESP8266, piloter le GPIO et afficher des mesures en provenance de capteurs. Nous n’avons vu ici que les principaux rudiment pour débuter mais il couvrent déjà une grande partie de ce que l’on a besoin pour développer de petits objets connectés.

Stocker le code HTML de l’interface dans une variable PROGMEM R”===()===”

Cette façon d’écrire l’interface HTML d’un projet ESP8266 peut rapidement devenir problématique dès que l’interface prend de l’ampleur. A la place, il est possible d’utiliser l’opérateur C++ R qui permet de stocker n’importe quelle chaîne. Le R signifie “Traitez tout ce qui se trouve entre ces délimiteurs comme une chaîne brute”. Tout est expliqué en détail ici.

Le code de l’interface HTML sera donc stocker entre deux délimiteurs comme ceci.

R"delimiteur(code html de la page)delimiteur"

La chaîne entre les deux délimiteurs peut avoir n’importe quelle longueur et contenir n’importe quel caractère. Ce qui est important, c’est d’avoir le même délimiteur avant et après la chaîne. La chaîne est placée entre parenthèses.

Comme le code HTML peut être très long, il est préférable de le placer dans la mémoire flash plutôt que dans la SRAM, où il irait normalement. On peut faire cela à l’aide du mot clé PROGMEM qui est un modificateur de variable (documentation).

Ce qui donne ici

const char page[] PROGMEM = R"=====(

    
    
    ESP8266 Demo - www.projetsdiy.fr
     
      body { 
        background-color: #fffff; 
        font-family: Arial, Helvetica, Sans-Serif; 
        Color: #000088; 
      }
    "
    
    
    

ESP8266 Demo

"
    

DHT22

    
  • Temperature : %0.1d°C
  • Humidite : %0.1d%% »
    

BMP180

"
    
  • Pression atmospherique : %0.1d mbar
"
    

GPIO

    "
        
  • D3 (etat: %s »)ONOFF
        
        
www.projetsdiy.fr/> )=====";

Tout le code HTML reste une simple chaîne de caractère facile à manipuler. Vous avez du vois dans le texte qu’il y a des %s %0.1d et %u. Ce sont les emplacements des données que l’on va actualiser à chaque fois que la page change.

Pour mettre à jour la page, il suffit de remplacer chaque variable par sa valeur, voici comment faire

On détermine la taille de la page (le nombre de caractères). par sécurité, vous pouvez ajouter quelques caractères supplémentaires.

int pagesize = sizeof(page) + 10;

On prépare un buffer qui contiendra la page modifiée

char newpage[pagesize] = "";

Maintenant, il suffit de substituer chaque variable par sa valeur à l’aide de la méthode sprintf en utilisant le format indiqué. Par exemple %s pour une chaîne, %0.1d pour un nombre décimal avec un chiffre derrière la virgule…

sprintf(newpage, page, t, h, p, etatLed);

La fonction getPage() est beaucoup plus simple à écrire maintenant

String getPage()
{
  int pagesize = sizeof(page) + 10;
  char newpage[pagesize] = "";
  sprintf(newpage, page, t, h, p, etatLed);
  return newpage;
}

Lisez cet article pour connaître en détail tous les formats disponibles.

D’autres projets pour aller plus loin

L’interface obtenue avec du code HTML standard est assez basique. Heureusement, il est possible d’utiliser des frameworks pour avoir une interface beaucoup plus moderne. Bootstrap (développé par Twetter) est super simple à utiliser. Vous trouvez dans ces projets tout ce qu’il vous faut pour aller plus loin.

Mises à jour

28/08/2020 Ré-écriture de la présentation de la librairie ESP8266WiFi. Comment ajouter une favicon.

Avez-vous aimé cet article ?