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’ añadimos lo siguiente,
El programa principal queda
#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();
}
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");
}
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