Sono stupefatto dall’enorme potenza che piccoli dispositivi WiFi, come l’esp8266 e l’esp32 hanno.
Sono in grado di servire un intero sito Web, e questo ci consente di realizzare cose veramente interessanti.
Un server web è un server software, o hardware in grado di soddisfare le richieste dei clienti sul World Wide Web. Un server web può, in generale, contenere uno o più siti web. Un server web elabora le richieste di rete in entrata sull’HTTP e molti altri protocolli correlati.
Wikipedia
La funzione principale di un server Web è archiviare, elaborare e fornire pagine Web ai client. La comunicazione tra client e server avviene utilizzando il protocollo HTTP (Hypertext Transfer Protocol). Le pagine fornite sono di solito documenti HTML, che possono includere immagini, fogli di stile e script oltre a contenuti testuali.
Puoi gestire programmaticamente la pagina web (restituendo una stringa) o una soluzione migliore è mettere la tua pagina sul filesystem SPIFFS, maggiori informazioni su SPIFFS in questo articolo “WeMos D1 mini (esp8266): SPIFFS FileSystem integrato” o per esp32 “ESP32: fileSystem integrato SPIFFS“.
Una pagina Web si composta in maniera similare ai REST, puoi approfondire le chiamate REST in questo articolo “Come creare un server REST su esp8266 o esp32“.
Librerie standard
Per gli esempi standard è necessario installare dal gestore della librerie l’ESP8266WiFi e ESP8266WebServer per l’esp8266, WiFi e WebServer per l’esp32.
Basi
Ecco un semplice esempio dichiarativo
/*
* WeMos D1 mini (esp8266)
* Simple web server
* URI served / and /inline
* by Mischianti Renzo <https://mischianti.org>
*
* https://mischianti.org/category/tutorial/how-to-create-a-web-server-with-esp8266-and-esp32/
*
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
const char* ssid = "<YOUR-SSID>";
const char* password = "<YOUR-PASSWD>";
ESP8266WebServer server(80);
void setup(void) {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.on("/", []() {
server.send(200, "text/plain", "Hello from esp8266 server web!");
});
server.on("/inline", []() {
server.send(200, "text/plain", "this works as well");
});
server.onNotFound([]() {
server.send(404, "text/plain", "Uri not found "+server.uri());
});
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
server.handleClient();
}
Per gli esp32 devi solo andare a sostituire queste righe
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
[...]
ESP8266WebServer server(80);
con
#include <WiFi.h>
#include <WebServer.h>
[...]
WebServer server(80);
Il codice è abbastanza semplice, ecco la dichiarazione del Server
ESP8266WebServer server(80);
Dove 80 è la porta, che poi è quella predefinita dell’HTTP, quindi puoi ometterla quando scrivi l’URL.
server.on("/", []() {
server.send(200, "text/plain", "Hello from esp8266 server web!");
});
server.on("/inline", []() {
server.send(200, "text/plain", "this works as well");
});
server.onNotFound([]() {
server.send(404, "text/plain", "Uri not found "+server.uri());
});
Quindi devi specificare quale uri puoi gestire, utilizzerai server.on con il percorso.
Quindi se scrivi http://192.168.1.143/
andrai a server.on("/"
se scrivi http://192.168.1.143/inline
andrai a server.on ("/inline"
.
Altra cosa importante è la gestione dei non trovati con server.onNotFound
ed andremo a restituire l’http status 404 ed un messaggio per avvisare l’utente.
Come puoi vedere la risposta è gestita da server.send
dove il primo parametro è lo status code http poi il mime type e poi il messaggio.
Il tipo mime dice al browser che tipo di risposta è arrivata, quindi il browser attiva il parser corretto del messaggio.
Devi connetterti con la porta seriale per scoprire l’ip da inserire sul browser.
.......
Connected to reef-casa-sopra
IP address: 192.168.1.143
HTTP server started
Prova a scrivere l’url di base http://192.168.1.143
che corrisponde a http://192.168.1.143:80/
e otterrai l’url relativa /.
AsyncWebServer
Un’altra libreria molto popolare è AsyncWebServer, esiste una versione per esp8266 e esp32, devi solo cambiare le librerie di base.
Per ESP8266 richiede ESPAsyncTCP. Per utilizzare questa libreria potrebbe essere necessario disporre delle ultime versioni git di ESP8266 Arduino Core.
Per ESP32 richiede l’AsyncTCP per funzionare. Per utilizzare questa libreria potrebbe essere necessario disporre delle ultime versioni git di ESP32 Arduino Core
Questo è un server completamente asincrono e come tale non viene eseguito sul thread del ciclo.
Ecco l’esempio precedente per esp32 con questa libreria.
/*
* ESP32
* Simple web server
* URI served / and /inline
*
* by Mischianti Renzo <https://mischianti.org>
*
* https://mischianti.org/category/tutorial/how-to-create-a-web-server-with-esp8266-and-esp32/
*
*/
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
const char* ssid = "<YOUR-SSID>";
const char* password = "<YOUR-PASSWD>";
AsyncWebServer server(80);
void setup(void) {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.on("/", [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello from esp8266 server web!");
});
server.on("/inline", [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "this works as well");
});
server.onNotFound([](AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Uri not found "+request->url());
});
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
// server.handleClient();
}
Gestire pagine HTML
Naturalmente, dobbiamo organizzare meglio il nostro codice e gestire un comportamento più complesso magari in una funzione separata.
Ora faremo una semplice pagina html.
/*
* WeMos D1 mini (esp8266)
* Simple web server an html page served
* URI served / and /inline
* by Mischianti Renzo <https://mischianti.org>
*
* https://mischianti.org/category/tutorial/how-to-create-a-web-server-with-esp8266-and-esp32/
*
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
const char* ssid = "<YOUR-SSID>";
const char* password = "<YOUR-PASSWD>";
ESP8266WebServer server(80);
void handleRoot() {
char temp[400];
int sec = millis() / 1000;
int min = sec / 60;
int hr = min / 60;
snprintf(temp, 400,
"<html>\
<head>\
<meta http-equiv='refresh' content='5'/>\
<title>ESP8266 Demo</title>\
<style>\
body { background-color: #00ffff4d; font-family: Arial, Helvetica, Sans-Serif; Color: blue; }\
</style>\
</head>\
<body>\
<center>\
<h1>Hi from your esp8266 HTML page!</h1>\
<p>Uptime: %02d:%02d:%02d</p>\
</center>\
</body>\
</html>",
hr, min % 60, sec % 60
);
server.send(200, "text/html", temp);
}
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
void setup(void) {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/inline", []() {
server.send(200, "text/plain", "this works as well");
});
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
server.handleClient();
}
Per prima cosa puoi vedere che ho messo send
all’interno di una funzione per avere un codice più pulito, ora anche il mime type
dell’errore è text/html
.
server.send(200, "text/html", temp);
Per restituire una pagina html devo aggiungere text/html
al mime type, così che il browser prende il testo che invii e lo gestisce come una pagina HTML, nell’html inserisco anche un elemento variabile (l’uptime) ed ogni volta che viene generata la pagina quel valore cambierà.
Uptime: 00:10:10
La direttiva < meta http-equiv='refresh' content='5' />
sull’head
dice al browser di aggiornare la pagina ogni 5 secondi in modo da avere aggiornato l’uptime in automatico.
AsyncWebServer
Ecco l’esempio precedente per esp32 con questa libreria.
/*
* ESP32
* Simple web server an html page served
* URI served / and /inline
*
* by Mischianti Renzo <https://mischianti.org>
*
* https://mischianti.org/category/tutorial/how-to-create-a-web-server-with-esp8266-and-esp32/
*
*/
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
const char* ssid = "<YOUR-SSID>";
const char* password = "<YOUR-PASSWD>";
AsyncWebServer server(80);
void handleRoot(AsyncWebServerRequest *request) {
char temp[400];
int sec = millis() / 1000;
int min = sec / 60;
int hr = min / 60;
snprintf(temp, 400,
"<html>\
<head>\
<meta http-equiv='refresh' content='5'/>\
<title>ESP8266 Demo</title>\
<style>\
body { background-color: #00ffff4d; font-family: Arial, Helvetica, Sans-Serif; Color: blue; }\
</style>\
</head>\
<body>\
<center>\
<h1>Hi from your esp8266 HTML page!</h1>\
<p>Uptime: %02d:%02d:%02d</p>\
</center>\
</body>\
</html>",
hr, min % 60, sec % 60
);
request->send(200, "text/html", temp);
}
void handleNotFound(AsyncWebServerRequest *request) {
String message = "File Not Found\n\n";
message += "URI: ";
message += request->url();
message += "\nMethod: ";
message += (request->method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += request->args();
message += "\n";
for (uint8_t i = 0; i < request->args(); i++) {
message += " " + request->argName(i) + ": " + request->arg(i) + "\n";
}
request->send(404, "text/plain", message);
}
void setup(void) {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/inline", [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "this works as well");
});
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
}
Richiamare un end-point e gestire il LED integrato.
È importante recuperare dati ma è importante anche inviare informazioni, e questo comportamento normalmente è ottenuto utilizzando il protocollo REST, ma ora creeremo 2 semplici endpoint che avranno un comportamento diverso senza alcun parametro.
Cioè aggiungeremo 2 end point serviti come 2 pagine web separate.
In questo sketch aggiungiamo 2 pulsanti per attivare e disattivare un led, funzionano come un collegamento a una semplice pagina web che cambierà anche lo stato del microcontrollore.
/*
* WeMos D1 mini (esp8266)
* Simple web server an html page served with 2 end point
* that change status of builtin LED
* URI served / /ledon /ledoff
* by Mischianti Renzo <https://mischianti.org>
*
* https://mischianti.org/category/tutorial/how-to-create-a-web-server-with-esp8266-and-esp32/
*
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#define LED_PIN LED_BUILTIN
#define LED_ON LOW
#define LED_OFF HIGH
const char* ssid = "<YOUR-SSID>";
const char* password = "<YOUR-PASSWD>";
ESP8266WebServer server(80);
bool ledStatus = false;
String handleRoot(bool ledStatus = false) {
String ptr =
"<html>\
<head>\
<title>ESP8266 Demo</title>\
<style>\
body { background-color: #00ffff4d; font-family: Arial, Helvetica, Sans-Serif; Color: blue; }\
</style>\
</head>\
<body>\
<center>\
<h1>Hi from your esp8266 HTML page!</h1>\
";
if (ledStatus) {
ptr +=
"<p>LED: ON</p><a class=\"button button-off\" href=\"/ledoff\">OFF</a>\n";
} else {
ptr +=
"<p>LED: OFF</p><a class=\"button button-on\" href=\"/ledon\">ON</a>\n";
}
ptr += "</center>\
</body>\
</html>";
return ptr;
}
void handleRootDefault(){
ledStatus = false;
digitalWrite(LED_PIN, LED_OFF);
Serial.println("LED Status: OFF");
server.send(200, "text/html", handleRoot(ledStatus));
}
void handleLedOn() {
ledStatus = true;
Serial.println("LED Status: ON");
digitalWrite(LED_PIN, LED_ON);
server.send(200, "text/html", handleRoot(ledStatus));
}
void handleLedOff() {
ledStatus = LOW;
Serial.println("LED Status: OFF");
digitalWrite(LED_PIN, LED_OFF);
server.send(200, "text/html", handleRoot(ledStatus));
}
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
void setup(void) {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
pinMode(LED_PIN, OUTPUT);
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.on("/", handleRootDefault);
server.on("/ledon", handleLedOn);
server.on("/ledoff", handleLedOff);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
server.handleClient();
}
Perciò se andiamo a scivere http://192.168.1.143:80/ledon
server.on("/ledon", handleLedOn);
chiamiamo la relativa funzione handleLedOn
che fa una digitalWrite
del valore del led.
void handleLedOn() {
ledStatus = true;
Serial.println("LED Status: ON");
digitalWrite(LED_PIN, LED_ON);
server.send(200, "text/html", handleRoot(ledStatus));
}
alla fine della funzione chiama il comando che genera il codice html di risposta con aspetto diverso se il parametro ledStatus
è vero o falso.
AsyncWebServer
Ora con l’AsyncWebserver.
/*
* ESP32
* Simple web server an html page served with 2 end point
* that change status of builtin LED
* URI served / /ledon /ledoff
*
* by Mischianti Renzo <https://mischianti.org>
*
* https://mischianti.org/category/tutorial/how-to-create-a-web-server-with-esp8266-and-esp32/
*
*/
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
#define LED_PIN LED_BUILTIN
#define LED_ON LOW
#define LED_OFF HIGH
const char* ssid = "<YOUR-SSID>";
const char* password = "<YOUR-PASSWD>";
AsyncWebServer server(80);
bool ledStatus = false;
String handleRoot(bool ledStatus = false) {
String ptr =
"<html>\
<head>\
<title>ESP8266 Builtin led control</title>\
<style>\
body { background-color: #00ffff4d; font-family: Arial, Helvetica, Sans-Serif; Color: blue; }\
</style>\
</head>\
<body>\
<center>\
<h2>ESP8266 Builtin led control!</h2>\
";
if (ledStatus) {
ptr +=
"<p>LED: ON</p><a class=\"button button-off\" href=\"/ledoff\">OFF</a>\n";
} else {
ptr +=
"<p>LED: OFF</p><a class=\"button button-on\" href=\"/ledon\">ON</a>\n";
}
ptr += "</center>\
</body>\
</html>";
return ptr;
}
void handleRootDefault(AsyncWebServerRequest *request){
ledStatus = false;
digitalWrite(LED_PIN, LED_OFF);
Serial.println("LED Status: OFF");
request->send(200, "text/html", handleRoot(ledStatus));
}
void handleLedOn(AsyncWebServerRequest *request) {
ledStatus = true;
Serial.println("LED Status: ON");
digitalWrite(LED_PIN, LED_ON);
request->send(200, "text/html", handleRoot(ledStatus));
}
void handleLedOff(AsyncWebServerRequest *request) {
ledStatus = LOW;
Serial.println("LED Status: OFF");
digitalWrite(LED_PIN, LED_OFF);
request->send(200, "text/html", handleRoot(ledStatus));
}
void handleNotFound(AsyncWebServerRequest *request) {
String message = "File Not Found\n\n";
message += "URI: ";
message += request->url();
message += "\nMethod: ";
message += (request->method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += request->args();
message += "\n";
for (uint8_t i = 0; i < request->args(); i++) {
message += " " + request->argName(i) + ": " + request->arg(i) + "\n";
}
request->send(404, "text/plain", message);
}
void setup(void) {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
pinMode(LED_PIN, OUTPUT);
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.on("/", handleRootDefault);
server.on("/ledon", handleLedOn);
server.on("/ledoff", handleLedOff);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
}
Grazie
Naturalmente questo è un uso troppo semplice di un Web Server, quindi nel prossimo capitolo andremo più nel dettaglio.
- Web Server su esp8266 e esp32: servire pagine e gestire LEDs
- Web Server su esp8266 e esp32: servire pagine compresse come byte array e SPIFFS
- Web Server su esp8266 e esp32: web server generico multiuso
- Web Server su esp8266 e esp32: gestione sicurezza ed autenticazione
- Web Server su esp8266 e esp32: aggiunta di un back-end REST protetto
- Web Server su esp8266 e esp32: Interfaccia Web sicura per temperatura ed umidità di un DHT
Codice ed esempio su repository GitHub