Cómo emplear el ESP8266 o ESP32 como servidor HTTP


En esta entrada vamos a empezar a ver el uso de ESP8266 y el ESP32 como servidor, uno de los roles más habituales en los que vamos a emplear y que, ya adelantamos, va a ocuparnos unas cuantas entradas.

Haremos referencia al ESP8266, pero el mismo código es compatible para el ESP32, ajustando el nombre de las librerías. Al final tenéis el código tanto para el ESP8266 como para el ESP32.

En la entrada anterior sobre el ESP8266 vimos el funcionamiento como cliente y remarcamos que, aunque a veces es un rol olvidado, es tan importante como el de servidor.

Recordar que en una M2M (machine to machine) el cliente es el dispositivo que inicia la conexión. Por su parte, el servidor recibe las peticiones de los clientes, ejecuta las acciones oportunas en el backend, y devuelve una respuesta.

¿Para qué usarlo cómo servidor?

En este caso, vamos a emplear el ESP8266 como servidor. Por tanto, el ESP8266 servidor va a estar permanentemente encendido y a recibir peticiones de los clientes (por ejemplo, un ordenador, un móvil u otro ESP8266).

Los clientes se conectan con el ESP8266 servidor y realizan una petición HTTP a una URI. Opcionalmente envían información sobre la petición como parámetros.

El ESP8266 servidor recibe y procesa la petición, ejecuta las acciones oportunas en el backend (encender un LED, mover un motor, leer un sensor) y devuelve una respuesta al cliente.

En muchas ocasiones, el servidor va a devolver un fichero HTML para renderizarse en el cliente (frontend). Pero remarcar que no siempre es así. Alguna (o todas) de las URIs pueden ser simplemente endpoints que ofrecen un API al cliente.

Como vemos, hay bastante mandanga que explicar, y nos va a ocupar unas cuantas entradas, de más a menos fácil. Empezamos por lo más sencillo, como montar un servidor simple con el ESP8266.

ESP8266 como servidor sencillo

De forma similar a lo que pasaba al actuar como cliente y la librería ESP8266HTTPClient, el uso del ESP8266 como servidor es muy sencillo gracias al trabajo de la comunidad en el desarrollo de la librería ESP8266WebServer.

Para usarla primero debemos instanciar un objeto de la clase ESP8266WebServer a través de uno de sus constructores.

ESP8266WebServer(IPAddress addr, int port = 80);
ESP8266WebServer(int port = 80);

E iniciarlo a través de una de sus funciones 'begin(...)'

void begin();
void begin(uint16_t port);
 

A continuación, tenemos que definir los "ruteos", es decir, la asociación entre la URI de la petición y la acción de callback que se ejecutará. Para ello disponemos de las funciones 'on(...)', que establecen los ruteos.

Posteriormente, en el Loop, simplemente debemos llamar a la función 'handleClient()' que se encarga de recibir las peticiones de los clientes y lanzar las funciones de callback asociadas en el ruteo.

Para enviar la respuesta al cliente empleamos las funciones 'send(...)'. Finalmente, podemos finalizar la conexión del cliente con 'close()' o parar el servidor por completo con 'stop()'.

En el caso del ESP32 la librería se llama 'ESP8266WebServer' se llama simplemente 'WebServer'.

Ejemplo ESP8266 como servidor

Vamos a verlo mejor con un ejemplo práctico, un típico y sencillo 'Hola mundo'. En este ejemplo nos conectamos al WiFi, y creamos un servidor en el puerto 80. A continuación definimos 2 endpoints, '/' y '/inline', y una función default para el caso en que se solicite una URI no reconocida.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
 
#include "config.h"  // Sustituir con datos de vuestra red
#include "ESP8266_Utils.hpp"

ESP8266WebServer server(80);
 
// Funcion que se ejecutara en la URI '/'
void handleRoot() 
{
   server.send(200, "text/plain", "Hola mundo!");
}
 
// Funcion que se ejecutara en URI desconocida
void handleNotFound() 
{
   server.send(404, "text/plain", "Not found");
}
 
void setup(void) 
{
   Serial.begin(115200);
   
   ConnectWiFi_STA();
 
   // Ruteo para '/'
   server.on("/", handleRoot);
 
   // Ruteo para '/inline' usando función lambda
   server.on("/inline", []() {
      server.send(200, "text/plain", "Esto tambien funciona");
   });
 
   // Ruteo para URI desconocida
   server.onNotFound(handleNotFound);
 
   // Iniciar servidor
   server.begin();
   Serial.println("HTTP server started");
}
 
