**Server-Sent Events (SSE)** permite que **el cliente reciba actualizaciones automáticas desde un servidor a través de una conexión HTTP**. **El cliente inicia la conexión SSE y el servidor utiliza el protocolo `EventSource` para enviar actualizaciones al cliente**. El cliente **recibirá actualizaciones del servidor, pero no podrá enviar ningún dato al servidor después del handshake inicial**.
Esto es **especialmente útil para enviar lecturas actualizadas de sensores al navegador**. **Cada vez que hay una nueva lectura disponible, el ESP32 la envía al cliente y la página web puede actualizarse automáticamente sin necesidad de peticiones adicionales**. En lugar de lecturas de sensores, puedes **enviar cualquier dato que pueda ser útil para tu proyecto**, como **estados de GPIO, notificaciones cuando se detecta movimiento**, etc.
> **Importante**: **Server-Sent Events (SSE) no son compatibles con Internet Explorer**. Si necesitas compatibilidad con navegadores antiguos, tendrás que usar WebSocket o polling tradicional.
## Resumen del proyecto
El **servidor web del ESP32 muestra tres tarjetas con las lecturas de temperatura, humedad y presión del sensor BME280**. **El ESP32 obtiene nuevas lecturas del sensor cada 30 segundos**. **Cada vez que hay una nueva lectura disponible, la placa (servidor) la envía al cliente usando Server-Sent Events**. **El cliente recibe los datos y actualiza la página web en consecuencia**. Esto permite que **la página web se actualice automáticamente cada vez que hay nuevas lecturas disponibles**.
### ¿Cómo funciona esto?
El funcionamiento de SSE para actualizar la página web se puede resumir así:
**El cliente inicia la conexión SSE y el servidor utiliza el protocolo `EventSource` en la URL `/events` para enviar actualizaciones al cliente**. **El ESP32 obtiene nuevas lecturas del sensor**. **Envía las lecturas como eventos con los siguientes nombres al cliente: `temperature`, `humidity` y `pressure`**. **El cliente tiene event listeners para los eventos enviados por el servidor y recibe las lecturas actualizadas en esos eventos**. **Actualiza la página web con las lecturas más recientes**.
## Preparar Arduino IDE
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
Para construir el servidor web usaremos la **librería ESPAsyncWebServer (de ESP32Async)**. Esta librería necesita 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**.
### Instalar las librerías del sensor BME280
Para obtener lecturas del módulo sensor BME280, usaremos la **librería Adafruit_BME280**. También necesitas instalar la **librería Adafruit_Sensor**. Ambas pueden instalarse a través del **Gestor de Librerías de Arduino IDE**.
## Construir el circuito
Para ejemplificar cómo usar server-sent events con el ESP32, **enviaremos lecturas del sensor BME280 al navegador**. Así que necesitas **conectar un sensor BME280 a tu ESP32**.
### Componentes necesarios
Para completar este tutorial necesitas: un **módulo sensor BME280**, una **placa ESP32**, una **protoboard** y **cables jumper**.
### Esquema de conexión
Vamos a usar **comunicación I2C con el módulo sensor BME280**. Para ello, **conecta el sensor a los pines SCL (GPIO 22) y SDA (GPIO 21) por defecto del ESP32**. La alimentación del sensor va a **3,3 V y GND**.
## Código del servidor web del ESP32 con SSE
Copia el siguiente código en tu Arduino IDE:
“`cpp
/*
Rui Santos & Sara Santos – Random Nerd Tutorials
Detalles completos del proyecto en https://RandomNerdTutorials.com/esp32-web-server-sent-events-sse/
*/
#include
#include
#include
#include
#include
// Reemplaza con las credenciales de tu red
const char* ssid = “REPLACE_WITH_YOUR_SSID”;
const char* password = “REPLACE_WITH_YOUR_PASSWORD”;
// Crea el objeto AsyncWebServer en el puerto 80
AsyncWebServer server(80);
// Crea un Event Source en /events
AsyncEventSource events(“/events”);
// Variables del temporizador
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;
// Crea un objeto sensor
Adafruit_BME280 bme; // BME280 conectado al I2C del ESP32 (GPIO 21 = SDA, GPIO 22 = SCL)
float temperature;
float humidity;
float pressure;
// Inicia el BME280
void initBME(){
if (!bme.begin(0x76)) {
Serial.println(“Could not find a valid BME280 sensor, check wiring!”);
while (1);
}
}
void getSensorReadings(){
temperature = bme.readTemperature();
// Convierte la temperatura a Fahrenheit
//temperature = 1.8 * bme.readTemperature() + 32;
humidity = bme.readHumidity();
pressure = bme.readPressure()/ 100.0F;
}
// Inicia WiFi
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print(“Connecting to WiFi ..”);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(‘.’);
delay(1000);
}
Serial.println(WiFi.localIP());
}
String processor(const String& var){
getSensorReadings();
//Serial.println(var);
if(var == “TEMPERATURE”){
return String(temperature);
}
else if(var == “HUMIDITY”){
return String(humidity);
}
else if(var == “PRESSURE”){
return String(pressure);
}
return String();
}
const char index_html[] PROGMEM = R”rawliteral(
BME280 WEB SERVER (SSE)
TEMPERATURE
%TEMPERATURE% °C
HUMIDITY
%HUMIDITY% %
PRESSURE
%PRESSURE% hPa
)rawliteral”;
void setup() {
Serial.begin(115200);
initWiFi();
initBME();
// Maneja el servidor web
server.on(“/”, HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, “text/html”, index_html, processor);
});
// Maneja los eventos del servidor web
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf(“Client reconnected! Last message ID that it got is: %u\n”, client->lastId());
}
// envía un evento con el mensaje “hello!”, id actual en millis
// y establece el delay de reconexión a 10 segundos
client->send(“hello!”, NULL, millis(), 10000);
});
server.addHandler(&events);
server.begin();
}
void loop() {
if ((millis() – lastTime) > timerDelay) {
getSensorReadings();
Serial.printf(“Temperature = %.2f ºC \n”, temperature);
Serial.printf(“Humidity = %.2f \n”, humidity);
Serial.printf(“Pressure = %.2f hPa \n”, pressure);
Serial.println();
// Envía eventos al cliente web con las lecturas del sensor
events.send(“ping”,NULL,millis());
events.send(String(temperature).c_str(),”temperature”,millis());
events.send(String(humidity).c_str(),”humidity”,millis());
events.send(String(pressure).c_str(),”pressure”,millis());
lastTime = millis();
}
}
“`
**Inserta las credenciales de tu red en las siguientes variables y el código funcionará inmediatamente**:
“`cpp
const char* ssid = “REPLACE_WITH_YOUR_SSID”;
const char* password = “REPLACE_WITH_YOUR_PASSWORD”;
“`
## Cómo funciona el código paso a paso
### Incluir las librerías
Las librerías **`Adafruit_Sensor` y `Adafruit_BME280`** son necesarias para **interactuar con el sensor BME280**. Las librerías **`WiFi`, `ESPAsyncWebServer` y `AsyncTCP`** se usan para **crear el servidor web**.
### Credenciales de red
Se **insertan las credenciales de red en las variables correspondientes** para que el ESP32 pueda **conectarse a tu red local mediante Wi-Fi**.
### AsyncWebServer y AsyncEventSource
Se **crea un objeto `AsyncWebServer` en el puerto 80**. La siguiente línea **crea un nuevo event source en `/events`**:
“`cpp
AsyncEventSource events(“/events”);
“`
Esta es la **pieza central de SSE**: el objeto `events` será el encargado de **enviar los eventos a todos los clientes conectados** cada vez que tengamos nuevos datos.
### Declarar variables
Las variables **`lastTime` y `timerDelay` se usarán para actualizar las lecturas del sensor cada X número de segundos**. Como ejemplo, **obtendremos nuevas lecturas cada 30 segundos (30000 milisegundos)**. Puedes **cambiar ese tiempo de delay en la variable `timerDelay`**.
Se **crea un objeto `Adafruit_BME280` llamado `bme` en los pines I2C por defecto del ESP32**. Las variables **`temperature`, `humidity` y `pressure` de tipo `float`** se usarán para **almacenar las lecturas del sensor BME280**.
### Inicializar el BME280
La siguiente función **se puede llamar para inicializar el sensor BME280**. Si el sensor no responde en la dirección I2C `0x76`, el programa se **queda en un bucle infinito** e imprime un mensaje de error por el Monitor Serie.
### Obtener lecturas del BME280
La **función `getSensorReadings()` obtiene las lecturas de temperatura, humedad y presión del sensor BME280** y las **guarda en las variables `temperature`, `humidity` y `pressure`**. También incluye una línea comentada para **convertir la temperatura a Fahrenheit si lo prefieres**.
### Inicializar Wi-Fi
La siguiente función **configura el ESP32 como estación Wi-Fi y se conecta a tu router** usando el **SSID y la contraseña definidos antes**. La función **imprime puntos suspensivos mientras espera la conexión** y, una vez conectada, **muestra la dirección IP local** por el Monitor Serie.
### La función `processor()`
La **función `processor()` reemplaza cualquier placeholder del texto HTML** que se usa para **construir la página web con las lecturas actuales del sensor antes de enviarla al navegador**. Esto nos permite **mostrar las lecturas actuales del sensor en la página web cuando la visitas por primera vez**. De lo contrario, **verías un espacio en blanco hasta que hubiera nuevas lecturas disponibles** (lo que puede tardar un tiempo según el delay definido en el código).
### Construir la página web
La **variable `index_html` contiene todo el HTML, CSS y JavaScript necesarios para construir la página web**.
> **Nota**: para simplificar este tutorial, **colocamos todo lo necesario para construir la página web en la variable `index_html`** que 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 subas al sistema de archivos del ESP32 y referencies en el código.
**Sección CSS**
Entre las etiquetas `
` se incluyen **los estilos para estilizar la página web usando CSS**. Puedes **cambiarlos libremente para hacer que la página web se vea como quieras**.
**Sección HTML**
Entre las etiquetas `
` y `` se añade **el contenido de la página web visible para el usuario**. Hay un **encabezado principal con el contenido “BME280 WEB SERVER (SSE)”**. Ese es el texto que **aparece en la barra superior**. Puedes **modificar ese texto libremente**.Después, **mostramos las lecturas del sensor en etiquetas `div` separadas**. Veamos los párrafos que **muestran la temperatura**:
“`html
TEMPERATURE
%TEMPERATURE% °C
“`
El **primer párrafo simplemente muestra el texto “TEMPERATURE”**. Definimos **el color y también un icono**. En el **segundo párrafo**, puedes ver que el **placeholder `%TEMPERATURE%` está rodeado por las etiquetas ` `**. El **atributo `id` de HTML se usa para especificar un identificador único para un elemento HTML**.
Se usa para **apuntar a un estilo específico o puede ser usado por JavaScript para acceder y manipular el elemento con ese id específico**. Eso es lo que vamos a hacer. Por ejemplo, **cuando el cliente recibe un nuevo evento con la última lectura de temperatura, actualiza el elemento HTML con el id `temp` con la nueva lectura**.
Un **proceso similar se hace para actualizar las otras lecturas**.
**JavaScript: EventSource**
El JavaScript va entre las etiquetas ``. Es **responsable de inicializar una conexión EventSource con el servidor y manejar los eventos recibidos del servidor**.
**Manejar eventos**
Se **crea un nuevo objeto `EventSource` y se especifica la URL de la página que envía las actualizaciones**. En nuestro caso, es **`/events`**:
“`javascript
if (!!window.EventSource) {
var source = new EventSource(‘/events’);
“`
Una vez que has **instanciado un event source, puedes empezar a escuchar mensajes del servidor con `addEventListener()`**. Estos son los **event listeners por defecto**:
“`javascript
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(‘message’, function(e) {
console.log(“message”, e.data);
}, false);
“`
Después, se **añade el event listener para `temperature`**:
“`javascript
source.addEventListener(‘temperature’, function(e) {
“`
**Cuando hay una nueva lectura de temperatura disponible, el ESP32 envía un evento (`temperature`) al cliente**. Las siguientes líneas **manejan qué ocurre cuando el navegador recibe ese evento**:
“`javascript
console.log(“temperature”, e.data);
document.getElementById(“temp”).innerHTML = e.data;
“`
Básicamente, **imprime las nuevas lecturas en la consola del navegador y coloca los datos recibidos en el elemento con el id correspondiente (`temp`) en la página web**.
Un **procesador similar se usa para humedad y presión**:
“`javascript
source.addEventListener(‘humidity’, function(e) {
console.log(“humidity”, e.data);
document.getElementById(“hum”).innerHTML = e.data;
}, false);
source.addEventListener(‘pressure’, function(e) {
console.log(“pressure”, e.data);
document.getElementById(“pres”).innerHTML = e.data;
}, false);
“`
### La función `setup()`
En el `setup()`, se **inicializa el Monitor Serie, se inicializa Wi-Fi y el sensor BME280**.
**Manejar peticiones**
Cuando **accedes a la dirección IP del ESP32 en la URL raíz `/`, se envía el texto que está almacenado en la variable `index_html`** para **construir la página web** y se **pasa el `processor` como argumento**, para que **todos los placeholders sean reemplazados con las últimas lecturas del sensor**.
**Event source del servidor**
Se **configura el event source en el servidor**:
“`cpp
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf(“Client reconnected! Last message ID that it got is: %u\n”, client->lastId());
}
client->send(“hello!”, NULL, millis(), 10000);
});
server.addHandler(&events);
“`
La función `onConnect` se **ejecuta cada vez que un nuevo cliente se conecta al stream de eventos**. Enviamos un **mensaje de bienvenida “hello!” con un identificador de mensaje** y un **delay de reconexión de 10 segundos**. Esto es **útil para que los navegadores sepan cuánto tiempo esperar antes de intentar reconectar si la conexión se pierde**.
Finalmente, **se inicia el servidor**.
### La función `loop()`
En el `loop()`, se **obtienen nuevas lecturas del sensor**. Se **imprimen las nuevas lecturas en el Monitor Serie**. Finalmente, se **envían eventos al navegador con las lecturas más recientes del sensor para actualizar la página web**:
“`cpp
events.send(“ping”,NULL,millis());
events.send(String(temperature).c_str(),”temperature”,millis());
events.send(String(humidity).c_str(),”humidity”,millis());
events.send(String(pressure).c_str(),”pressure”,millis());
“`
Fíjate en que **cada evento tiene un nombre específico**: `temperature`, `humidity` o `pressure`. El JavaScript del cliente **escucha cada uno de esos eventos por separado** y **actualiza el elemento HTML correspondiente**.
## 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 monitorizar las lecturas del sensor**. **Las lecturas se actualizan automáticamente cada 30 segundos**.
Al mismo tiempo, deberías **obtener nuevas lecturas del sensor en el Monitor Serie**. Además, puedes **comprobar si el cliente está recibiendo los eventos**. En tu navegador, **abre la consola pulsando `Ctrl+Shift+J`** (en Windows y Linux) o **`Cmd+Option+J`** (en macOS). Verás los **mensajes de log que hemos añadido al JavaScript**, confirmando que **los eventos llegan correctamente**.
## Conclusión: lo que has aprendido
En este tutorial has aprendido **cómo usar Server-Sent Events con el ESP32**. **Server-Sent Events permite que una página web (cliente) reciba actualizaciones de un servidor**. Esto puede usarse para **mostrar automáticamente nuevas lecturas de sensores en la página del servidor web cuando estén disponibles**.
**SSE es una alternativa más simple que WebSocket cuando solo necesitas comunicación unidireccional**, es decir, cuando el servidor envía datos al cliente pero el cliente no necesita enviar datos al servidor. **Para proyectos de monitorización de sensores, dashboards y paneles de solo lectura, SSE es ideal**: la implementación es más ligera, el protocolo es más sencillo, y **funciona sobre HTTP estándar sin necesidad de librerías JavaScript complejas**.
Si **en algún momento necesitas comunicación bidireccional** (por ejemplo, para controlar salidas desde la página web), puedes **combinar SSE para las lecturas con HTTP POST para los comandos**, o **migrar a WebSocket** para tener comunicación full duplex.
Server-Sent Events (SSE) permite que el cliente reciba actualizaciones automáticas desde un servidor a través de una conexión HTTP. El cliente inicia la conexión SSE y el servidor utiliza el protocolo EventSource para enviar actualizaciones al cliente. El cliente recibirá actualizaciones del servidor, pero no podrá enviar ningún dato al servidor después del handshake inicial.
Esto es especialmente útil para enviar lecturas actualizadas de sensores al navegador. Cada vez que hay una nueva lectura disponible, el ESP32 la envía al cliente y la página web puede actualizarse automáticamente sin necesidad de peticiones adicionales. En lugar de lecturas de sensores, puedes enviar cualquier dato que pueda ser útil para tu proyecto, como estados de GPIO, notificaciones cuando se detecta movimiento, etc.
Importante: Server-Sent Events (SSE) no son compatibles con Internet Explorer. Si necesitas compatibilidad con navegadores antiguos, tendrás que usar WebSocket o polling tradicional.
Resumen del proyecto
El servidor web del ESP32 muestra tres tarjetas con las lecturas de temperatura, humedad y presión del sensor BME280. El ESP32 obtiene nuevas lecturas del sensor cada 30 segundos. Cada vez que hay una nueva lectura disponible, la placa (servidor) la envía al cliente usando Server-Sent Events. El cliente recibe los datos y actualiza la página web en consecuencia. Esto permite que la página web se actualice automáticamente cada vez que hay nuevas lecturas disponibles.
¿Cómo funciona esto?
El funcionamiento de SSE para actualizar la página web se puede resumir así:
El cliente inicia la conexión SSE y el servidor utiliza el protocolo EventSource en la URL /events para enviar actualizaciones al cliente. El ESP32 obtiene nuevas lecturas del sensor. Envía las lecturas como eventos con los siguientes nombres al cliente: temperature, humidity y pressure. El cliente tiene event listeners para los eventos enviados por el servidor y recibe las lecturas actualizadas en esos eventos. Actualiza la página web con las lecturas más recientes.
Preparar Arduino IDE
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
Para construir el servidor web usaremos la librería ESPAsyncWebServer (de ESP32Async). Esta librería necesita 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.
Instalar las librerías del sensor BME280
Para obtener lecturas del módulo sensor BME280, usaremos la librería Adafruit_BME280. También necesitas instalar la librería Adafruit_Sensor. Ambas pueden instalarse a través del Gestor de Librerías de Arduino IDE.
Construir el circuito
Para ejemplificar cómo usar server-sent events con el ESP32, enviaremos lecturas del sensor BME280 al navegador. Así que necesitas conectar un sensor BME280 a tu ESP32.
Componentes necesarios
Para completar este tutorial necesitas: un módulo sensor BME280, una placa ESP32, una protoboard y cables jumper.
Esquema de conexión
Vamos a usar comunicación I2C con el módulo sensor BME280. Para ello, conecta el sensor a los pines SCL (GPIO 22) y SDA (GPIO 21) por defecto del ESP32. La alimentación del sensor va a 3,3 V y GND.
Código del servidor web del ESP32 con SSE
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-web-server-sent-events-sse/
*/
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
// Reemplaza con las credenciales de tu red
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Crea el objeto AsyncWebServer en el puerto 80
AsyncWebServer server(80);
// Crea un Event Source en /events
AsyncEventSource events("/events");
// Variables del temporizador
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;
// Crea un objeto sensor
Adafruit_BME280 bme; // BME280 conectado al I2C del ESP32 (GPIO 21 = SDA, GPIO 22 = SCL)
float temperature;
float humidity;
float pressure;
// Inicia el BME280
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
}
void getSensorReadings(){
temperature = bme.readTemperature();
// Convierte la temperatura a Fahrenheit
//temperature = 1.8 * bme.readTemperature() + 32;
humidity = bme.readHumidity();
pressure = bme.readPressure()/ 100.0F;
}
// Inicia WiFi
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.println(WiFi.localIP());
}
String processor(const String& var){
getSensorReadings();
//Serial.println(var);
if(var == "TEMPERATURE"){
return String(temperature);
}
else if(var == "HUMIDITY"){
return String(humidity);
}
else if(var == "PRESSURE"){
return String(pressure);
}
return String();
}
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="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
p { font-size: 1.2rem;}
body { margin: 0;}
.topnav { overflow: hidden; background-color: #50B8B4; color: white; font-size: 1rem; }
.content { padding: 20px; }
.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
.cards { max-width: 800px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.reading { font-size: 1.4rem; }
</style>
</head>
<body>
<div class="topnav">
<h1>BME280 WEB SERVER (SSE)</h1>
</div>
<div class="content">
<div class="cards">
<div class="card">
<p><i class="fas fa-thermometer-half" style="color:#059e8a;"></i> TEMPERATURE</p><p><span class="reading"><span id="temp">%TEMPERATURE%</span> °C</span></p>
</div>
<div class="card">
<p><i class="fas fa-tint" style="color:#00add6;"></i> HUMIDITY</p><p><span class="reading"><span id="hum">%HUMIDITY%</span> %</span></p>
</div>
<div class="card">
<p><i class="fas fa-angle-double-down" style="color:#e1e437;"></i> PRESSURE</p><p><span class="reading"><span id="pres">%PRESSURE%</span> hPa</span></p>
</div>
</div>
</div>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
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('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('temperature', function(e) {
console.log("temperature", e.data);
document.getElementById("temp").innerHTML = e.data;
}, false);
source.addEventListener('humidity', function(e) {
console.log("humidity", e.data);
document.getElementById("hum").innerHTML = e.data;
}, false);
source.addEventListener('pressure', function(e) {
console.log("pressure", e.data);
document.getElementById("pres").innerHTML = e.data;
}, false);
}
</script>
</body>
</html>)rawliteral";
void setup() {
Serial.begin(115200);
initWiFi();
initBME();
// Maneja el servidor web
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", index_html, processor);
});
// Maneja los eventos del servidor web
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// envía un evento con el mensaje "hello!", id actual en millis
// y establece el delay de reconexión a 10 segundos
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
server.begin();
}
void loop() {
if ((millis() - lastTime) > timerDelay) {
getSensorReadings();
Serial.printf("Temperature = %.2f ºC \n", temperature);
Serial.printf("Humidity = %.2f \n", humidity);
Serial.printf("Pressure = %.2f hPa \n", pressure);
Serial.println();
// Envía eventos al cliente web con las lecturas del sensor
events.send("ping",NULL,millis());
events.send(String(temperature).c_str(),"temperature",millis());
events.send(String(humidity).c_str(),"humidity",millis());
events.send(String(pressure).c_str(),"pressure",millis());
lastTime = millis();
}
}
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
Incluir las librerías
Las librerías Adafruit_Sensor y Adafruit_BME280 son necesarias para interactuar con el sensor BME280. Las librerías WiFi, ESPAsyncWebServer y AsyncTCP se usan para crear el servidor web.
Credenciales de red
Se insertan las credenciales de red en las variables correspondientes para que el ESP32 pueda conectarse a tu red local mediante Wi-Fi.
AsyncWebServer y AsyncEventSource
Se crea un objeto AsyncWebServer en el puerto 80. La siguiente línea crea un nuevo event source en /events:
AsyncEventSource events("/events");
Esta es la pieza central de SSE: el objeto events será el encargado de enviar los eventos a todos los clientes conectados cada vez que tengamos nuevos datos.
Declarar variables
Las variables lastTime y timerDelay se usarán para actualizar las lecturas del sensor cada X número de segundos. Como ejemplo, obtendremos nuevas lecturas cada 30 segundos (30000 milisegundos). Puedes cambiar ese tiempo de delay en la variable timerDelay.
Se crea un objeto Adafruit_BME280 llamado bme en los pines I2C por defecto del ESP32. Las variables temperature, humidity y pressure de tipo float se usarán para almacenar las lecturas del sensor BME280.
Inicializar el BME280
La siguiente función se puede llamar para inicializar el sensor BME280. Si el sensor no responde en la dirección I2C 0x76, el programa se queda en un bucle infinito e imprime un mensaje de error por el Monitor Serie.
Obtener lecturas del BME280
La función getSensorReadings() obtiene las lecturas de temperatura, humedad y presión del sensor BME280 y las guarda en las variables temperature, humidity y pressure. También incluye una línea comentada para convertir la temperatura a Fahrenheit si lo prefieres.
Inicializar Wi-Fi
La siguiente función configura el ESP32 como estación Wi-Fi y se conecta a tu router usando el SSID y la contraseña definidos antes. La función imprime puntos suspensivos mientras espera la conexión y, una vez conectada, muestra la dirección IP local por el Monitor Serie.
La función processor()
La función processor() reemplaza cualquier placeholder del texto HTML que se usa para construir la página web con las lecturas actuales del sensor antes de enviarla al navegador. Esto nos permite mostrar las lecturas actuales del sensor en la página web cuando la visitas por primera vez. De lo contrario, verías un espacio en blanco hasta que hubiera nuevas lecturas disponibles (lo que puede tardar un tiempo según el delay definido en el código).
Construir la página web
La variable index_html contiene todo el HTML, CSS y JavaScript necesarios para construir la página web.
Nota: para simplificar este tutorial, colocamos 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 subas al sistema de archivos del ESP32 y referencies en el código.
Sección CSS
Entre las etiquetas <style> y </style> se incluyen los estilos para estilizar la página web usando CSS. Puedes cambiarlos libremente para hacer que la página web se vea como quieras.
Sección HTML
Entre las etiquetas <body> y </body> se añade el contenido de la página web visible para el usuario. Hay un encabezado principal con el contenido “BME280 WEB SERVER (SSE)”. Ese es el texto que aparece en la barra superior. Puedes modificar ese texto libremente.
Después, mostramos las lecturas del sensor en etiquetas div separadas. Veamos los párrafos que muestran la temperatura:
<p><i class="fas fa-thermometer-half" style="color:#059e8a;"></i> TEMPERATURE</p>
<p><span class="reading"><span id="temp">%TEMPERATURE%</span> °C</span></p>
El primer párrafo simplemente muestra el texto “TEMPERATURE”. Definimos el color y también un icono. En el segundo párrafo, puedes ver que el placeholder %TEMPERATURE% está rodeado por las etiquetas <span id="temp"> </span>. El atributo id de HTML se usa para especificar un identificador único para un elemento HTML.
Se usa para apuntar a un estilo específico o puede ser usado por JavaScript para acceder y manipular el elemento con ese id específico. Eso es lo que vamos a hacer. Por ejemplo, cuando el cliente recibe un nuevo evento con la última lectura de temperatura, actualiza el elemento HTML con el id temp con la nueva lectura.
Un proceso similar se hace para actualizar las otras lecturas.
JavaScript: EventSource
El JavaScript va entre las etiquetas <script> y </script>. Es responsable de inicializar una conexión EventSource con el servidor y manejar los eventos recibidos del servidor.
Manejar eventos
Se crea un nuevo objeto EventSource y se especifica la URL de la página que envía las actualizaciones. En nuestro caso, es /events:
if (!!window.EventSource) {
var source = new EventSource('/events');
Una vez que has instanciado un event source, puedes empezar a escuchar mensajes del servidor con addEventListener(). Estos son los event listeners por defecto:
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('message', function(e) {
console.log("message", e.data);
}, false);
Después, se añade el event listener para temperature:
source.addEventListener('temperature', function(e) {
Cuando hay una nueva lectura de temperatura disponible, el ESP32 envía un evento (temperature) al cliente. Las siguientes líneas manejan qué ocurre cuando el navegador recibe ese evento:
console.log("temperature", e.data);
document.getElementById("temp").innerHTML = e.data;
Básicamente, imprime las nuevas lecturas en la consola del navegador y coloca los datos recibidos en el elemento con el id correspondiente (temp) en la página web.
Un procesador similar se usa para humedad y presión:
source.addEventListener('humidity', function(e) {
console.log("humidity", e.data);
document.getElementById("hum").innerHTML = e.data;
}, false);
source.addEventListener('pressure', function(e) {
console.log("pressure", e.data);
document.getElementById("pres").innerHTML = e.data;
}, false);
La función setup()
En el setup(), se inicializa el Monitor Serie, se inicializa Wi-Fi y el sensor BME280.
Manejar peticiones
Cuando accedes a la dirección IP del ESP32 en la URL raíz /, se envía el texto que está almacenado en la variable index_html para construir la página web y se pasa el processor como argumento, para que todos los placeholders sean reemplazados con las últimas lecturas del sensor.
Event source del servidor
Se configura el event source en el servidor:
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
La función onConnect se ejecuta cada vez que un nuevo cliente se conecta al stream de eventos. Enviamos un mensaje de bienvenida “hello!” con un identificador de mensaje y un delay de reconexión de 10 segundos. Esto es útil para que los navegadores sepan cuánto tiempo esperar antes de intentar reconectar si la conexión se pierde.
Finalmente, se inicia el servidor.
La función loop()
En el loop(), se obtienen nuevas lecturas del sensor. Se imprimen las nuevas lecturas en el Monitor Serie. Finalmente, se envían eventos al navegador con las lecturas más recientes del sensor para actualizar la página web:
events.send("ping",NULL,millis());
events.send(String(temperature).c_str(),"temperature",millis());
events.send(String(humidity).c_str(),"humidity",millis());
events.send(String(pressure).c_str(),"pressure",millis());
Fíjate en que cada evento tiene un nombre específico: temperature, humidity o pressure. El JavaScript del cliente escucha cada uno de esos eventos por separado y actualiza el elemento HTML correspondiente.
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 monitorizar las lecturas del sensor. Las lecturas se actualizan automáticamente cada 30 segundos.
Al mismo tiempo, deberías obtener nuevas lecturas del sensor en el Monitor Serie. Además, puedes comprobar si el cliente está recibiendo los eventos. En tu navegador, abre la consola pulsando Ctrl+Shift+J (en Windows y Linux) o Cmd+Option+J (en macOS). Verás los mensajes de log que hemos añadido al JavaScript, confirmando que los eventos llegan correctamente.
Conclusión: lo que has aprendido
En este tutorial has aprendido cómo usar Server-Sent Events con el ESP32. Server-Sent Events permite que una página web (cliente) reciba actualizaciones de un servidor. Esto puede usarse para mostrar automáticamente nuevas lecturas de sensores en la página del servidor web cuando estén disponibles.
SSE es una alternativa más simple que WebSocket cuando solo necesitas comunicación unidireccional, es decir, cuando el servidor envía datos al cliente pero el cliente no necesita enviar datos al servidor. Para proyectos de monitorización de sensores, dashboards y paneles de solo lectura, SSE es ideal: la implementación es más ligera, el protocolo es más sencillo, y funciona sobre HTTP estándar sin necesidad de librerías JavaScript complejas.
Si en algún momento necesitas comunicación bidireccional (por ejemplo, para controlar salidas desde la página web), puedes combinar SSE para las lecturas con HTTP POST para los comandos, o migrar a WebSocket para tener comunicación full duplex.
Descubre más desde CIBERED
Suscríbete y recibe las últimas entradas en tu correo electrónico.
