¿Cómo crear un servidor web asíncrono con la placa ESP32 para controlar sus salidas?

Libro Guía del Microcontrolador ESP32

En este tutorial vas a aprender cómo construir un servidor web asíncrono con la placa ESP32 para controlar sus salidas. La placa se programará usando Arduino IDE, y usaremos la librería ESPAsyncWebServer.

¿Qué es un servidor web asíncrono y por qué deberías usarlo?

Para construir el servidor web usaremos la librería ESPAsyncWebServer, que proporciona una forma sencilla de construir un servidor web asíncrono. Construir un servidor web asíncrono tiene varias ventajas importantes frente al servidor web síncrono tradicional. Tal y como se menciona en la página de GitHub de la librería:

“Maneja más de una conexión al mismo tiempo”. “Cuando envías la respuesta, estás inmediatamente listo para manejar otras conexiones mientras el servidor se encarga de enviar la respuesta en segundo plano”. “Motor de procesamiento de plantillas simple para manejar plantillas”. Y mucho más.

La diferencia fundamental entre un servidor síncrono y uno asíncrono es esta: con un servidor síncrono, el ESP32 se queda bloqueado atendiendo una petición hasta terminarla, lo que significa que no puede atender a otros clientes mientras tanto. Con un servidor asíncrono, el ESP32 puede atender múltiples peticiones simultáneamente, lo que mejora enormemente la capacidad de respuesta de tu proyecto. Si tienes varios dispositivos conectados a la vez o necesitas que la interfaz sea fluida, el servidor asíncrono es la opción correcta.

Componentes necesarios

En este tutorial, controlaremos tres salidas. Como ejemplo, controlaremos LEDs. Así que necesitas: una placa ESP32, 3 LEDs, 3 resistencias de 220 Ω, una protoboard y cables jumper.

Esquema de conexión

Antes de proceder al código, conecta 3 LEDs al ESP32. Estamos conectando los LEDs a los GPIOs 2, 4 y 33, pero puedes usar cualquier otro GPIO. Cada LED debe ir con su resistencia de 220 Ω en serie hacia GND, con el ánodo conectado al GPIO correspondiente.

Instalar las librerías del servidor web asíncrono

Construiremos el servidor web usando las siguientes librerías: la libería ESPAsyncWebServer de ESP32Async y la libería AsyncTCP de ESP32Async.

Puedes instalar estas librerías en el Gestor de Librerías de Arduino. Abre el Gestor de Librerías pulsando el icono de la biblioteca en la barra lateral izquierda. Busca ESPAsyncWebServer e instala la librería ESPAsyncWebServer de ESP32Async. Después, instala la librería AsyncTCP. Busca AsyncTCP e instala la librería AsyncTCP de ESP32Async.

Resumen del proyecto

Para entender mejor el código, veamos cómo funciona el servidor web. El servidor web contiene un encabezado “ESP Web Server” y tres botones (interruptores de tipo toggle) para controlar tres salidas. Cada botón tiene una etiqueta que indica el pin GPIO de salida. Puedes eliminar o añadir fácilmente más salidas.

Cuando el interruptor está en rojo, significa que la salida está encendida (su estado es HIGH). Si cambias el interruptor, apagas la salida (cambias el estado a LOW). Cuando el interruptor está en gris, significa que la salida está apagada (su estado es LOW). Si cambias el interruptor, enciendes la salida (cambias el estado a HIGH).

Cómo funciona internamente

Veamos qué ocurre cuando cambias los botones. Veremos el ejemplo para el GPIO 2. Funciona de forma similar para los otros botones.

Primer escenario: cambias el botón para encender el GPIO 2. Cuando eso ocurre, el navegador hace una petición HTTP GET en la URL /update?output=2&state=1. Basándose en esa URL, el ESP32 cambia el estado del GPIO 2 a 1 (HIGH) y enciende el LED.

Segundo escenario: cambias el botón para apagar el GPIO 2. Cuando eso ocurre, el navegador hace una petición HTTP GET en la URL /update?output=2&state=0. Basándose en esa URL, cambiamos el estado del GPIO 2 a 0 (LOW) y apagamos el LED.

Código completo del servidor web asíncrono

Copia el siguiente código en tu Arduino IDE:

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Detalles completos del proyecto en https://RandomNerdTutorials.com/esp32-async-web-server-espasyncwebserver-library/
*/

// Importa las librerías requeridas
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Reemplaza con las credenciales de tu red
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";

// Crea el objeto AsyncWebServer en el puerto 80
AsyncWebServer server(80);

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
    input:checked+.slider {background-color: #b30000}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
  </style>
</head>
<body>
  <h2>ESP Web Server</h2>
  %BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?output="+element.id+"&state=0", true); }
  xhr.send();
}
</script>
</body>
</html>
)rawliteral";

