¿Quieres aprender a crear un servidor web con WebSockets en tu placa ESP32 utilizando Arduino IDE? ¡Estás en el lugar correcto! En este artículo, te mostraremos paso a paso cómo configurar tu servidor web y establecer una conexión con WebSockets para una comunicación bidireccional efectiva. ¡Vamos a empezar!
Digamos que necesitamos crear un proyecto que utilice el ESP32 para controlar una bombilla a través de WiFi. La implementación es bastante simple: configuramos el ESP32 en modo Soft AP o STA para que pueda proporcionar una página web que muestre el estado del interruptor de luz como «encendido» o «apagado». Cuando un usuario activa el interruptor de la luz en el navegador, el ESP32 recibe una solicitud HTTP. En respuesta, ajusta el estado de la bombilla en consecuencia y envía una respuesta al navegador. Esto es exactamente lo que hicimos al crear un servidor web ESP32 simple y funciona perfectamente.
Sin embargo, esta solución tiene un pequeño problema. La web es un sistema multiusuario, lo que significa que muchas personas pueden conectarse al mismo servidor web. En otras palabras, el servidor es un recurso compartido. ¿Qué pasa si dos usuarios intentan encender o apagar la bombilla al mismo tiempo? Las dos interfaces del navegador ya no están sincronizadas y ya no muestran con precisión el estado real de la bombilla. Por este motivo, ésta no es una solución adecuada para un sistema de este tipo.
En cambio, podemos aprovechar las capacidades bidireccionales del protocolo de comunicación WebSocket para construir un sistema responsivo donde un clic en el interruptor de la luz se reenvía inmediatamente al servidor y se envía a todos los navegadores conectados, de modo que el estado del interruptor de la luz esté siempre sincronizado para todos los usuarios.
Eso es exactamente lo que aprenderá en este tutorial: Cómo crear un servidor web con ESP32 usando el protocolo de comunicación WebSocket. Para una demostración práctica, crearemos un sitio web para controlar el LED integrado del ESP32. El estado del LED se muestra en el sitio web. Cuando alguien activa el interruptor del LED en la página, el estado del LED se actualiza inmediatamente en todos los navegadores conectados al servidor.
¿Listo? ¡Entonces comencemos! Lo importante primero:
¿Qué es WebSocket?
Aunque el nombre «WebSocket» no parece tener sentido a primera vista, el concepto detrás de él es bastante simple y puedes obtener una comprensión básica del mismo en muy poco tiempo.
WebSocket es el nombre de un protocolo de comunicación que permite la comunicación bidireccional (más precisamente, dúplex completo) entre un cliente y un servidor web. En pocas palabras, WebSocket es una tecnología que permite que tanto el cliente como el servidor establezcan una conexión a través de la cual cada lado puede enviar mensajes al otro en cualquier momento.
Esto es diferente de una conexión HTTP normal, donde el cliente inicia una solicitud, el servidor envía una respuesta y luego la conexión finaliza. De hecho, WebSocket es un protocolo de comunicación completamente diferente: cuando un cliente se conecta a un servidor, ambos extremos de la conexión pueden enviar y recibir datos. Así como el servidor espera nuevos mensajes, todos los clientes conectados también escuchan activamente. Esto permite que el servidor envíe datos a un cliente específico o a todos los clientes sin necesidad de una solicitud. Además, la conexión permanece abierta hasta que la cierra el cliente o el servidor, lo que permite una comunicación continua entre ellos.
WebSocket es fantástico, pero eso no significa que deba usarse en todas partes. La implementación de WebSocket puede aumentar la complejidad de su proyecto, especialmente en el lado del servidor. Por lo tanto, sólo debes hacerlo si tu proyecto involucra transferencias de datos (donde deseas transferir los mismos datos a múltiples clientes al mismo tiempo); de lo contrario, las encuestas podrían ser suficientes.
Descripción del proyecto
Ahora creemos un proyecto simple en el que construimos un servidor WebSocket usando el ESP32 para controlar de forma remota el LED incorporado del ESP32 a través de una conexión WebSocket.
Diseñaremos una página web con un interruptor de palanca que permita a los usuarios cambiar el estado de GPIO 2 correspondiente al LED integrado. La interfaz web también muestra el estado actual del LED.
El ESP32 escucha activamente en el puerto 80 las conexiones y mensajes entrantes de WebSocket. Cuando un usuario activa el interruptor LED en la página web, se envía un mensaje de «alternancia» al ESP32. Cuando el ESP32 recibe este mensaje, alterna el LED y notifica inmediatamente a todos los clientes conectados (navegadores) enviando un «1» (LED encendido) o un «0» (LED apagado). Como resultado, todos los clientes activos actualizan inmediatamente el estado del LED en sus sitios web.
Para ilustrar mejor cómo funciona este proyecto, hemos incluido una animación a continuación que muestra varios clientes conectándose al ESP32 a través de una conexión WebSocket e intercambiando mensajes.
Aunque este proyecto es bastante sencillo, puede servir como base para experimentos y proyectos más prácticos. Por ejemplo, puede utilizarse como punto de partida para crear un sistema de iluminación inteligente para el hogar.
Configurando el IDE de Arduino
Usaremos el IDE de Arduino para programar el ESP32. Por lo tanto, asegúrese de tener instalado el complemento ESP32 antes de continuar:
Para crear un servidor WebSocket utilizamos ESPAsyncWebServer Biblioteca. Esta biblioteca requiere la AsíncronoTCP Biblioteca para que funcione. Desafortunadamente, ninguna de estas bibliotecas está disponible para su instalación en el administrador de bibliotecas Arduino IDE. Por lo tanto, debes instalarlos manualmente.
Haga clic en los enlaces a continuación para descargar las bibliotecas.
Una vez descargado, navegue hasta su IDE de Arduino Bosquejo > incluir biblioteca > Agregar biblioteca .ZIP y seleccione las bibliotecas que acaba de descargar.
Sube el código
Copie el siguiente código en su IDE de Arduino. Pero antes de cargar el código, es necesario realizar un cambio fundamental. Actualice las siguientes dos variables con sus credenciales de red para permitir que el ESP32 se conecte a su red.
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Después de realizar estos cambios, puede cargar el código.
// Import required libraries
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
bool ledState = 0;
const int ledPin = 2;
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>ESP32 WebSocket Server</title>
<style>
html{font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
body{margin-top: 50px;}
h1{color: #444444;margin: 50px auto;}
p{font-size: 19px;color: #888;}
#state{font-weight: bold;color: #444;}
.switch{margin:25px auto;width:80px}
.toggle{display:none}
.toggle+label{display:block;position:relative;cursor:pointer;outline:0;user-select:none;padding:2px;width:80px;height:40px;background-color:#ddd;border-radius:40px}
.toggle+label:before,.toggle+label:after{display:block;position:absolute;top:1px;left:1px;bottom:1px;content:""}
.toggle+label:before{right:1px;background-color:#f1f1f1;border-radius:40px;transition:background .4s}
.toggle+label:after{width:40px;background-color:#fff;border-radius:20px;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:margin .4s}
.toggle:checked+label:before{background-color:#4285f4}
.toggle:checked+label:after{margin-left:42px}
</style>
</head>
<body>
<h1>ESP32 WebSocket Server</h1>
<div class="switch">
<input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
<label for="toggle-btn"></label>
</div>
<p>On-board LED: <span id="state">%STATE%</span></p>
<script>
window.addEventListener('load', function() {
var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
websocket.onopen = function(event) {
console.log('Connection established');
}
websocket.onclose = function(event) {
console.log('Connection died');
}
websocket.onerror = function(error) {
console.log('error');
};
websocket.onmessage = function(event) {
if (event.data == "1") {
document.getElementById('state').innerHTML = "ON";
document.getElementById('toggle-btn').checked = true;
}
else {
document.getElementById('state').innerHTML = "OFF";
document.getElementById('toggle-btn').checked = false;
}
};
document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
});
</script>
</body>
</html>
)rawliteral";
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;
ws.textAll(String(ledState));
}
}
}
void eventHandler(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 %sn", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnectedn", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
digitalWrite(ledPin, ledState);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
String processor(const String& var){
if(var == "STATE"){
return ledState ? "ON" : "OFF";
}
if(var == "CHECK"){
return ledState ? "checked" : "";
}
return String();
}
void setup(){
// Serial port for debugging purposes
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
Serial.print("Connecting to ");
Serial.println(ssid);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("Connected..!");
Serial.print("Got IP: "); Serial.println(WiFi.localIP());
ws.onEvent(eventHandler);
server.addHandler(&ws);
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
// Start server
server.begin();
}
void loop() {
ws.cleanupClients();
}
demostración
Después de cargar el código, abra el monitor serie y establezca la velocidad en baudios en 115200. Luego presione el botón EN en el ESP32. Es posible que tarde unos minutos en conectarse a su red. Después de eso, se mostrará la dirección IP dinámica que recibió de su enrutador.
A continuación, inicie un navegador web y navegue hasta la dirección IP que se muestra en el monitor serie. El ESP32 debe mostrar una página web con el estado actual del LED integrado y un interruptor de palanca para control.
Ahora levante su teléfono o tableta y navegue hasta la misma dirección IP. Al hacer esto, asegúrese de que esté conectado a la misma red que su computadora. Haga clic en el interruptor de palanca para alternar el LED. Notará que no solo el LED incorporado cambia, sino que el estado del LED se actualiza automáticamente en todos los navegadores web.
Al mismo tiempo, puede utilizar el monitor serie para rastrear qué clientes se conectan y desconectan del ESP32.
Explicación detallada del código
Analicemos este código.
Importar bibliotecas requeridas
El boceto comienza incluyendo las siguientes bibliotecas:
WiFi.h
: proporciona métodos WiFi específicos de ESP32 que utilizamos para conectarnos a la red.ESPAsyncWebServer.h
: se utiliza para crear un servidor HTTP que admita puntos finales WebSocket.AsyncTCP.h
: La biblioteca ESPAsyncWebServer se basa en esta biblioteca.
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
Definir constantes y variables
A continuación, se proporcionan las credenciales de red (SSID y contraseña), que deberás reemplazar con tus propias credenciales de red WiFi.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Hay dos variables definidas en el mismo ámbito global: una para rastrear el estado actual de GPIO (ledState
) y otro para configurar el pin GPIO conectado al LED (ledPin
). En este caso controlamos el LED integrado (que está conectado a GPIO 2).
bool ledState = 0;
const int ledPin = 2;
Inicializar el servidor web y WebSocket
A continuación, un objeto de clase llamado AsyncWebServer server
es creado. El constructor de esta clase acepta como argumento el número de puerto en el que el servidor HTTP escucha las solicitudes entrantes. En este caso utilizamos el puerto HTTP estándar, es decir, el puerto 80.
AsyncWebServer server(80);
Luego, para configurar el punto final WebSocket, cree un objeto de clase AsyncWebSocket llamado ws
es creado. La ruta del punto final de WebSocket se debe pasar como argumento al constructor de esta clase. Esta ruta se definirá como una cadena y se hará referencia a ella en nuestro código. /ws
.
Para que WebSocket escuche las conexiones en la ruta: ws://[esp ip]/ws
AsyncWebSocket ws("/ws");
Creando el sitio web
El index_html
Luego se define una constante. Contiene un literal de cadena sin formato que contiene el código HTML para mostrar un interruptor de palanca en una página web para controlar el LED incorporado del ESP32, así como CSS para diseñar la página web y JavaScript para establecer una conexión WebSocket y monitorear los cambios en Estado del LED. Todo este código se ejecuta en el lado del cliente.
Tenga en cuenta que se utiliza la macro PROGMEM, que almacena el contenido en la memoria de programa del ESP32 (memoria flash) en lugar de SRAM para optimizar la memoria.
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>ESP32 WebSocket Server</title>
<style>
html{font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
body{margin-top: 50px;}
h1{color: #444444;margin: 50px auto;}
p{font-size: 19px;color: #888;}
#state{font-weight: bold;color: #444;}
.switch{margin:25px auto;width:80px}
.toggle{display:none}
.toggle+label{display:block;position:relative;cursor:pointer;outline:0;user-select:none;padding:2px;width:80px;height:40px;background-color:#ddd;border-radius:40px}
.toggle+label:before,.toggle+label:after{display:block;position:absolute;top:1px;left:1px;bottom:1px;content:""}
.toggle+label:before{right:1px;background-color:#f1f1f1;border-radius:40px;transition:background .4s}
.toggle+label:after{width:40px;background-color:#fff;border-radius:20px;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:margin .4s}
.toggle:checked+label:before{background-color:#4285f4}
.toggle:checked+label:after{margin-left:42px}
</style>
</head>
<body>
<h1>ESP32 WebSocket Server</h1>
<div class="switch">
<input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
<label for="toggle-btn"></label>
</div>
<p>On-board LED: <span id="state">%STATE%</span></p>
<script>
window.addEventListener('load', function() {
var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
websocket.onmessage = function () {
if (event.data == "1") {
document.getElementById('state').innerHTML = "ON";
document.getElementById('toggle-btn').checked = true;
}
else {
document.getElementById('state').innerHTML = "OFF";
document.getElementById('toggle-btn').checked = false;
}
};
document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
});
</script>
</body>
</html>
)rawliteral";
El CSS
Entre <style></style>
Las etiquetas son el código CSS que se utiliza para diseñar la página web. No dudes en cambiarlo para que el sitio web quede como lo deseas.
<style>
html{font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
body{margin-top: 50px;}
h1{color: #444444;margin: 50px auto;}
p{font-size: 19px;color: #888;}
#state{font-weight: bold;color: #444;}
.switch{margin:25px auto;width:80px}
.toggle{display:none}
.toggle+label{display:block;position:relative;cursor:pointer;outline:0;user-select:none;padding:2px;width:80px;height:40px;background-color:#ddd;border-radius:40px}
.toggle+label:before,.toggle+label:after{display:block;position:absolute;top:1px;left:1px;bottom:1px;content:""}
.toggle+label:before{right:1px;background-color:#f1f1f1;border-radius:40px;transition:background .4s}
.toggle+label:after{width:40px;background-color:#fff;border-radius:20px;box-shadow:0 2px 5px rgba(0,0,0,.3);transition:margin .4s}
.toggle:checked+label:before{background-color:#4285f4}
.toggle:checked+label:after{margin-left:42px}
</style>
El HTML
Entre <body></body>
Las etiquetas son el contenido real del sitio web.
<h1>ESP32 WebSocket Server</h1>
<div class="switch">
<input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
<label for="toggle-btn"></label>
</div>
<p>On-board LED: <span id="state">%STATE%</span></p>
La primera línea de código establece el encabezado de la página con el <h1>
Día; Puede cambiar este texto por el que mejor se adapte a su aplicación.
<h1>ESP32 WebSocket Server</h1>
El siguiente bloque de código crea un interruptor de palanca.
En realidad, se trata de una casilla de verificación con cierto estilo para que parezca un interruptor de palanca.
<div class="switch">
<input id="toggle-btn" class="toggle" type="checkbox" %CHECK%>
<label for="toggle-btn"></label>
</div>
Finalmente, hay un párrafo que muestra el estado del LED integrado. El estado real del LED (“ON” o “OFF”) se muestra en una <span>
Elemento.
<p>On-board LED: <span id="state">%STATE%</span></p>
Los marcadores de posición %CHECK%
Y %STATE%
Son esencialmente variables que el ESP32 reemplaza con los valores apropiados al servir la página web según el estado actual del LED. Los marcadores de posición garantizan que el estado del LED permanezca sincronizado entre todos los usuarios. Si un usuario cambia el estado del LED, el próximo usuario que se conecte al ESP32 debería ver el estado actualizado del LED, ¿verdad?
El %STATE%
El marcador de posición se reemplaza con el texto apropiado (ya sea «ON» o «OFF») según el estado actual del LED. Y el %CHECK%
El marcador de posición determina si la casilla de verificación debe marcarse o desmarcarse cuando se carga la página (el interruptor de palanca está activado o desactivado).
Tenga en cuenta que los comodines entran en juego cuando un cliente se conecta al ESP32 por primera vez. Una vez establecida la conexión WebSocket, JavaScript se encarga de todas las actualizaciones. Por lo tanto, cada elemento con un marcador de posición tiene uno id
Atributo. El id
El atributo le da a cada elemento un identificador único. Esto permite que JavaScript acceda y manipule estos elementos cuando WebSocket recibe un mensaje del servidor.
El JavaScript
El código JavaScript está escrito dentro del <script></script>
etiquetas. Es responsable de inicializar una conexión WebSocket con el servidor y gestionar el intercambio de datos a través de WebSockets y actualizar todos los elementos de la página web en consecuencia.
<script>
window.addEventListener('load', function() {
var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
websocket.onopen = function(event) {
console.log('Connection established');
}
websocket.onclose = function(event) {
console.log('Connection died');
}
websocket.onerror = function(error) {
console.log('error');
};
websocket.onmessage = function(event) {
if (event.data == "1") {
document.getElementById('state').innerHTML = "ON";
document.getElementById('toggle-btn').checked = true;
}
else {
document.getElementById('state').innerHTML = "OFF";
document.getElementById('toggle-btn').checked = false;
}
};
document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
});
</script>
Primero, se adjunta un detector de eventos al objeto de la ventana para garantizar que no se ejecute todo el código JavaScript hasta que la página esté completamente cargada.
window.addEventListener('load', function() {
Cuando se carga, se establece una nueva conexión WebSocket al servidor utilizando el nombre de host actual (dirección IP de ESP32) y el protocolo especial. ws://
en la URL. FYI: También los hay cifrados wss://
Protocolo; es como HTTPS para websockets.
var websocket = new WebSocket(`ws://${window.location.hostname}/ws`);
Una vez que se establece la conexión, se activan varios eventos en la instancia de WebSocket en el lado del cliente. Y deberíamos escuchar estos acontecimientos. Hay cuatro eventos en total:
onopen
– Se activa al conectarse a un WebSocket.onclose
– Se activa cuando se cierra una conexión con un WebSocket.onerror
– Se activa cuando se cierra una conexión a un WebSocket debido a un error, p. B. si algunos datos no se pudieron enviar.onmessage
– Se activa cuando el WebSocket recibe datos del servidor.
Si la conexión se abre o se cierra o se produce un error, simplemente imprimimos un mensaje en la consola web. Sin embargo, puede utilizar estos eventos para agregar más funciones si es necesario.
websocket.onopen = function(event) {
console.log('Connection established');
}
websocket.onclose = function(event) {
console.log('Connection died');
}
websocket.onerror = function(error) {
console.log('error');
};
Ahora necesitamos cubrir lo que sucede cuando el WebSocket recibe datos del servidor, es decir, cuando el onmessage
Se desencadena el evento. El servidor (su ESP32) envía un mensaje «1» o «0» dependiendo del estado actual del LED. Según el informe recibido, debemos tomar las medidas adecuadas.
- Cuando se recibe “1”: El texto dentro del elemento con ID «estado» debe estar configurado en «ON» y la casilla de verificación con ID «toggle-btn» debe estar marcada como marcada.
- Cuando se recibe “0”: El texto dentro del elemento con ID «estado» debe estar configurado en «DESACTIVADO» y la casilla de verificación con ID «toggle-btn» debe permanecer sin marcar.
websocket.onmessage = function(event) {
if (event.data == "1") {
document.getElementById('state').innerHTML = "ON";
document.getElementById('toggle-btn').checked = true;
}
else {
document.getElementById('state').innerHTML = "OFF";
document.getElementById('toggle-btn').checked = false;
}
};
Finalmente, se adjunta un detector de eventos a nuestro interruptor de palanca. Cuando el interruptor se enciende o apaga (es decir, cuando cambia el estado de la casilla de verificación toggle-btn), el mensaje de alternancia se envía a través de la conexión WebSocket, indicando al ESP32 que alterne el LED.
document.getElementById('toggle-btn').addEventListener('change', function() { websocket.send('toggle'); });
La función configuración()
Hasta ahora has visto cómo se maneja la conexión WebSocket en el lado del cliente (navegador). Ahora veamos cómo se maneja en el lado del servidor.
En setup() el monitor serie se inicializa con fines de depuración.
Serial.begin(115200);
Luego, el pin LED se configura para comportarse como una SALIDA y se establece en BAJO.
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
Luego, el ESP32 intenta conectarse a la red WiFi. El progreso de la conexión y la dirección IP obtenida se muestran en el monitor serie.
Serial.print("Connecting to ");
Serial.println(ssid);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("Connected..!");
Serial.print("Got IP: "); Serial.println(WiFi.localIP());
La siguiente línea configura un detector de eventos para WebSocket. Esto significa que cada vez que se produce un evento asociado con WebSocket (ws
objeto) ocurre, eventHandler()
La función se activa para manejar este evento. El eventHandler()
La función contiene lógica para determinar qué tipo de evento WebSocket ocurrió (por ejemplo, nueva conexión de cliente, datos recibidos, cliente desconectado, etc.) y cómo responder a ese evento. Veremos eso más tarde.
ws.onEvent(eventHandler);
La siguiente línea agrega o registra el WebSocket (ws
objeto) al servidor web (server
Objeto). Esto dice que server
que tiene un controlador WebSocket y que todas las conexiones o solicitudes entrantes de WebSocket deben enrutarse a este controlador. El &ws
es un puntero al objeto WebSocket, dándole la server
para saber desde dónde se deben enviar y recibir los datos relacionados con WebSocket.
server.addHandler(&ws);
La siguiente línea configura un punto final o ruta en el servidor web. En este caso la ruta raíz (“/
«) Están definidos. Si un cliente tiene un HTTP_GET
La solicitud a esta ruta raíz ejecutará la función Lambda (dentro de las llaves {…}).
Dentro de esta función hay una llamada a request->send_P(...)
. Esta función envía una respuesta al cliente.
200
es el código de estado HTTP que indica una solicitud exitosa."text/html"
le dice al cliente que el servidor está devolviendo contenido HTML.index_html
es el contenido que se va a enviar, es decir, su sitio web HTML almacenado en la memoria del programa. Luego más.processor
es una función que prepara el contenido HTML antes de enviarlo (por ejemplo, para reemplazar marcadores de posición). Más sobre eso más adelante.
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
Finalmente se inicia el servidor web. Una vez en ejecución, el servidor comienza a escuchar las solicitudes entrantes de los clientes en las rutas previamente definidas (como la ruta raíz que configuramos).
server.begin();
La función bucle()
Con un servidor web síncrono normalmente tienes una llamada server.handleClient();
dentro del bucle que comprueba si hay solicitudes de clientes que deben procesarse. Si este es el caso, se espera que el código procese estas solicitudes y envíe respuestas antes de devolver el control al resto del bucle.
Con un servidor web asíncrono, no se requiere nada en el bucle. AsyncWebServer está configurado con controladores de eventos y también utiliza la biblioteca AsyncTCP. Esto le permite esperar activamente conexiones en segundo plano, analizar las solicitudes entrantes y activar el controlador de eventos correspondiente. Por esta razón, al crear un servidor web asíncrono, el loop()
La función generalmente se deja en blanco.
Sin embargo, en nuestro caso cleanupClients()
Se llama al método. Este método es un método integrado de la clase AsyncWebSocket. Cuando se trabaja con WebSockets, los clientes pueden conectarse y desconectarse con frecuencia, o sus conexiones pueden caer inesperadamente. Con el tiempo, es posible que tenga clientes WebSocket que ya no estén activos pero que sigan consumiendo recursos. Esto eventualmente agotará los recursos del servidor web, provocando que el servidor falle.
El cleanupClients()
El método recorre todos los clientes WebSocket conectados y elimina aquellos que ya no están activos o conectados. La llamada ordinaria del cleanClients()
función desde el principal loop()
garantiza que su ESP32 no esté sobrecargado por conexiones de clientes antiguas y no utilizadas y siga siendo eficiente al manejar clientes nuevos o activos.
void loop() {
ws.cleanupClients();
}
Manejar eventos WebSocket del lado del servidor
Si recuerdas en el setup()
Función: configuramos un detector de eventos para WebSocket usando el ws.onEvent(eventHandler)
método para que cada vez que se produzca un evento asociado con el WebSocket (ws
objeto) ocurre, eventHandler()
La función se activa para manejar este evento.
El eventHandler()
La función sólo comprueba el type
Argumento para determinar el tipo de evento ocurrido y responder a ese evento. El type
El argumento representa el evento que ocurre. Puede tomar los siguientes valores:
WS_EVT_CONNECT
– cuando un nuevo cliente se conecta al servidor WebSocketWS_EVT_DISCONNECT
– cuando un cliente se desconecta del servidor WebSocketWS_EVT_DATA
– cuando el servidor WebSocket recibe datos de un clienteWS_EVT_PONG
– cuando el WebSocket recibe una solicitud de pingWS_EVT_ERROR
– si el cliente informa un error
void eventHandler(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 %sn", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnectedn", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
digitalWrite(ledPin, ledState);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
Como puedes ver, si el tipo de evento WS_EVT_CONNECT
o WS_EVT_DISCONNECT
El monitor serie muestra la dirección IP y la identificación única de los clientes cuando se conectan o desconectan. Si bien no hay acciones para el WS_EVT_PONG
Y WS_EVT_ERROR
Tipos de eventos, aunque podrían desarrollarse más si fuera necesario.
El tipo de evento más importante es WS_EVT_DATA
(cuando el servidor WebSocket recibe datos de un cliente): Los datos recibidos se envían al handleWebSocketMessage()
La función para su posterior procesamiento y el estado del LED se actualizarán con la última versión. ledState
Valor.
Leer y enviar mensajes WebSocket
La función handleWebSocketMessage()
está definido para manejar mensajes WebSocket entrantes. Cuando se recibe un mensaje de «alternar» válido, esta función alterna el LED y notifica a todos los clientes conectados de este cambio mediante el textAll()
Método.
El textAll()
El método de la clase AsyncWebSocket permite que ESP32 envíe el mismo mensaje a todos los clientes conectados simultáneamente, asegurando que el estado del LED permanezca sincronizado en todos los clientes.
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;
ws.textAll(String(ledState));
}
}
}
Procesamiento de marcadores de posición para páginas web
Como recordarás, nuestro sitio web tiene dos marcadores de posición: %CHECK%
Y %STATE%
. El processor()
La función se encarga de buscar dichos marcadores de posición en el texto HTML y reemplazarlos con los valores apropiados antes de enviar la página web al navegador.
El %STATE%
Dependiendo del estado actual del LED, el marcador de posición se reemplaza por «ON» o «OFF». Y el %CHECK%
El marcador de posición determina si la casilla de verificación debe marcarse o desmarcarse cuando se carga la página (el interruptor de palanca está activado o desactivado).
String processor(const String& var){
if(var == "STATE"){
return ledState ? "ON" : "OFF";
}
if(var == "CHECK"){
return ledState ? "checked" : "";
}
return String();
}
Introducción
Imagina que necesitas crear un proyecto que utilice ESP32 para controlar una bombilla a través de WiFi. La implementación es bastante simple: configuraremos el ESP32 en modo soft-AP o STA, lo que le permitirá servir una página web que muestra el estado del interruptor de luz como «encendido» o «apagado». Cuando un usuario cambie el interruptor de luz en el navegador, el ESP32 recibirá una solicitud HTTP. En respuesta, ajustará el estado de la bombilla de luz correspondientemente y enviará una respuesta de vuelta al navegador. Esto es exactamente lo que hicimos al construir un servidor web ESP32 simple, ¡y funciona perfectamente!
Preguntas Frecuentes
-
¿Qué es WebSocket?
WebSocket es un protocolo de comunicación que permite la comunicación bidireccional entre un cliente y un servidor web. Permite que tanto el cliente como el servidor establezcan una conexión a través de la cual cada lado puede enviar mensajes al otro en cualquier momento.
-
¿Por qué usar WebSocket en un servidor web ESP32?
WebSocket es ideal para proyectos donde se requiere una transmisión de datos simultánea a múltiples clientes, como en un sistema de control de iluminación para el hogar inteligente.
-
¿Cómo se configura un servidor web ESP32 con WebSockets?
Para crear un servidor web con ESP32 usando el protocolo de comunicación WebSocket, se necesita replicar el código proporcionado en el tutorial. Esto incluye configurar la red Wi-Fi y cargar el código en el ESP32.
-
¿Cuál es el propósito de la biblioteca ESPAsyncWebServer?
La biblioteca ESPAsyncWebServer se utiliza para crear un servidor HTTP que admita puntos finales de websocket en un ESP32. Es fundamental para implementar la funcionalidad de WebSocket en el ESP32.
Para obtener más detalles y realizar el proyecto paso a paso, asegúrese de seguir las instrucciones en el tutorial completo aquí.
Qué interesante, definitivamente voy a probarlo en mi proyecto con el ESP32. ¡Gracias por compartir esta guía!
¡Genial! Me parece una idea súper útil y emocionante para experimentar con mi ESP32. ¡Gracias por las instrucciones!