void loop()
{
   server.handleClient();
}

Si cargamos este programa en el ESP8266 veremos que nos indica la dirección IP de nuestro dispositivo.

Ahora, si nos conectamos con un navegador a esta dirección IP veremos el contenido servido desde el ESP8266.

¡Enhorabuena, ya tenéis un servidor correctamente configurado!

Ejemplo simplificado

Como de costumbre, vamos a dividir el código para que sea más sencillo de usar y mantener. A los ya habituales ficheros 'config.h' y 'ESP8266_Utils.hpp' vamos a añadir un fichero 'Server.hpp' que contenga la lógica asociada al funcionamiento del servidor. En este ejemplo, tendrá este contenido.

// Funcion que se ejecutara en la URI '/'
void handleRoot() 
{
   server.send(200, "text/plain", "Hola mundo!");
}
 
// Funcion que se ejecutara en URI desconocida
void handleNotFound() 
{
   server.send(404, "text/plain", "Not found");
}

void InitServer()
{
   // Ruteo para '/'
   server.on("/", handleRoot);
 
   // Ruteo para '/inline' usando función lambda
   server.on("/inline", []() {
      server.send(200, "text/plain", "Esto tambien funciona");
   });
 
   // Ruteo para URI desconocida
   server.onNotFound(handleNotFound);
 
   // Iniciar servidor
   server.begin();
   Serial.println("HTTP server started");
}

Por lo que el programa principal queda reducido a,

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
 
ESP8266WebServer server(80);
 
#include "config.h"  // Sustituir con datos de vuestra red
#include "ESP8266_Utils.hpp"
#include "Server.hpp"

void setup(void) 
{
   Serial.begin(115200);
   
   ConnectWiFi_STA();
 
   InitServer();
}
 
void loop()
{
   server.handleClient();
}

ESP8266WebServer en detalle

Los métodos 'on(...)' admiten distintas sobrecargas que permiten, por ejemplo, distinguir el tipo de petición recibida.

void on(const String &uri, THandlerFunction handler);
void on(const String &uri, HTTPMethod method, THandlerFunction fn);
void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
void onNotFound(THandlerFunction fn);  //called when handler is not assigned
void onFileUpload(THandlerFunction fn); //handle file uploads

Por su parte, las funciones 'send(...) también tienen distintas variantes con diferentes parámetros

void send(int code, const char* content_type = NULL, const String& content = String(""));
void send(int code, char* content_type, const String& content);
void send(int code, const String& content_type, const String& content);
void send_P(int code, PGM_P content_type, PGM_P content);
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);

Siendo:

  • Code: Código de respuesta HTTP (200, 301, 303, 404...)
  • Type: Tipo de contenido HTTP (text/plain, text/html, text/json, image/png...)
  • Content: El contenido del cuerpo de la respuesta.

También tenemos otras funciones para enviar datos al cliente que son útiles, por ejemplo, para enviar la información en varias instrucciones del ESP8266.

void setContentLength(const size_t contentLength);
void sendHeader(const String& name, const String& value, bool first = false);
void sendContent(const String& content);
void sendContent_P(PGM_P content);
void sendContent_P(PGM_P content, size_t size);
 

Por otro lado, tenemos funciones para recibir los parámetros de la petición, como veremos en la siguiente entrada.

const String& arg(String name) const;    // get request argument value by name
const String& arg(int i) const;          // get request argument value by number
const String& argName(int i) const;      // get request argument name by number
int args() const;                        // get arguments count
bool hasArg(const String& name) const;   // check if argument exists

Y funciones para obtener y gestionar las cabeceras de la petición HTTP.

void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
const String& header(String name) const; // get request header value by name
const String& header(int i) const;       // get request header value by number
const String& headerName(int i) const;   // get request header name by number
int headers() const;                     // get header count
bool hasHeader(String name) const;       // check if header exists
const String& hostHeader() const;        // get request host header if available or empty String if not
 

Como veis, la clase ESP8266WebServer da mucho más juego de lo que parece y convierte al ESP8266, pese a sus limitaciones de memoria y procesado, es un auténtico servidor con una gran cantidad de opciones.