// Reemplaza el placeholder con la sección de botones en tu página web
String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons = "";
    buttons += "<h4>Output - GPIO 2</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"2\" " + outputState(2) + "><span class=\"slider\"></span></label>";
    buttons += "<h4>Output - GPIO 4</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"4\" " + outputState(4) + "><span class=\"slider\"></span></label>";
    buttons += "<h4>Output - GPIO 33</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"33\" " + outputState(33) + "><span class=\"slider\"></span></label>";
    return buttons;
  }
  return String();
}

String outputState(int output){
  if(digitalRead(output)){
    return "checked";
  }
  else {
    return "";
  }
}

void setup(){
  // Puerto serie para propósitos de depuración
  Serial.begin(115200);

  pinMode(2, OUTPUT);
  digitalWrite(2, LOW);
  pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
  pinMode(33, OUTPUT);
  digitalWrite(33, LOW);
  
  // Conecta a Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Imprime la dirección IP local del ESP32
  Serial.println(WiFi.localIP());

  // Ruta para la raíz / página web
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/html", index_html, processor);
  });

  // Envía una petición GET a <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage1;
    String inputMessage2;
    // GET input1 value on <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>
    if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
      inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
      inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
      digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
    }
    else {
      inputMessage1 = "No message sent";
      inputMessage2 = "No message sent";
    }
    Serial.print("GPIO: ");
    Serial.print(inputMessage1);
    Serial.print(" - Set to: ");
    Serial.println(inputMessage2);
    request->send(200, "text/plain", "OK");
  });

  // Inicia el servidor
  server.begin();
}

void loop() {

}

Cómo funciona el código paso a paso

En esta sección explicaremos cómo funciona el código. Sigue leyendo si quieres aprender más o salta a la sección de Demostración para ver el resultado final.

Importar las librerías

Primero, se importan las librerías requeridas. Necesitas incluir las librerías WiFi, ESPAsyncWebServer y AsyncTCP.

Configurar las credenciales de red

Se insertan las credenciales de red en las variables correspondientes, para que el ESP32 pueda conectarse a tu red local.

Parámetros de entrada

Para comprobar los parámetros pasados en la URL (número de GPIO y su estado), creamos dos variables, una para la salida y otra para el estado. Recuerda que el ESP32 recibe peticiones como esta: /update?output=2&state=0.

Objeto AsyncWebServer

Se crea un objeto AsyncWebServer en el puerto 80.

Construir la página web

Todo el texto HTML con estilos y JavaScript se almacena en la variable index_html. Ahora veremos el texto HTML y qué hace cada parte.

El título va dentro de las etiquetas <title> y </title>. El título es exactamente lo que parece: el título de tu documento, que aparece en la barra de título de tu navegador web. En este caso, es “ESP Web Server”.

La siguiente etiqueta <meta> hace que tu página web sea responsive en cualquier navegador (portátil, tablet o smartphone). La siguiente línea evita peticiones sobre el favicon. En este caso, no tenemos favicon. El favicon es el icono del sitio web que aparece junto al título en la pestaña del navegador. Si no añadimos esta línea, el ESP32 recibirá una petición del favicon cada vez que accedamos al servidor web, lo que desperdicia recursos de manera innecesaria.

Entre las etiquetas <style> y </style>, añadimos algo de CSS para estilizar la página web. No entraremos en detalle sobre cómo funciona este estilo CSS; simplemente diremos que define la apariencia de los interruptores tipo toggle con colores gris y rojo según el estado.

Cuerpo HTML

Dentro de las etiquetas <body> y </body> es donde añadimos el contenido de la página web. Las etiquetas <h2> y </h2> añaden un encabezado a la página web. En este caso, el texto “ESP Web Server”, pero puedes añadir cualquier otro texto.

Después del encabezado, tenemos los botones. La forma en que los botones se muestran en la página web (rojo si el GPIO está encendido, o gris si el GPIO está apagado) varía según el estado actual del GPIO.

Cuando accedes a la página del servidor web, quieres que muestre los estados actuales correctos de los GPIO. Así que, en lugar de añadir el texto HTML para construir los botones directamente, añadimos un placeholder %BUTTONPLACEHOLDER%. Este placeholder será reemplazado por el texto HTML real para construir los botones con los estados correctos cuando se cargue la página web.

JavaScript

Después, hay algo de JavaScript que es responsable de hacer una petición HTTP GET cuando cambias los botones como hemos explicado antes:

function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?output="+element.id+"&state=0", true); }
  xhr.send();
}

Aquí está la línea que hace la petición:

if(element.checked){ xhr.open("GET", "/update?output="+element.id+"&state=1", true); }

element.id devuelve el id de un elemento HTML. El id de cada botón será el GPIO controlado, como veremos en la siguiente sección:

GPIO 2 → element.id = 2. GPIO 4 → element.id = 4. GPIO 33 → element.id = 33.

La función processor()

