En este tutorial vas a aprender cómo construir un servidor web con el ESP32 usando el protocolo de comunicación WebSocket. Como ejemplo, te mostraremos cómo construir una página web para controlar las salidas del ESP32 de forma remota. El estado de la salida se muestra en la página web y se actualiza automáticamente en todos los clientes.
El ESP32 se programará usando Arduino IDE y la librería ESPAsyncWebServer. Si vienes de proyectos anteriores con servidores web tradicionales, probablemente hayas notado un problema muy común: si tienes varias pestañas abiertas (en el mismo o en diferentes dispositivos) al mismo tiempo, el estado no se actualiza en todas las pestañas automáticamente a menos que refresques la página. Para resolver este problema, podemos usar el protocolo WebSocket: todos los clientes pueden ser notificados cuando ocurre un cambio y actualizar la página web en consecuencia.
¿Qué es WebSocket y por qué es tan potente?
Un WebSocket es una conexión persistente entre un cliente y un servidor que permite comunicación bidireccional entre ambas partes usando una conexión TCP. Esto significa que puedes enviar datos del cliente al servidor y del servidor al cliente en cualquier momento.
El cliente establece una conexión WebSocket con el servidor a través de un proceso conocido como handshake de WebSocket. El handshake empieza con una petición/respuesta HTTP, lo que permite a los servidores manejar conexiones HTTP y conexiones WebSocket en el mismo puerto. Una vez establecida la conexión, el cliente y el servidor pueden enviar datos WebSocket en modo full duplex.
Usando el protocolo WebSocket, el servidor (la placa ESP32) puede enviar información al cliente o a todos los clientes sin que se la pidan. Esto también nos permite enviar información al navegador web cuando ocurre un cambio. Ese cambio puede ser algo que ocurrió en la página web (pulsaste un botón) o algo que ocurrió en el lado del ESP32 (como pulsar un botón físico en un circuito).
Esta capacidad de comunicación bidireccional en tiempo real es lo que hace a WebSocket la tecnología ideal para paneles de control, dashboards interactivos y aplicaciones donde la inmediatez es crítica.
Resumen del proyecto
El servidor web del ESP32 muestra una página web con un botón para alternar el estado del GPIO 2. Por simplicidad, controlaremos el GPIO 2, el LED integrado en la placa. Puedes usar este ejemplo para controlar cualquier otro GPIO. La interfaz muestra el estado actual del GPIO. Cada vez que ocurre un cambio en el estado del GPIO, la interfaz se actualiza instantáneamente. El estado del GPIO se actualiza automáticamente en todos los clientes. Esto significa que si tienes varias pestañas de navegador abiertas en el mismo dispositivo o en dispositivos diferentes, todas se actualizan al mismo tiempo.
Cómo funciona
Cuando pulsas el botón “Toggle”, ocurre lo siguiente:
Pulsas el botón “Toggle”. El cliente (tu navegador) envía datos vía protocolo WebSocket con el mensaje “toggle”. El ESP32 (servidor) recibe este mensaje, así que sabe que debe alternar el estado del LED. Si el LED estaba previamente apagado, lo enciende. Después, envía datos con el nuevo estado del LED a todos los clientes también a través del protocolo WebSocket. Los clientes reciben el mensaje y actualizan el estado del LED en la página web en consecuencia. Esto nos permite actualizar todos los clientes casi instantáneamente cuando ocurre un cambio, sin necesidad de recargar la página.
Preparar Arduino IDE y las librerías necesarias
Vamos a programar la placa ESP32 usando Arduino IDE, así que asegúrate de tenerlo instalado con el soporte para ESP32.
Instalar las librerías del servidor web asíncrono
Construiremos el servidor web usando las siguientes librerías: la librería ESPAsyncWebServer de ESP32Async y la librerí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.
Código del servidor WebSocket en el ESP32
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-websocket-server-arduino/
*/
// 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";
bool ledState = 0;
const int ledPin = 2;
// Crea el objeto AsyncWebServer en el puerto 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
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, Helvetica, sans-serif;
text-align: center;
}
h1 {
font-size: 1.8rem;
color: white;
}
h2{
font-size: 1.5rem;
font-weight: bold;
color: #143642;
}
.topnav {
overflow: hidden;
background-color: #143642;
}
body {
margin: 0;
}
.content {
padding: 30px;
max-width: 600px;
margin: 0 auto;
}
.card {
background-color: #F8F7F9;;
box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
padding-top:10px;
padding-bottom:20px;
}
.button {
padding: 15px 50px;
font-size: 24px;
text-align: center;
outline: none;
color: #fff;
background-color: #0f8b8d;
border: none;
border-radius: 5px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.button:active {
background-color: #0f8b8d;
box-shadow: 2 2px #CDCDCD;
transform: translateY(2px);
}
.state {
font-size: 1.5rem;
color:#8c8c8c;
font-weight: bold;
}
</style>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
<div class="topnav">
<h1>ESP WebSocket Server</h1>
</div>
<div class="content">
<div class="card">
<h2>Output - GPIO 2</h2>
<p class="state">state: <span id="state">%STATE%</span></p>
<p><button id="button" class="button">Toggle</button></p>
</div>
</div>
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onLoad);
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- añade esta línea
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
function onMessage(event) {
var state;
if (event.data == "1"){
state = "ON";
}
else{
state = "OFF";
}
document.getElementById('state').innerHTML = state;
}
function onLoad(event) {
initWebSocket();
initButton();
}
function initButton() {
document.getElementById('button').addEventListener('click', toggle);
}
function toggle(){
websocket.send('toggle');
}
</script>
</body>
</html>
)rawliteral";
void notifyClients() {
ws.textAll(String(ledState));
}
void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
if (strcmp((char*)data, "toggle") == 0) {
ledState = !ledState;
notifyClients();
}
}
}
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
void initWebSocket() {
ws.onEvent(onEvent);
server.addHandler(&ws);
}
String processor(const String& var){
Serial.println(var);
if(var == "STATE"){
if (ledState){
return "ON";
}
else{
return "OFF";
}
}
return String();
}
void setup(){
// Puerto serie para propósitos de depuración
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, 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());
initWebSocket();
// Ruta para la raíz / página web
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", index_html, processor);
});
// Inicia el servidor
server.begin();
}
void loop() {
ws.cleanupClients();
digitalWrite(ledPin, ledState);
}
Inserta las credenciales de tu red en las siguientes variables y el código funcionará inmediatamente:
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Cómo funciona el código paso a paso
Importar librerías y definir credenciales
Se importan las librerías necesarias para construir el servidor web: la librería WiFi.h, la librería AsyncTCP.h y la librería ESPAsyncWebServer.h.
Se insertan las credenciales de red en las variables ssid y password.
Definir el GPIO de salida
Se crea una variable ledState para almacenar el estado del GPIO y una variable ledPin que hace referencia al GPIO que quieres controlar. En este caso, controlaremos el LED integrado en la placa (conectado al GPIO 2).
Crear el servidor y el WebSocket
Se crea un objeto AsyncWebServer en el puerto 80. La librería ESPAsyncWebServer incluye un plugin WebSocket que facilita el manejo de conexiones WebSocket. Se crea un objeto AsyncWebSocket llamado ws para manejar las conexiones en la ruta /ws.
Construir la página web
La variable index_html contiene el HTML, CSS y JavaScript necesarios para construir y estilizar la página web y manejar las interacciones cliente-servidor usando el protocolo WebSocket.
Nota: estamos colocando todo lo necesario para construir la página web en la variable
index_htmlque usamos en el sketch de Arduino. Ten en cuenta que puede ser más práctico tener archivos HTML, CSS y JavaScript separados que luego subes al sistema de archivos del ESP32 y referencias en el código.
La sección CSS (entre las etiquetas <style> y </style>) incluye los estilos para estilizar la página web usando CSS. Puedes cambiarlos libremente para hacer que la página web se vea como quieras.
La sección HTML (entre las etiquetas <body> y </body>) contiene el contenido visible de la página web. Hay un encabezado principal con el texto “ESP WebSocket Server”. Después, hay un encabezado secundario con el texto “Output – GPIO 2”. Después, un párrafo que muestra el estado actual del GPIO.
El %STATE% es un placeholder para el estado del GPIO. Será reemplazado con el valor actual por el ESP32 en el momento de enviar la página web. Los placeholders en el texto HTML deben ir entre signos %. Esto significa que este texto %STATE% es como una variable que luego se reemplazará con el valor real.
Después de enviar la página web al cliente, el estado debe cambiar dinámicamente cada vez que haya un cambio en el estado del GPIO. Recibiremos esa información vía protocolo WebSocket. Después, JavaScript maneja qué hacer con la información recibida para actualizar el estado en consecuencia. Para poder manejar ese texto usando JavaScript, el texto debe tener un id que podamos referenciar. En este caso, el id es state (<span id="state">).
Finalmente, hay un párrafo con el botón para alternar el estado del GPIO. Ten en cuenta que hemos dado un id al botón (id="button").
JavaScript: manejo de WebSockets en el cliente
El JavaScript va entre las etiquetas <script> y </script>. Es responsable de inicializar una conexión WebSocket con el servidor tan pronto como la interfaz web esté completamente cargada en el navegador y de manejar el intercambio de datos a través de WebSockets.
El gateway es el punto de entrada a la interfaz WebSocket. window.location.hostname obtiene la dirección de la página actual (la dirección IP del servidor web). Se crea una nueva variable global llamada websocket. Se añade un event listener que llamará a la función onload cuando la página web se cargue.
La función onload() llama a la función initWebSocket() para inicializar una conexión WebSocket con el servidor y a la función initButton() para añadir event listeners a los botones.
La función initWebSocket() inicializa una conexión WebSocket en el gateway definido antes. También asigna varias funciones de callback para cuando la conexión WebSocket se abra, se cierre o cuando se reciba un mensaje.
Cuando la conexión se abre, simplemente imprimimos un mensaje en la consola. Si por alguna razón la conexión WebSocket se cierra, llamamos a la función initWebSocket() de nuevo después de 2000 milisegundos (2 segundos). El método setTimeout() llama a una función o evalúa una expresión después de un número especificado de milisegundos.
Finalmente, necesitamos manejar qué ocurre cuando recibimos un nuevo mensaje. El servidor (tu placa ESP) enviará un mensaje “1” o un mensaje “0”. Según el mensaje recibido, queremos mostrar un mensaje “ON” o “OFF” en el párrafo que muestra el estado. Obtenemos ese elemento <span> con id="state" y establecemos su valor a ON u OFF.
La función initButton() obtiene el botón por su id (button) y añade un event listener de tipo click. Esto significa que cuando pulsas el botón, se llama a la función toggle. La función toggle envía un mensaje usando la conexión WebSocket con el texto toggle.
Después, el ESP32 debe manejar qué ocurre cuando recibe este mensaje: alternar el estado actual del GPIO.
Manejo del lado del servidor
Notificar a todos los clientes
La función notifyClients() notifica a todos los clientes con un mensaje que contiene lo que le pases como argumento. En este caso, queremos notificar a todos los clientes del estado actual del LED cada vez que haya un cambio. La clase AsyncWebSocket proporciona un método textAll() para enviar el mismo mensaje a todos los clientes conectados al servidor al mismo tiempo.
Manejar los mensajes WebSocket
La función handleWebSocketMessage() es una función de callback que se ejecutará cada vez que recibamos nuevos datos de los clientes vía protocolo WebSocket. Si recibimos el mensaje toggle, alternamos el valor de la variable ledState. Además, notificamos a todos los clientes llamando a la función notifyClients(). De esta forma, todos los clientes son notificados del cambio y actualizan la interfaz en consecuencia.
Configurar el servidor WebSocket
Ahora necesitamos configurar un event listener para manejar los diferentes pasos asíncronos del protocolo WebSocket. Este manejador de eventos puede implementarse definiendo la función onEvent(). El argumento type representa el evento que ocurre. Puede tomar los siguientes valores:
WS_EVT_CONNECT cuando un cliente se ha conectado. WS_EVT_DISCONNECT cuando un cliente se ha desconectado. WS_EVT_DATA cuando se recibe un paquete de datos del cliente. WS_EVT_PONG en respuesta a una petición ping. WS_EVT_ERROR cuando se recibe un error del cliente.
Inicializar WebSocket
Finalmente, la función initWebSocket() inicializa el protocolo WebSocket.
La función processor()
La función processor() es responsable de buscar placeholders en el texto HTML y reemplazarlos con lo que queramos antes de enviar la página web al navegador. En nuestro caso, reemplazaremos el placeholder %STATE% con ON si ledState es 1. De lo contrario, lo reemplazamos con OFF.
La función setup()
En el setup(), se inicializa el Monitor Serie para propósitos de depuración. Se configura ledPin como OUTPUT y se pone a LOW cuando el programa arranca por primera vez. Se inicializa Wi-Fi e imprime la dirección IP del ESP32 en el Monitor Serie. Se inicializa el protocolo WebSocket llamando a la función initWebSocket() creada antes.
Se sirve el texto guardado en la variable index_html cuando recibes una petición en la URL raíz /. Necesitas pasar la función processor como argumento para reemplazar los placeholders con el estado actual del GPIO. Finalmente, se inicia el servidor.
La función loop()
El LED se controla físicamente en el loop(). Nota que también llamamos al método cleanupClients(). Esta es la razón, según la explicación de la página de GitHub de la librería ESPAsyncWebServer:
Los navegadores a veces no cierran correctamente la conexión WebSocket, incluso cuando se llama a la función
close()en JavaScript. Esto eventualmente agotará los recursos del servidor web y hará que el servidor se cuelgue. Llamar periódicamente a la funcióncleanupClients()desde elloop()principal limita el número de clientes cerrando el cliente más antiguo cuando se excede el número máximo de clientes. Se puede llamar cada ciclo, sin embargo, si quieres usar menos energía, llamar tan poco como una vez por segundo es suficiente.
Demostración
Después de insertar tus credenciales de red en las variables ssid y password, puedes subir el código a tu placa. No olvides comprobar que tienes la placa y el puerto COM correctos seleccionados.
Después de subir el código, abre el Monitor Serie a 115200 baudios y pulsa el botón EN/RST integrado en la placa. La dirección IP del ESP32 debería imprimirse.
Abre un navegador en tu red local e inserta la dirección IP del ESP32. Deberías obtener acceso a la página web para controlar la salida. Pulsa el botón para alternar el LED. Puedes abrir varias pestañas de navegador al mismo tiempo o acceder al servidor web desde dispositivos diferentes y el estado del LED se actualizará automáticamente en todos los clientes cada vez que haya un cambio.
Conclusión: lo que has aprendido
En este tutorial has aprendido cómo configurar un servidor WebSocket con el ESP32. El protocolo WebSocket permite una comunicación full duplex entre el cliente y el servidor. Después de inicializar, el servidor y el cliente pueden intercambiar datos en cualquier momento.
Esto es muy útil porque el servidor puede enviar datos al cliente cada vez que ocurre algo. Por ejemplo, puedes añadir un botón físico a esta configuración que, al pulsarlo, notifique a todos los clientes para que actualicen la interfaz web. En este ejemplo, te hemos mostrado cómo controlar un GPIO del ESP32. Puedes usar este método para controlar más GPIOs. También puedes usar el protocolo WebSocket para enviar lecturas de sensores o notificaciones en cualquier momento.
WebSocket es la evolución natural del servidor web tradicional del ESP32. Mientras que el servidor HTTP clásico obliga al cliente a refrescar o hacer polling constante, WebSocket invierte el flujo: el servidor empuja los datos en el momento en que ocurren. Esto lo convierte en la tecnología ideal para dashboards de sensores en tiempo real, paneles de control domótico y cualquier aplicación donde la actualización instantánea sea importante.
Combinado con ESP-NOW, MQTT o BLE, WebSocket completa un arsenal de protocolos que convierten al ESP32 en una plataforma IoT de nivel profesional, capaz de comunicarse con cualquier tipo de dispositivo en cualquier tipo de escenario.
Descubre más desde CIBERED
Suscríbete y recibe las últimas entradas en tu correo electrónico.