En la siguiente entrada de la serie del ESP8266 continuaremos profundizando en el ESP8266 como servidor recibiendo e interpretando distintos tipos de peticiones y sus parámetros. ¡Hasta pronto!

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github.

Versión para el ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Versión para el ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples

Si te ha gustado esta entrada y quieres leer más sobre ESP8266 o el ESP32 puedes consultar la sección tutoriales de ESP8266/32
5 8 votes
Article Rating
Previous WinSCP, transfiere archivos entre Windows y Rasperry Pi
Next Librería de Arduino I2C Scanner
8 Comments
oldest
newest
Inline Feedbacks
View all comments
Nicolás
2 years ago

Hola, estoy siguiendo cada uno de tus publicaciones y estoy quedando encantado con todas estas publicaciones, estoy haciendo cada uno de tus ejemplos, y quisiera saber si puedes llegar a hacer una en la que le llegue informacion a estos servidores y la guarden en alguna base de datos, quisiera que nos enseles a como manejar estas bases de datos o como crearlas, tu me entiendes, gracias nuevamnete por tu tiempo y dedicación con estas entregas

Antonio
2 years ago

Hola Luis, primero de todo felicitarte por el trabajo que compartes y darte las gracias, soy profesor y es una fuente de conocimiento que utilizo desde que lo descubrí.

Como te decía trabajo en una escuela y tenemos una red wifi donde la autentificación necesita usario y contraseña. He leído la documentación de la librería WiFiClient.h y no consigo entender cómo incluir el username al iniciar la conexión con WiFi.begin(ssid, password). Sólo colocando en el programa la SSID y el pasword, no me conecta (claro).

Por favor, ¿me podrías orientar?, no consigo conectarme.

Un saldo y muchas gracias por adelantado.

Mariano
1 year ago

Muy Buena explicacion Luis, te hago una consulta en el caso que con el metodo on llame a un handle que tenga un loop (por ejemplo hacer una secuancia de luces), como hago para cortar ese proceso cuando ejecuto otro GET a traves de URL
muchas gracia

Francisco
1 year ago

Hola Luis, tras estar un tiempo siguiendo tus trabajos, sólo tengo palabras buenas para ellos, me parecen fantásticos y gracias a ellos me he internado en el mundo del ESP8266. Estoy detrás de un proyecto Domótico grande, pero me falta la posibilidad de realizar un servidor web que sirva varias páginas con el ESP8266. No se si es posible, si es así me gustaría que lo comentaras un poco. Gracias

Alfredo
4 months ago

Hola Luis, te hago una consulta: luego de crear el nuevo fichero llamado "Server.hpp", en tu programa principal lo incluís (#include) LUEGO de definir el objeto "ESP8266WebServer server(80)". Si no lo hicieras así, dá error en la compilación (me pasó).
Mi consulta es... de esta manera te quedan tus sentencias "include" en medio de la creación de un objeto... es "correcto" esto? Hay alguna manera de agrupar todas las sentencias "include" y recién después de eso definir el objeto? Por una cuestión de "orden" lo digo.
Muchas gracias, un saludo

JOSE MISSEL
3 months ago

Saludos luis, soy fanatico de tus publicaciones excelente pedagogia, quisiera hacerte una consulta tengo una pagina HTML alojada en una NODEMCU V3 (uso el modo WIFISERVER) esta conectada a la red WIFI de mi casa con ella controlo una llave motorizada fabricada por mi persona el tema es que en ocasiones al tratar de acceder a la direccion IP(la cual configure estatica en el router para que siempre sea la misma) se cuelga deja de responder, teniendo que resetear el sistema quitandole alimentacion remotamente lo cual es muy molesto porque no esta disponible el control cuando se requiere a que… Read more »

David
7 days ago

Muy buena serie de post. Los estoy leyendo todos y me estan gustanto mucho. Te planteo una duda por si me puedes orientar o ayudar. Estoy intentando meter el servidor dentro de una clase, pero me da error al usar el metodo On, e intentar enlazarlo con una funcion de dentro de la clase.
Server.on("/wifisave", HTTP_GET, HandleWifiSave);

El error es:
no matching function for call to 'esp8266webserver::ESP8266WebServerTemplate<WiFiServer>::on(const char [7], HTTPMethod, <unresolved overloaded function type>)'

Puedes ayudarme? gracias.

Y sobre todo gracias por tu web