Ahora necesitamos crear la función processor(), que reemplaza los placeholders en el texto HTML con lo que definamos. Cuando se solicita la página web, comprueba si el HTML tiene algún placeholder. Si encuentra el placeholder %BUTTONPLACEHOLDER%, devuelve el texto HTML para crear los botones.

Puedes eliminar o añadir más líneas fácilmente para crear más botones.

Veamos cómo se crean los botones. Creamos una variable String llamada buttons que contiene el texto HTML para construir los botones. Concatenamos el texto HTML con el estado actual de la salida para que el botón toggle esté en gris o en rojo. El estado actual de la salida es devuelto por la función outputState(<GPIO>) (acepta como argumento el número de GPIO).

La barra invertida \ se usa para poder pasar comillas dobles dentro del String. La función outputState() devuelve "checked" si el GPIO está encendido o un campo vacío "" si el GPIO está apagado.

Así que el texto HTML para el GPIO 2 cuando está encendido sería: incluiría el atributo checked dentro del input de tipo checkbox. Ese atributo checked es lo que hace que el interruptor aparezca visualmente en rojo (encendido) al cargar la página web.

Entendiendo los interruptores toggle

En HTML, un interruptor toggle es un tipo de input. La etiqueta <input> especifica un campo de entrada donde el usuario puede introducir datos. El interruptor toggle es un campo de entrada de tipo checkbox. Hay muchos otros tipos de campos de entrada.

El checkbox puede estar marcado o no. Cuando está marcado, tienes algo como <input type="checkbox" checked>.

El onchange es un atributo de evento que ocurre cuando cambiamos el valor del elemento (el checkbox). Cada vez que marcas o desmarcas el interruptor, llama a la función JavaScript toggleCheckbox() para ese id de elemento específico (this).

El id especifica un id único para ese elemento HTML. El id nos permite manipular el elemento usando JavaScript o CSS.

La función setup()

En el setup(), se inicializa el Monitor Serie para propósitos de depuración. Se configuran los GPIOs que quieres controlar como salidas usando la función pinMode() y se establecen a LOW cuando el ESP32 arranca por primera vez. Si has añadido más GPIOs, haz el mismo procedimiento.

Se conecta a tu red local y se imprime la dirección IP del ESP32.

En el setup(), necesitas manejar qué ocurre cuando el ESP32 recibe peticiones. Como hemos visto antes, recibes una petición de este tipo: <ESP_IP>/update?output=<inputMessage1>&state=<inputMessage2>. Así que comprobamos si la petición contiene el valor de la variable PARAM_INPUT1 (output) y PARAM_INPUT2 (state) y guardamos los valores correspondientes en las variables input1Message e input2Message.

Después, controlamos el GPIO correspondiente con el estado correspondiente (la variable inputMessage1 guarda el número de GPIO y la variable inputMessage2 guarda el estado, 0 o 1):

digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());

Finalmente, se inicia el servidor.

Demostración

Después de subir el código a tu ESP32, abre el Monitor Serie a 115200 baudios. Pulsa el botón RST/EN integrado en la placa. Deberías obtener su dirección IP.

Abre un navegador y escribe la dirección IP del ESP32. Obtendrás acceso a una página web similar a la descrita.

Pulsa los botones toggle para controlar los GPIOs del ESP32. Al mismo tiempo, deberías obtener los siguientes mensajes en el Monitor Serie para ayudarte a depurar tu código.

También puedes acceder al servidor web desde un navegador en tu smartphone. Cada vez que abres el servidor web, muestra los estados actuales de los GPIO. Rojo indica que el GPIO está encendido, y gris que el GPIO está apagado.

Conclusión: lo que has aprendido

En este tutorial has aprendido cómo crear un servidor web asíncrono con el ESP32 para controlar sus salidas usando interruptores toggle. Cada vez que abres la página web, muestra los estados actualizados de los GPIO.

El servidor web asíncrono es un salto cualitativo enorme respecto al servidor síncrono tradicional. Con esta técnica, tu ESP32 puede atender a múltiples clientes simultáneamente, responder a peticiones mientras hace otras tareas y ofrecer una experiencia de usuario mucho más fluida. La librería ESPAsyncWebServer se ha convertido en el estándar de facto para construir servidores web profesionales con el ESP32, y dominar su uso te abrirá las puertas a proyectos mucho más ambiciosos: dashboards de sensores en tiempo real, paneles de control multi-dispositivo, sistemas de automatización del hogar y cualquier aplicación donde la capacidad de respuesta sea crítica.

Este patrón de servidor asíncrono + botones toggle + placeholders dinámicos es directamente reutilizable en prácticamente cualquier proyecto de control remoto. Solo tienes que cambiar los GPIOs, añadir más botones en el processor() y adaptar el diseño CSS. En pocos minutos tendrás un panel de control web completo y funcional alojado íntegramente en tu ESP32.

Vistas: 0

Descubre más desde CIBERED

Suscríbete y recibe las últimas entradas en tu correo electrónico.

Scroll al inicio