server-side-events-en-esp32

Cómo usar Server-Side Events (SSE) en un ESP32

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.

  1. Conexión persistente: El cliente mantiene una conexión abierta con el servidor.
  2. Actualizaciones en tiempo real: El servidor puede enviar datos al cliente en cualquier momento.
  3. Formato de mensaje simple: Los mensajes se envían en formato de texto plano.
  4. 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);