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 porESPAsyncWebServer
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
ypassword
: Credenciales para la conexión Wi-Fi.PARAM_INPUT_1
yPARAM_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ámetrosoutput
ystate
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
.