Servidor web asíncrono ESP32: Control de salidas (biblioteca ESPAsyncWebServer)

En este tutorial aprenderás cómo codificar un servidor web asíncrono con la placa ESP32 para controlar sus salidas. La placa se programará usando Arduino IDE y usaremos la biblioteca ESPAsyncWebServer. Pero, primero, ¿qué es un servidor asíncrono y por qué es importante en proyectos como este?.

¿Qué es un servidor asíncrono?

Un servidor asíncrono es aquel que puede manejar múltiples conexiones y solicitudes al mismo tiempo sin bloquear el flujo de ejecución. Esto significa que el servidor puede continuar trabajando en otras tareas mientras espera que una tarea (como una consulta a una base de datos o un cliente lento) se complete.

Para construir el servidor web con la placa ESP32 usaremos la  biblioteca ESPAsyncWebServer  que proporciona una manera fácil de construir un servidor web asíncrono.

Materiales Necesarios

  • Un ESP32.
  • Un LED.
  • Una resistencia de 220 ohmios.
  • Cables de conexión.
  • Protoboard.

Esquemático

Antes de continuar con el código, conecte 1 LED al ESP32. Estamos conectando el LED al GPIO 13, pero puede usar cualquier otro GPIO.

Configuración de Arduino IDE

1. Descarga e Instalación del IDE de Arduino:

  • Ve a arduino.cc y descarga la última versión del IDE de Arduino para tu sistema operativo.
  • Instala el IDE siguiendo las instrucciones de instalación.

2. Añadir Soporte para ESP32:

  • Abre el IDE de Arduino y accede a Archivo > Preferencias (o Arduino > Preferencias en macOS).
  • En «Gestor de URLs Adicionales de Tarjetas», añade la siguiente URL: https://espressif.github.io/arduino-esp32/package_esp32_index.json y haz clic en «OK».
  • Navega a Herramientas > Placa > Gestor de Tarjetas, busca «ESP32» y presiona «Instalar».

3. Instalación de Bibliotecas Necesarias:

  • Visita el repositorio de GitHub para cada biblioteca.
  • En cada página, busca el botón «Code» y luego selecciona «Download ZIP» para descargar las bibliotecas en formato .zip.
  • Ve a Programa > Incluir Biblioteca > Añadir Biblioteca .ZIP…
  • Busca y selecciona el archivo .zip que descargaste para cada biblioteca y confirma la instalación.
  • Repite el proceso para la segunda biblioteca.

Código para el servidor web asíncrono ESP32

Copie el siguiente código a su IDE de Arduino.

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
 
const char* ssid = "Wifi";
const char* password = "12345678";
 
const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";
 
AsyncWebServer server(80);
 
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>Servidor web asincronico</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: #00FF00}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
  </style>
</head>
<body>
  <h2>Servidor web asincronico</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";
 
// BUTTONPLACEHOLDER
String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons = "";
    buttons += "<h4>Salida - GPIO 13</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"13\" " + outputState(13) + "><span class=\"slider\"></span></label>";
 
    return buttons;
  }
  return String();
}
 
String outputState(int output){
  if(digitalRead(output)){
    return "checked";
  }
  else {
    return "";
  }
}
 
void setup(){
 
  Serial.begin(115200);
 
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
 
 
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Conectado a Wifi..");
  }
 
  Serial.println(WiFi.localIP());
 
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });
 
 
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage1;
    String 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(" - Setear en: ");
    Serial.println(inputMessage2);
    request->send(200, "text/plain", "OK");
  });
 
 
  server.begin();
}
 
void loop() {
 
}

Cómo funciona el código

A continuación, se detalla cada línea del código o puede ver el video tutorial:

Inclusión de Bibliotecas

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
 
 
  • WiFi.h: Biblioteca para conectar el ESP32 a una red Wi-Fi.
  • AsyncTCP.h: Biblioteca requerida por ESPAsyncWebServer para manejar conexiones TCP de manera asincrónica.
  • ESPAsyncWebServer.h: Biblioteca para crear un servidor web asincrónico.

Definición de Constantes y Variables Globales

const char* ssid = "Wifi";
const char* password = "12345678";
 
const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";
 
AsyncWebServer server(80);
 
  • ssid y password: Credenciales para la conexión Wi-Fi.
  • PARAM_INPUT_1 y PARAM_INPUT_2: Nombres de los parámetros usados en la URL para controlar el GPIO.
  • server(80): Crea una instancia del servidor en el puerto 80.

HTML de la Página Web

const char index_html[] PROGMEM = R"rawliteral( ... )rawliteral";

Define el HTML de la página web. Este código contiene el diseño y la funcionalidad de la página, incluyendo un botón para controlar el GPIO 13.

Procesador de Plantillas

String processor(const String& var){
  if(var == "BUTTONPLACEHOLDER"){
    String buttons = "";
    buttons += "<h4>Salida - GPIO 13</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"13\" " + outputState(13) + "><span class=\"slider\"></span></label>";
    return buttons;
  }
  return String();
}

Reemplaza el marcador %BUTTONPLACEHOLDER% en el HTML con un botón, cuyo estado refleja el estado actual del GPIO 13.

Estado del GPIO

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

Devuelve el atributo checked si el GPIO especificado está en alto, para que el botón se muestre como activado.

Configuración Inicial

void setup(){
  Serial.begin(115200);
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Conectado a Wifi..");
  }
  Serial.println(WiFi.localIP());
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage1;
    String 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(" - Setear en: ");
    Serial.println(inputMessage2);
    request->send(200, "text/plain", "OK");
  });
  server.begin();
}
 
  • Inicializa la comunicación serial, configura el GPIO 13 como salida y se conecta a la red Wi-Fi.
  • Define dos rutas en el servidor: / para servir la página web y /update para actualizar el estado del GPIO basado en los parámetros recibidos en la URL.
  • En la ruta /update, lee los parámetros output y state de la solicitud, los usa para actualizar el estado del GPIO y envía una respuesta.

Loop Principal

void loop() {
 
}

El loop está vacío porque la lógica se maneja completamente de forma asincrónica por las interrupciones y los manejadores de eventos definidos en setup.