Web Server con esp8266 e esp32: servire pagine e gestire LEDs – 1
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
Ciao,
sto gestendo un web server su wemos d1 in modalità WIFI_AP. E’ possibile far in modo che quando un client seleziona la rete venga aperto direttamente il browser puntando direttamente alla home page anzichè aprire il browser e digitare l’indirizzo ip?
Grazie
G
Ciao Gabriele,
si è possibile, basta dare una regola al DNS per ridirigere tutte le chiamate sull’ip dell’AP
Ho implementato questa features sul LoRa E32 Manager lo trovi qui
Ciao Renzo
Bello, complimenti.
Come è possibile (con arduino in generale… ESP01, ESP12, Wemos D1 mini ecc..) dal modulo/scheda programmata come SERVER inviare una richiesta ad altro server ovvero agire come client??? ho fatto diversi tentativi ma non riesco a fare convivere le varie/diverse librerie necessarie… ESP 32 consente questo tipo di situazione ovvero integra entrambe le funzionalità? Dove trovare esempi di codice…
Più in generale ad esempio, il server deve rispondere al client con una stringa recuperata da un file di testo presente su altro server remoto.Grazie.
Ciao Claudio,
premesso che non è la soluzione migliore per scambiare messaggi tra dispositivi tramite WiFi (ma che funziona più che bene), puoi creare un server REST che espone una GET
How to create a REST server on esp8266 or esp32: CORS request, OPTION and GET – Part 4
e un client che consuma la GET
#include
[...]
http.begin("http://192.168.1.145/urldellaget");
int httpCode = http.GET();
String payload = http.getString();
Serial.println(payload);
http.end();
Ciao Renzo
Grazie mille per la cortese risposta… alla fine sono riuscito a fare coesistere il codice servere e client HTTPS sullo stesso dispositivo… non mi funzionava per una semplice banalità… avevo omesso il parametro di rete DNS !!!
Secondo la tua opinione quindi… qual’è la soluzione migliore per scambiare messaggi tra dispositivi tramite WiFi???… UDP ??? …. o cosa altro?
Ancora grazie…
Ciao Claudio,
secondo me per reti interne ve bene anche UDP senza pretese o TCP.
Se ti serve qualche cosa di più efficiente ti consiglio websocket.
Ma se i messaggi sono pochi o estemporanei i REST sono perfetti.
Dipende dal progetto, se apri un topic sul forum, e descrivi la casistica potrebbe essere un’interessante argomento.
Ciao Renzo
Grazie per la guida Renzo, spero tu non apra mai un canale yt, è così bello leggere e forse anche più comprensibile.
Una cosa che negli ultimi anni si è un po’ persa.
(si lo so sono un boomer 🙂 )
Hehehe… Prima o poi le proverò tutte.
Ma io non riesco a seguire i tutorial di YouTube, mi risultano troppi lenti rispetto alla versione scritta.
Ciao e grazie Renzo