Los Server-Side Events (SSE) son una tecnología que permite a un servidor enviar actualizaciones en tiempo real a un cliente a través de una conexión HTTP persistente.
A diferencia de WebSockets, SSE es unidireccional. Es decir, el servidor puede enviar datos al cliente, pero el cliente no puede enviar datos al servidor a través de la misma conexión.
A cambio, generalmente SSE es más ligero que WebSockets. Por lo que, para aplicaciones unidireccionales, es una buena opción.
- Conexión persistente: El cliente mantiene una conexión abierta con el servidor.
- Actualizaciones en tiempo real: El servidor puede enviar datos al cliente en cualquier momento.
- Formato de mensaje simple: Los mensajes se envían en formato de texto plano.
- Reconexión automática: Si la conexión se pierde, el cliente intenta reconectarse automáticamente.
Los eventos SSE se basan en el estándar HTML5 y son compatibles con la mayoría de los navegadores modernos.
SSE es muy útil en situaciones donde el servidor necesita enviar actualizaciones frecuentes al cliente (como notificaciones, actualizaciones de estado, o datos de sensores en tiempo real).
Lo he metido en la categoría de WebSockets porque están relacionados. Pero extrictamente son tecnologías diferentes.
Implementando SSE en ESP32
Vamos a ver cómo implementar la SSE en el ESP32. En el ESP32, podemos implementar un manejador SSE utilizando el framework web integrado, como el proporcionado por la biblioteca ESPAsyncWebServer
.
Yo voy a usar ESPAsyncWebServer
, porque me gusta esta librería.
Vamos a crear un endpoint dedicado para SSE y define una función de manejo que establezca la conexión HTTP y envíe eventos SSE al cliente.
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
#include "config.h" // Sustituir con datos de vuestra red
#include "Server.hpp"
#include "ServerSideEvents.hpp"
#include "ESP32_Utils.hpp"
void setup(void)
{
Serial.begin(115200);
SPIFFS.begin();
ConnectWiFi_STA();
InitSSE();
InitServer();
}
String payload;
void loop(void)
{
payload = String(millis(), DEC);
events.send(payload.c_str(), "millis", millis());
delay(1000);
}
AsyncWebServer server(80);
void InitServer()
{
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
server.onNotFound([](AsyncWebServerRequest *request) {
request->send(400, "text/plain", "Not found");
});
server.begin();
Serial.println("HTTP server started");
}
- Crea una instancia del servidor web en el puerto 80 (el puerto estándar para HTTP).
- Configura el servidor para servir archivos estáticos desde el sistema de archivos SPIFFS.
- Configura un manejador para las solicitudes que no coinciden con ninguna ruta definida. En este caso, se devuelve un error 400 con el mensaje “Not found”.
AsyncEventSource events("/events");
void InitSSE()
{
// Handle Web Server Events
events.onConnect([](AsyncEventSourceClient *client)
{
if(client->lastId())
{
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
});
}
- Crea una instancia de
AsyncEventSource
en la ruta/events
. Esta ruta se utilizará para enviar eventos desde el servidor al cliente. - Configura un manejador para cuando un cliente se conecta al servidor de eventos. Si el cliente se ha reconectado, se imprime el ID del último mensaje que recibió.
- Imprime un mensaje en el puerto serial indicando que un cliente se ha reconectado y muestra el ID del último mensaje que recibió.
Recibir Eventos en el Cliente
Ahora vamos a ver el lado del cliente, creando una página web que reciba los eventos SSE. Para ello podemos usar el siguiente código de JavaScript.
const evtSource = new EventSource('/events');
evtSource.onmessage = function(event) {
const data = event.data;
// Procesa los datos recibidos
};
Por lo que la web completa quedaría algo así.
<!DOCTYPE html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>ESP32 Websocket Async</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Millis</h1>
<div id="counterDiv">---</div>
</body>
<script type="text/javascript" src="./js/main.js"></script>
</html>
const source = new EventSource('/events');
const myDiv = document.getElementById('counterDiv');
function updateCounterUI(counter)
{
myDiv.innerHTML = counter;
}
source.addEventListener('open', function (e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function (e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('millis', function (e) {
updateCounterUI(e.data);
console.log("message", e.data);
}, false);