como-emplear-el-esp8266-como-servidor

Cómo emplear el ESP32 como servidor HTTP

  • 7 min

Un ESP32 o ESP8266 actuando como servidor HTTP acepta peticiones web y devuelve respuestas a otros clientes de la red.

El ejemplo está orientado a ESP32. En muchos casos también puede adaptarse a ESP8266 cambiando librerías y algunos detalles de pines.

Este es uno de los roles más habituales en proyectos conectados: podemos servir una página web, exponer una pequeña API, recibir órdenes o mostrar el estado de sensores y salidas.

Recordad que en una comunicación 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 ESP32 como servidor. Por tanto, el dispositivo va a estar permanentemente encendido y a recibir peticiones de los clientes (por ejemplo, un ordenador, un móvil u otra placa ESP).

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

esp8266-servidor-esquema

El ESP32 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. Empezamos por lo más sencillo: montar un servidor simple con el ESP32.

ESP32 como servidor sencillo

El uso del ESP32 como servidor es muy sencillo gracias al trabajo de la comunidad en el desarrollo de la librería WebServer.

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

WebServer(IPAddress addr, int port = 80);
WebServer(int port = 80);
Copied!

E iniciarlo a través de una de sus funciones ‘begin(…)’

void begin();
void begin(uint16_t port);
Copied!

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()’.

Ejemplo ESP32 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 <WiFi.h>
#include <WebServer.h>
 
#include "config.h"  // Sustituir con datos de vuestra red
#include "ESP32_Utils.hpp"

WebServer 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();
}
Copied!

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

esp8266-servidor-prueba-serial

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

esp8266-servidor-prueba-navegador

¡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 ‘ESP32_Utils.hpp’ añadimos lo siguiente,

El programa principal queda

#include <WiFi.h>
#include <WebServer.h>
 
WebServer server(80);
 
#include "config.h"  // Sustituir con datos de vuestra red
#include "ESP32_Utils.hpp"
#include "Server.hpp"

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

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");
}
Copied!

WebServer 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
Copied!

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);
Copied!

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);
Copied!

Por otro lado, tenemos funciones para recibir los parámetros de la petición.

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
Copied!

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
Copied!

Como veis, la clase WebServer 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.

Con esto ya tenemos un servidor HTTP básico funcionando en nuestra placa ESP. A partir de aquí podemos ampliar el ejemplo para recibir parámetros, servir páginas completas o construir una API propia.

Descarga el código

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

github-full

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

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