Différences entre versions de « Esp8266 webserver »

De The Linux Craftsman
Aller à la navigation Aller à la recherche
 
(6 versions intermédiaires par le même utilisateur non affichées)
Ligne 137 : Ligne 137 :
 
</source>
 
</source>
 
L'inconvénient est que le HTML ne peut plus se trouver dans une variable global...
 
L'inconvénient est que le HTML ne peut plus se trouver dans une variable global...
 +
=== Contenu dynamique ===
 +
Pour renvoyer du contenu dynamique, il suffit d'utiliser un tampon qui va nous permettre de stocker la ou les variables à renvoyer.
 +
 +
Imaginons que nous voulions renvoyer un tableau avec l'état des broches. Nous allons ''couper'' la page en deux:
 +
{|style="width:650px" align="center"
 +
|
 +
<source lang="html">
 +
<!DOCTYPE html>
 +
<html>
 +
<head>
 +
<title>ESP8266</title>
 +
<meta charset="utf-8">
 +
</head>
 +
<body>
 +
</source>
 +
|width=5px|
 +
|valign=top|
 +
<source lang="html">
 +
</body>
 +
</html>
 +
</source>
 +
|}
 +
Ce qui donnera:
 +
<source lang="c">
 +
char header[] = "<!DOCTYPE html><html><head><title>ESP8266</title><meta charset=\"utf-8\"></head><body>";
 +
char footer[] = "</body></html>";
 +
</source>
 +
Il ne reste plus qu'à modifier notre fonction pour récupérer l'état des broches:
 +
<source lang="c">
 +
void maFonction(){
 +
  char buffer[800];
 +
  strcpy(buffer, header);
 +
  sprintf(buffer, "%s<table border=\"1\"><tr><td>D0</td><td>%s</td></tr>", buffer, digitalRead(D0) == HIGH ? "HIGH" : "LOW");
 +
  sprintf(buffer, "%s<tr><td>D1</td><td>%s</td></tr>", buffer, digitalRead(D1) == HIGH ? "HIGH" : "LOW");
 +
  sprintf(buffer, "%s<tr><td>D2</td><td>%s</td></tr>", buffer, digitalRead(D2) == HIGH ? "HIGH" : "LOW");
 +
  sprintf(buffer, "%s<tr><td>D3</td><td>%s</td></tr>", buffer, digitalRead(D3) == HIGH ? "HIGH" : "LOW");
 +
  sprintf(buffer, "%s<tr><td>D4</td><td>%s</td></tr>", buffer, digitalRead(D4) == HIGH ? "HIGH" : "LOW");
 +
  sprintf(buffer, "%s<tr><td>D5</td><td>%s</td></tr>", buffer, digitalRead(D5) == HIGH ? "HIGH" : "LOW");
 +
  sprintf(buffer, "%s<tr><td>D6</td><td>%s</td></tr>", buffer, digitalRead(D6) == HIGH ? "HIGH" : "LOW");
 +
  sprintf(buffer, "%s<tr><td>D7</td><td>%s</td></tr>", buffer, digitalRead(D7) == HIGH ? "HIGH" : "LOW");
 +
  sprintf(buffer, "%s<tr><td>D8</td><td>%s</td></tr></table>", buffer, digitalRead(D8) == HIGH ? "HIGH" : "LOW");
 +
  strcat(buffer, footer);
 +
  server.send(200, "text/html", buffer);
 +
}
 +
</source>
 +
# strcpy permet de copier une chaîne dans une autre et va nous permettre de rajouter le terminateur de chaîne (''\0'') à la variable buffer;
 +
# sprintf permet de générer des chaîne à partir de n'importe quelle variable (int, bool, etc...) et nous permet de concaténer les lignes de notre tableau;
 +
# strcat permet de concaténer le footer à buffer;
 +
 +
<u>NB</u>: on pourrait faire tout avec un seul sprintf, mais le code perdrait en visibilité.
 +
 +
Ce qui donne le tableau suivant:
 +
[[Fichier:Example dynamic html pin state.png|centré]]
  
 
==application/json==
 
==application/json==
 +
