Language: EN

server-side-events-en-esp32

How to Use Server-Side Events (SSE) on an ESP32

Server-Side Events (SSE) is a technology that allows a server to send real-time updates to a client over a persistent HTTP connection.

Unlike WebSockets, SSE is unidirectional. That is, the server can send data to the client, but the client cannot send data to the server over the same connection.

In return, SSE is generally lighter than WebSockets. Therefore, for unidirectional applications, it is a good option.

  1. Persistent connection: The client maintains an open connection with the server.
  2. Real-time updates: The server can send data to the client at any time.
  3. Simple message format: Messages are sent in plain text format.
  4. Automatic reconnection: If the connection is lost, the client attempts to reconnect automatically.

SSE events are based on the HTML5 standard and are compatible with most modern browsers.

SSE is very useful in situations where the server needs to send frequent updates to the client (such as notifications, status updates, or real-time sensor data).

I have placed it in the WebSockets category because they are related. But strictly speaking, they are different technologies.

Implementing SSE on ESP32

Let’s see how to implement SSE on the ESP32. On the ESP32, we can implement an SSE handler using the built-in web framework, such as the one provided by the ESPAsyncWebServer library.

I will use ESPAsyncWebServer because I like this library.

We will create a dedicated endpoint for SSE and define a handler function that establishes the HTTP connection and sends SSE events to the client.

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>

#include "config.h"  // Replace with your network data
#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");
}
  • Creates an instance of the web server on port 80 (the standard port for HTTP).
  • Configures the server to serve static files from the SPIFFS file system.
  • Sets up a handler for requests that do not match any defined route. In this case, a 400 error is returned with the message “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());
    }
  });
}
  • Creates an instance of AsyncEventSource at the route /events. This route will be used to send events from the server to the client.
  • Sets up a handler for when a client connects to the event server. If the client has reconnected, the ID of the last message received is printed.
  • Prints a message to the serial port indicating that a client has reconnected and shows the ID of the last message received.

Receiving Events on the Client

Now let’s look at the client side, creating a web page that receives SSE events. For this, we can use the following code in JavaScript.

const evtSource = new EventSource('/events');

evtSource.onmessage = function(event) {
  const data = event.data;
  // Process the received data
};

So the complete web page would look something like this.

<!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);