Lorsque l'on veut faire une API, le langage le plus approprié pour formater les données reste le JSON. Malheureusement il n'existe pas de programme pour la [https://fr.wikipedia.org/wiki/S%C3%A9rialisation sérialisation] sur l'ESP8266, il faut donc tout faire à la main.
 +
 +
Si on reprend l'exemple précédent:
 +
<source lang="c">
 +
void maFonction(){
 +
  char buffer[500];
 +
  sprintf(buffer, "{\"D0\":%s,", digitalRead(D0) == HIGH ? "true" : "false");
 +
  sprintf(buffer, "%s\"D1\":%s,", buffer, digitalRead(D1) == HIGH ? "true" : "false");
 +
  sprintf(buffer, "%s\"D2\":%s,", buffer, digitalRead(D2) == HIGH ? "true" : "false");
 +
  sprintf(buffer, "%s\"D3\":%s,", buffer, digitalRead(D3) == HIGH ? "true" : "false");
 +
  sprintf(buffer, "%s\"D4\":%s,", buffer, digitalRead(D4) == HIGH ? "true" : "false");
 +
  sprintf(buffer, "%s\"D5\":%s,", buffer, digitalRead(D5) == HIGH ? "true" : "false");
 +
  sprintf(buffer, "%s\"D6\":%s,", buffer, digitalRead(D6) == HIGH ? "true" : "false");
 +
  sprintf(buffer, "%s\"D7\":%s,", buffer, digitalRead(D7) == HIGH ? "true" : "false");
 +
  sprintf(buffer, "%s\"D8\":%s}", buffer, digitalRead(D8) == HIGH ? "true" : "false");
 +
  server.send(200, "application/json", buffer);
 +
}
 +
</source>
 +
Le navigateur comprend et interpréte le JSON parfaitement:
 +
[[Fichier:Example firefox json display pin state.png|centré]]
 +
= Formulaire et paramètres =
 +
On peut utiliser un formulaire pour envoyer des informations à l'ESP8266.
 +
 +
Imaginons que nous voulions piloter l'état des broches. Il nous faudrait une route pour le faire, ''/state'' par exemple, ainsi que trois paramètres ''gpio'' (pour la broche) et ''state'' pour spécifier l'état (0 &rarr; LOW, 1 &rarr; HIGH).
 +
 +
Voici le code sur l'ESP:
 +
<source lang="c">
 +
void setup(){
 +
  ...
 +
  server.on("/state", setState);
 +
  ...
 +
}
 +
 +
void setState(){
 +
  // Test de la présence du paramètre gpio
 +
  if(!server.hasArg("gpio")){
 +
    server.send(400, "text/plain", F("Missing parameter gpio"));
 +
    return;
 +
  }
 +
  // Test de la présence du paramètre state
 +
  if(!server.hasArg("state")){
 +
    server.send(400, "text/plain", F("Missing parameter state"));
 +
    return;
 +
  }
 +
  // Test de la présence du paramètre mode
 +
  if(!server.hasArg("mode")){
 +
    server.send(400, "text/plain", F("Missing parameter mode"));
 +
    return;
 +
  }
 +
  // Test de la valeur state (doit être 0 ou 1)
 +
  if(server.arg("state") != "1" && server.arg("state") != "0"){
 +
    // La valeur envoyée n'est pas valide
 +
    server.send(400, "text/plain", F("Wrong state value, must be 0 or 1"));
 +
    return;
 +
  }
 +
  // Initialisation de l'état
 +
  bool state = server.arg("state") == "1";
 +
  // Initialisation de la broche à -1
 +
  int gpio = -1;
 +
  if(server.arg("gpio") == "D0"){
 +
    gpio = D0;
 +
  }else if(server.arg("gpio") == "D1"){
 +
    gpio = D1;
 +
  }else if(server.arg("gpio") == "D2"){
 +
    gpio = D2;
 +
  }else if(server.arg("gpio") == "D3"){
 +
    gpio = D3;
 +
  }else if(server.arg("gpio") == "D4"){
 +
    gpio = D4;
 +
  }else if(server.arg("gpio") == "D5"){
 +
    gpio = D5;
 +
  }else if(server.arg("gpio") == "D6"){
 +
    gpio = D6;
 +
  }else if(server.arg("gpio") == "D7"){
 +
    gpio = D7;
 +
  }else if(server.arg("gpio") == "D8"){
 +
    gpio = D8;
 +
  }else{
 +
    // La valeur envoyée n'est pas valide
 +
    server.send(400, "text/plain", F("Wrong gpio value, must be between DO and D8"));
 +
    return;
 +
  }
 +
  // Test de la valeur state (doit être 0 ou 1)
 +
  if(server.arg("mode") != "O" && server.arg("mode") != "I"){
 +
    // La valeur envoyée n'est pas valide
 +
    server.send(400, "text/plain", F("Wrong mode value, must be O or I"));
 +
    return;
 +
  }
 +
  // initialisation du mode
 +
  int mode = server.arg("mode") == "O" ? OUTPUT : INPUT;
 +
  // Positionnement du mode de la broche
 +
  pinMode(gpio, mode);
 +
  // Positionnement de l'état de la broche
 +
  digitalWrite(gpio, state);
 +
  server.send(200, "text/plain", F(""));
 +
}
 +
</source>
 +
* server.hasArg() permet de tester la présence d'un paramètre
 +
* server.arg() permet de récupérer le paramètre sous forme de ''String''
 +
 +
Ce qui nous donne dans le navigateur:
 +
* S'il manque un paramètre :
 +
{|style="width:650px" align="center"
 +
|
 +
[[Fichier:Example missing parameter esp webserver.png|centré]]
 +
|width=5px|
 +
|valign=top|
 +
[[Fichier:Example missing parameter esp webserver off.jpg|centré|200px]]
 +
|}
 +
* Sinon :
 +
{|style="width:650px" align="center"
 +
|
 +
[[Fichier:Example parameter esp webserver.png|centré]]
 +
|width=5px|
 +
|valign=top|
 +
[[Fichier:Example missing parameter esp webserver on.jpg|centré|200px]]
 +
|}

Version actuelle datée du 16 avril 2019 à 04:40

Introduction

Warning manual.jpg

Soyez sûr de comprendre la section sur comment écrire un sketch avant de poursuivre. Le code ci-dessous fait référence à des parties bien spécifiques, détaillées et expliquées dans la section suscitée.

La puce ESP8266 doit d'abord être connectée en Wi-Fi avant d'aller plus loin !

Création du serveur

Il est très rapide de créer un serveur web sur l'ESP8266. Pour cela il faut passer par les étapes suivantes:

  • import de la librairie ESP8266WebServer
#include <ESP8266WebServer.h>
  • création de l'objet serveur:
ESP8266WebServer server(80);

Ajout d'une route

Dans la fonction setup() on peut maintenant faire le lien entre une URL et une fonction grâce à la fonction on:

void setup(){
  ...
  server.on("/", maFonction);
}

void maFonction(){
  // code de maFonction
}

Démarrage du serveur

Maintenant que le serveur est configuré, il ne reste plus qu'à le démarrer puis lui dire de s'occuper des clients:

  • dans la fonction setup, après la déclaration des routes, il faut utilisé begin:
void setup(){
  ...
  server.begin();
}
  • dans la fonction loop, il faut dire au serveur de s'occuper des clients:
void loop(){
  ...
  server.handleClient();
}

Ce qui donne le sketch suivant:

#include <ESP8266WebServer.h>
ESP8266WebServer server(80);

void setup(){
  server.on("/", maFonction);
  server.begin();
}

void loop(){
  server.handleClient();
}

void maFonction(){
  // code de maFonction
}

Notre serveur est prêt à répondre !

Réponses HTTP

Dans la fonction maFonction il faut maintenant dire au serveur quoi répondre et cela se fait grâce à la fonction send qui prend trois paramètres:

  1. le code http (200, 404, ...);
  2. l'entête Content-Type (text/plain, text/html, ...);
  3. le corps de la réponse;

text/plain

C'est le type de réponse le plus simple:

void maFonction(){
  server.send(200, "text/plain", "Coucou de l'ESP8266");
}

Ce qui nous donne l'échange suivant:

Esp8266 http plain text network frame.png

text/html

Pour afficher une page avec du HTML l'opération est un peu plus complexe.

Contenu statique

Le plus simple est de commencer avec une page qui ne change pas (contenu statique).

<!DOCTYPE html>
<html>
<head>
	<title>ESP8266</title>
</head>
<body>
	<h1>Coucou de l'ESP8266</h1>
</body>
</html>

Nous allons stocker la page dans une variable et pour cela nous allons devoir la minifier ! Il suffit de taper minify html sur google pour trouver des sites qui réalise cet opération gratuitement.

Minify html google search.png

Une fois le contenu minifier, voici le résultat:

<!DOCTYPE html><html><head><title>ESP8266</title></head><body><h1>Coucou de l'ESP8266</h1></body></html>

On peut maintenant déclarer une variable globale html:

char html[] = "<!DOCTYPE html><html><head><title>ESP8266</title></head><body><h1>Coucou de l'ESP8266</h1></body></html>";
Warning-icon.png

Le code HTML sus-mentionné ne contient pas de caractères " utilisés pour définir les valeurs des attributs HTML (eg. class="maclassecss").

Si c'est le cas de votre HTML vous devez échapper les caractères " en les faisant précéder du caractère d'échappement \.

Pour résumer: " devient \".

Pour effectuer les remplacement vous pouvez utiliser n'importe quel éditeur de texte (Notepad++, SublimeText, VSCode, ...) !

Voici le code de maFonction:

void maFonction(){
  server.send(200, "text/html", html);
}

Ce qui nous donne l'échange suivant:

Esp8266 http html text network frame.png

Si le contenu de votre page HTML dépasse 96 Kio (taille de la RAM de l'ESP8266) il faut dire à l'ESP8266 de ne pas faire transiter la variable par la RAM mais de la lire directement depuis la flash grâce à F():

server.send(200, "text/html", F("<!DOCTYPE html><html><head><title>ESP8266</title></head><body><h1>Coucou de l'ESP8266</h1></body></html>"));

L'inconvénient est que le HTML ne peut plus se trouver dans une variable global...

Contenu dynamique

Pour renvoyer du contenu dynamique, il suffit d'utiliser un tampon qui va nous permettre de stocker la ou les variables à renvoyer.

Imaginons que nous voulions renvoyer un tableau avec l'état des broches. Nous allons couper la page en deux:

<!DOCTYPE html>
<html>
<head>
	<title>ESP8266</title>
	<meta charset="utf-8">
</head>
<body>
</body>
</html>

Ce qui donnera:

char header[] = "<!DOCTYPE html><html><head><title>ESP8266</title><meta charset=\"utf-8\"></head><body>";
char footer[] = "</body></html>";

Il ne reste plus qu'à modifier notre fonction pour récupérer l'état des broches:

void maFonction(){
  char buffer[800];
  strcpy(buffer, header);
  sprintf(buffer, "%s<table border=\"1\"><tr><td>D0</td><td>%s</td></tr>", buffer, digitalRead(D0) == HIGH ? "HIGH" : "LOW");
  sprintf(buffer, "%s<tr><td>D1</td><td>%s</td></tr>", buffer, digitalRead(D1) == HIGH ? "HIGH" : "LOW");
  sprintf(buffer, "%s<tr><td>D2</td><td>%s</td></tr>", buffer, digitalRead(D2) == HIGH ? "HIGH" : "LOW");
  sprintf(buffer, "%s<tr><td>D3</td><td>%s</td></tr>", buffer, digitalRead(D3) == HIGH ? "HIGH" : "LOW");
  sprintf(buffer, "%s<tr><td>D4</td><td>%s</td></tr>", buffer, digitalRead(D4) == HIGH ? "HIGH" : "LOW");
  sprintf(buffer, "%s<tr><td>D5</td><td>%s</td></tr>", buffer, digitalRead(D5) == HIGH ? "HIGH" : "LOW");
  sprintf(buffer, "%s<tr><td>D6</td><td>%s</td></tr>", buffer, digitalRead(D6) == HIGH ? "HIGH" : "LOW");
  sprintf(buffer, "%s<tr><td>D7</td><td>%s</td></tr>", buffer, digitalRead(D7) == HIGH ? "HIGH" : "LOW");
  sprintf(buffer, "%s<tr><td>D8</td><td>%s</td></tr></table>", buffer, digitalRead(D8) == HIGH ? "HIGH" : "LOW");
  strcat(buffer, footer);
  server.send(200, "text/html", buffer);
}
  1. strcpy permet de copier une chaîne dans une autre et va nous permettre de rajouter le terminateur de chaîne (\0) à la variable buffer;
  2. sprintf permet de générer des chaîne à partir de n'importe quelle variable (int, bool, etc...) et nous permet de concaténer les lignes de notre tableau;
  3. strcat permet de concaténer le footer à buffer;

NB: on pourrait faire tout avec un seul sprintf, mais le code perdrait en visibilité.

Ce qui donne le tableau suivant:

Example dynamic html pin state.png

application/json

Lorsque l'on veut faire une API, le langage le plus approprié pour formater les données reste le JSON. Malheureusement il n'existe pas de programme pour la sérialisation sur l'ESP8266, il faut donc tout faire à la main.

Si on reprend l'exemple précédent:

void maFonction(){
  char buffer[500];
  sprintf(buffer, "{\"D0\":%s,", digitalRead(D0) == HIGH ? "true" : "false");
  sprintf(buffer, "%s\"D1\":%s,", buffer, digitalRead(D1) == HIGH ? "true" : "false");
  sprintf(buffer, "%s\"D2\":%s,", buffer, digitalRead(D2) == HIGH ? "true" : "false");
  sprintf(buffer, "%s\"D3\":%s,", buffer, digitalRead(D3) == HIGH ? "true" : "false");
  sprintf(buffer, "%s\"D4\":%s,", buffer, digitalRead(D4) == HIGH ? "true" : "false");
  sprintf(buffer, "%s\"D5\":%s,", buffer, digitalRead(D5) == HIGH ? "true" : "false");
  sprintf(buffer, "%s\"D6\":%s,", buffer, digitalRead(D6) == HIGH ? "true" : "false");
  sprintf(buffer, "%s\"D7\":%s,", buffer, digitalRead(D7) == HIGH ? "true" : "false");
  sprintf(buffer, "%s\"D8\":%s}", buffer, digitalRead(D8) == HIGH ? "true" : "false");
  server.send(200, "application/json", buffer);
}

Le navigateur comprend et interpréte le JSON parfaitement:

Example firefox json display pin state.png

Formulaire et paramètres

On peut utiliser un formulaire pour envoyer des informations à l'ESP8266.

Imaginons que nous voulions piloter l'état des broches. Il nous faudrait une route pour le faire, /state par exemple, ainsi que trois paramètres gpio (pour la broche) et state pour spécifier l'état (0 → LOW, 1 → HIGH).

Voici le code sur l'ESP:

void setup(){
  ...
  server.on("/state", setState);
  ...
}

void setState(){
  // Test de la présence du paramètre gpio
  if(!server.hasArg("gpio")){
    server.send(400, "text/plain", F("Missing parameter gpio"));
    return;
  }
  // Test de la présence du paramètre state
  if(!server.hasArg("state")){
    server.send(400, "text/plain", F("Missing parameter state"));
    return;
  }
  // Test de la présence du paramètre mode
  if(!server.hasArg("mode")){
    server.send(400, "text/plain", F("Missing parameter mode"));
    return;
  }
  // Test de la valeur state (doit être 0 ou 1)
  if(server.arg("state") != "1" && server.arg("state") != "0"){
    // La valeur envoyée n'est pas valide
    server.send(400, "text/plain", F("Wrong state value, must be 0 or 1"));
    return;
  }
  // Initialisation de l'état
  bool state = server.arg("state") == "1";
  // Initialisation de la broche à -1
  int gpio = -1;
  if(server.arg("gpio") == "D0"){
    gpio = D0;
  }else if(server.arg("gpio") == "D1"){
    gpio = D1;
  }else if(server.arg("gpio") == "D2"){
    gpio = D2;
  }else if(server.arg("gpio") == "D3"){
    gpio = D3;
  }else if(server.arg("gpio") == "D4"){
    gpio = D4;
  }else if(server.arg("gpio") == "D5"){
    gpio = D5;
  }else if(server.arg("gpio") == "D6"){
    gpio = D6;
  }else if(server.arg("gpio") == "D7"){
    gpio = D7;
  }else if(server.arg("gpio") == "D8"){
    gpio = D8;
  }else{
    // La valeur envoyée n'est pas valide
    server.send(400, "text/plain", F("Wrong gpio value, must be between DO and D8"));
    return;
  }
  // Test de la valeur state (doit être 0 ou 1)
  if(server.arg("mode") != "O" && server.arg("mode") != "I"){
    // La valeur envoyée n'est pas valide
    server.send(400, "text/plain", F("Wrong mode value, must be O or I"));
    return;
  }
  // initialisation du mode
  int mode = server.arg("mode") == "O" ? OUTPUT : INPUT;
  // Positionnement du mode de la broche
  pinMode(gpio, mode);
  // Positionnement de l'état de la broche
  digitalWrite(gpio, state);
  server.send(200, "text/plain", F(""));
}
  • server.hasArg() permet de tester la présence d'un paramètre
  • server.arg() permet de récupérer le paramètre sous forme de String

Ce qui nous donne dans le navigateur:

  • S'il manque un paramètre :
Example missing parameter esp webserver.png
Example missing parameter esp webserver off.jpg
  • Sinon :
Example parameter esp webserver.png
Example missing parameter esp webserver on.jpg