ESP8266 NodeMCU Web Server using Server-Sent Events (Update Sensor Readings Automatically)

This tutorial shows how to use Server-Sent Events (SSE) in an ESP8266 NodeMCU Web Server programmed with Arduino IDE. SSE allows the browser to receive automatic updates from a server via HTTP connection. This is useful to send updated sensor readings to the browser, for example. Whenever a new reading is available, the ESP8266 sends it to the client and the web page can be updated automatically without the need to make additional requests.

ESP8266 NodeMCU Web Server using Server-Sent Events (SSE) Update Sensor Readings Automatically Arduino

As an example, we’ll build a web server that displays sensor readings from a BME280 temperature, humidity and pressure sensor. To learn more about the BME280, read our guide:

We also have a similar Server-Sent Events guide for the ESP32.

Introducing Server-Sent Events (SSE)

Server-Sent Events (SSE) allows the client to receive automatic updates from a server via HTTP connection.

ESP8266 NodeMCU Server-Sent Events Web Server How it Works

The client initiates the SSE connection and the server uses the event source protocol to send updates to the client. The client will receive updates from the server, but it can’t send any data to the server after the initial handshake.

This is useful to send updated sensor readings to the browser. Whenever a new reading is available, the ESP8266 sends it to the client and the web page can be updated automatically without the need for further requests. Instead of sensor readings, you can send any data that might be useful for your project like GPIO states, notifications when motion is detected, etc.

Important: Server-Sent Events (SSE) are not supported on Internet Explorer.

Project Overview

Here’s the web page we’ll build for this project.

BME280 Web Server using Server-Sent Events (SSE) ESP8266 NodeMCU Arduino
  • The ESP8266 web server displays three cards with BME280 temperature, humidity and pressure readings;
  • The ESP8266 gets new readings from the sensor every 30 seconds;
  • Whenever a new reading is available, the board (server) sends it to the client using server-sent events;
  • The client receives the data and updates the web page accordingly;
  • This allows the web page to be updated automatically whenever new readings are available.

How it Works?

The following diagram summarizes how Server-Sent Events work to update the web page.

ESP8266 NodeMCU BME280 Web Server with Server Sent Events (SSE) How it Works
  1. The client initiates the SSE connection and the server uses the event source protocol on the /events URL to send updates to the client;
  2. The ESP8266 gets new sensor readings;
  3. It sends the readings as events with the following names to the client: ‘temperature’, ‘humidity’ and ‘pressure’;
  4. The client has event listeners for the events sent by the server and receives the updated sensor readings on those events;
  5. It updates the web page with the newest readings.

Preparing Arduino IDE

We’ll program the ESP8266 board using Arduino IDE, so make sure you have it installed in your Arduino IDE.

Installing Libraries – Async Web Server

To build the web server we’ll use the ESPAsyncWebServer library. This library needs the ESPAsyncTCP library to work properly. Click the links below to download the libraries.

These libraries aren’t available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the libraries you’ve just downloaded.

Installing Libraries – BME280 Sensor

To get readings from the BME280 sensor module, we’ll use the Adafruit_BME280 library. You also need to install the Adafruit_Sensor library. Follow the next steps to install the libraries in your Arduino IDE:

1. Open your Arduino IDE and go to Sketch Include Library > Manage Libraries. The Library Manager should open.

2. Search for “adafruit bme280 â€ť on the Search box and install the library.

Installing BME280 library in Arduino IDE

To use the BME280 library, you also need to install the Adafruit Unified Sensor. Follow the next steps to install the library in your Arduino IDE:

3. Search for “Adafruit Unified Sensor“in the search box. Scroll all the way down to find the library and install it.

Installing Adafruit Unified Sensor Driver library

After installing the libraries, restart your Arduino IDE.

To learn more about the BME280 sensor, read our guide: ESP8266 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity).

Building the Circuit

To exemplify how to use server-sent events with the ESP8266, we’ll send sensor readings from a BME280 sensor to the browser. So, you need to wire a BME280 sensor to your ESP8266.

Parts Required

To complete this tutorial you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic – ESP8266 with BME280 using I2C

We’re going to use I2C communication with the BME280 sensor module. For that, wire the sensor to the ESP8266 SDA and SCL pins, as shown in the following schematic diagram.

Schematic Wiring diagram ESP8266 NodeMCU with BME280 using I2C

Recommended reading: ESP8266 Pinout Reference Guide

Code for ESP8266 Web Server using Server-Sent Events (SSE)

Copy the following code to your Arduino IDE.

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp8266-nodemcu-web-server-sent-events-sse/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*********/

#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Create an Event Source on /events
AsyncEventSource events("/events");

// Timer variables
unsigned long lastTime = 0;  
unsigned long timerDelay = 30000;

// Create a sensor object
Adafruit_BME280 bme; // BME280 connect to ESP32 I2C (GPIO 21 = SDA, GPIO 22 = SCL)

float temperature;
float humidity;
float pressure;

// Init 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();
  // Convert temperature to Fahrenheit
  //temperature = 1.8 * bme.readTemperature() + 32;
  humidity = bme.readHumidity();
  pressure = bme.readPressure()/ 100.0F;
}

// Initialize 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> &deg;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> &percnt;</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();


  // Handle Web Server
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // 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());
    }
    // send event with message "hello!", id current millis
    // and set reconnect delay to 1 second
    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();

    // Send Events to the Web Server with the Sensor Readings
    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();
  }
}

View raw code

Insert your network credentials in the following variables and the code will work straight away.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

How the Code Works

Continue reading to learn how the code works or skip to the Demonstration section.

Including Libraries

The Adafruit_Sensor and Adafruit_BME280 libraries are needed to interface with the BME280 sensor.

#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>

The WiFi, ESPAsyncWebServer and AsyncTCP libraries are used to create the web server.

#include <WiFi.h>
#include "ESPAsyncWebServer.h"

Network Credentials

Insert your network credentials in the following variables, so that the ESP8266 can connect to your local network using Wi-Fi.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

AsyncWebServer and AsyncEventSource

Create an AsyncWebServer object on port 80.

AsyncWebServer server(80);

The following line creates a new event source on /events.

AsyncEventSource events("/events");

Declaring Variables

The lastTime and the timerDelay variables will be used to update sensor readings every X number of seconds. As an example, we’ll get new sensor readings every 30 seconds (30000 milliseconds). You can change that delay time in the timerDelay variable.

unsigned long lastTime = 0;
unsigned long timerDelay = 30000;

Create an Adafruit_BME280 object called bme on the default ESP8266 I2C pins.

Adafruit_BME280 bme;

The temperature, humidity and pressure float variables will be used to hold BME280 sensor readings.

float temperature;
float humidity;
float pressure;

Initialize BME280

The following function can be called to initialize the BME280 sensor.

void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

Get BME280 Readings

The getSensorReading() function gets temperature, humidity and pressure readings from the BME280 sensor and saves them on the temperature, humidity and pressure variables.

void getSensorReadings(){
  temperature = bme.readTemperature();
  // Convert temperature to Fahrenheit
  //temperature = 1.8 * bme.readTemperature() + 32;
  humidity = bme.readHumidity();
  pressure = bme.readPressure()/ 100.0F;
}

Initialize Wi-Fi

The following function sets the ESP8266 as a Wi-Fi station and connects to your router using the ssid and password defined earlier.

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());
}

Processor

The processor() function replaces any placeholders on the HTML text used to build the web page with the current sensor readings before sending it to the browser.

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

This allows us to display the current sensor readings on the web page when you access it for the first time. Otherwise, you would see a blank space until new readings were available (which can take some time depending on the delay time you’ve defined on the code).

Building the Web Page

The index_html variable contains all the HTML, CSS and JavaScript to build the web page.

Note: for the simplicity of this tutorial, we’re placing everything needed to build the web page on the index_html variable that we use on the Arduino sketch. Note that it may be more practical to have separated HTML, CSS and JavaScript files that then you upload to the ESP8266 filesystem and reference them on the code.

Here’s the content index_html variable:

<!DOCTYPE HTML>
<html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
  <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> &deg;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> &percnt;</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>

We won’t go into detail on how the HTML and CSS works. We’ll just take a look at how to handle the events sent by the server.

CSS

Between the <style> </style> tags we include the styles to style the web page using CSS. Feel free to change it to make the web page look as you wish. We won’t explain how the CSS for this web page works because it is not relevant for this tutorial.

<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>

HTML

Between the <body> </body> tags we add the web page content that is visible to the user.

<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> &deg;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> &percnt;</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>

There’s a heading 1 with the content “BME280 WEB SERVER (SSE)”. That’s the text that shows up on the top bar. Feel free to modify that text.

<h1>BME280 WEB SERVER (SSE)</h1>

Then, we display the sensor readings in separated div tags. Let’s take a quick look at the paragraphs that displays the temperature:

<p><i class="fas fa-thermometer-half" style="color:#059e8a;"></i> TEMPERATURE</p>
<p><span class="reading"><span id="temp">%TEMPERATURE%</span> &deg;C</span></p>

The first paragraph simply displays the “TEMPERATURE” text. We define the color and also an icon.

On the second paragraph, you can see that the %TEMPERATURE% placeholder is surrounded by <span id=”temp”> </span> tags. The HTML id attribute is used to specify a unique id for an HTML element.

It is used to point to a specific style or it can be used by JavaScript to access and manipulate the element with that specific id. That’s what we’re going to do. For instance, when the client receives a new event with the latest temperature reading, it updates the HTML element with the id “temp” with the new reading.

A similar process is done to update the other readings.

JavaScript – EventSource

The JavaScript goes between the <script> </script> tags. It is responsible for initializing an EventSource connection with the server and handle the events received from the server.

<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>

Let’s take a look at how this works.

Handle Events

Create a new EventSource object and specify the URL of the page sending the updates. In our case, it’s /events.

if (!!window.EventSource) {
  var source = new EventSource('/events');

Once you’ve instantiated an event source, you can start listening for messages from the server with addEventListener().

These are the default event listeners, as shown here in the AsyncWebServer documentation.

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

Then, add the event listener for “temperature”.

source.addEventListener('temperature', function(e) {

When a new temperature reading is available, the ESP8266 sends an event (“temperature”) to the client. The following lines handle what happens when the browser receives that event.

console.log("temperature", e.data);
document.getElementById("temp").innerHTML = e.data;

Basically, print the new readings on the browser console, and put the received data into the element with the corresponding id (“temp“) on the web page.

A similar processor is done for humidity and pressure.

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);
 
source.addEventListener('gas', function(e) {
  console.log("gas", e.data);
  document.getElementById("gas").innerHTML = e.data;
}, false);

setup()

In the setup(), initialize the Serial Monitor, initialize Wi-Fi and the BME280 sensor.

Serial.begin(115200);
initWiFi();
initBME();

Handle Requests

When you access the ESP8266 IP address on the root / URL, send the text that is stored on the index_html variable to build the web page and pass the processor as argument, so that all placeholders are replaced with the latest sensor readings.

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html, processor);
});

Server Event Source

Set up the event source on the server.

// 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());
  }
  // send event with message "hello!", id current millis
  // and set reconnect delay to 1 second
  client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);

Finally, start the server.

server.begin();

loop()

In the loop(), get new sensor readings:

getSensorReadings();

Print the new readings in the Serial Monitor.

Serial.printf("Temperature = %.2f ÂşC \n", temperature);
Serial.printf("Humidity = %.2f % \n", humidity);
Serial.printf("Pressure = %.2f hPa \n", pressure);
Serial.println();

Finally, send events to the browser with the newest sensor readings to update the web page.

// Send Events to the Web Server with the Sensor Readings
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());

Demonstration

After inserting your network credentials on the ssid and password variables, you can upload the code to your board. Don’t forget to check if you have the right board and COM port selected.

After uploading the code, open the Serial Monitor at a baud rate of 115200 and press the on-board EN/RST button. The ESP IP address should be printed.

ESP8266 NodeMCU Web Server using Server Sent Events Serial Monitor Demonstration

Open a browser on your local network and insert the ESP8266 IP address. You should get access to the web page to monitor the sensor readings.

BME280 Web Server using Server-Sent Events SSE ESP8266 NodeMCU

The readings are updated automatically every 30 seconds.

At the same time, you should get new sensor readings on the Serial Monitor as shown in the previous print screen. Additionally, you can check if the client is receiving the events. On your browser, open the console by pressing Ctrl+Shift+J.

Server-Sent Events Browser Console ESP8266 NodeMCU Web Server Demonstration

Wrapping Up

In this tutorial you’ve learned how to use Server-Sent Events with the ESP8266. Server-Sent Events allow a web page (client) to get updates from a server. This can be used to automatically display new sensor readings on the web server page when they are available.

We have a similar tutorial but using the BME680 environmental sensor. You can check the tutorial on the following link:

Instead of Server-Sent Events, you can also use the WebSocket protocol to keep the web page updated. Take a look at the following tutorial to learn how to set up a WebSocket server with the ESP8266:

Learn more about the ESP8266 with our resources:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

20 thoughts on “ESP8266 NodeMCU Web Server using Server-Sent Events (Update Sensor Readings Automatically)”

  1. Very interesting and practical topic.
    I have a problem that I have already seen in other tutorials where the ESPAsyncWebServer library is used.
    I can’t use the wifimanager.h library and it gives me many errors due to some compatibility between these libraries (I suppose).

    There may be a specific example, perhaps the best thing would be in an adaptation of this tutorial to replace the data of my network name / password using the wifimanager library?
    I am using tzapu / tablatronix version 2.03.
    Thank you.

    Reply
  2. I found the design worked great with updates every 30 seconds.
    Changing the update time to 10 seconds I found that the design often skipped an update. Am I asking the design for something that fundamentally cannot be done? Maybe there are other high speed libraries. I’ve no idea of how to test for where the bottleneck, if any might be. Any comments would be appreciated.

    I wish to adapt the design for a model train where over a 2 metre section of track there are 4 sensors so I could detect 4 states I called {no_train, approaching, in-station, departing}. The train would take roughly 30 seconds to transverse that 2 metres so to capture and display all states I would need to update approximately every 7.5 seconds.

    Many thanks
    John K

    Reply
  3. Further to my last comment I commented out the following two lines in the loop( ):
    events.send(String(humidity).c_str(),”humidity”,millis());
    events.send(String(pressure).c_str(),”pressure”,millis());
    and set the temperature += 1;
    The design now worked beautifully – the temperature going up in one degree steps. I reduced the sample time to 500 ms and the smartphone followed perfectly.
    Returning the events.send(String(humidity).c_str(),”humidity”,millis()); line the temperature missed values even with a 5 second sample time.
    I think if I wanted temperature, humidity and pressure I would consider sending them separately a few seconds apart rather than bang, bang bang as per the original code.

    You really need good tools to really see what is happening with the ESP8266 WiFi

    JK

    Reply
    • Hi.
      Thanks for the suggestion.
      When we’re talking about temperature readings, it might not be an issue missing one reading once in a time, specially with so little interval between them.
      However, in your project, as you said, it is crucial. So, thanks for pointing that out.
      Regards,
      Sara

      Reply
  4. Thanks for this excellent example
    I have similar problem in the oposite direction.
    I have a home automation system that gives on a webpage a continuous flow of data (every second) in JSON format for a dozen of measured parameters.
    I would need to read them and copy in the corresponding variables.
    So globally three steps:
    1. Logging to server ( Digest authentication) – solved I think
    2. reading String from Webpage Every second – NO IDEA HOW TO DO it and found no tutorials/examples
    3. Parsing JSON and extracting data (easiest part I think)

    Can anyone help with some links/opinion?

    Reply
  5. great tutorials!! ive tried loads of them, loved them all!

    this tutorial would be awesome in combination with plotting sensor graphs if pushed a sensor button on the served page. Any pointers how to accomplish this?

    Reply
  6. Hi
    Thanks for great sharing

    Is it possible to use esp32or esp8266 as sse EventListener or client so that it can listen to data sent by website hosted on share webservers

    Reply
  7. Hi.
    I like this project. I want to ask if this works within the world or just within one wifi? I would like to measure the temperature in my cottage and view it on the phone at work. Does it work like this? Thank you for your response.
    Have a nice day
    Ivan

    Reply
  8. HI Sara. Very good job. Helped a lot. But the question is whether it is really necessary to call getSensorReadings(); subprogram in both the processor function and the loop routine. I had big problems with controller restarts until I deleted this call from the processor subprogram.

    Reply
  9. Hi Sara, hi Rui,

    this sample-code works absolut fine – thanks for that. So what i found is a tricky situation in this code. When i set the loop-delay to 1 Minute (60000) and the code starts for the first time, i see the webpage and the gauges – so far , so good. But i have to wait 1 minute before the “first” sensor-data is shown in the gauges. This is about the following code-snipet in the loop() as follow:

    if ((millis() – lastTime) > timerDelay) {

    How can i reach the goal that when the system starts and the startpage (index_html) is rendered the first time that sensor valus is shown immediately as the form is loaded in the browser-page? Same situation when refreshing the browser page with e.g. F5. The startpage is loaded immediately but i have to wait for the first loop-running to get actual sensor-values.

    Would be nice if you could show a solutions for this “little gap”. I think a normal loop-timing could be between 10 Minutes up to 1 Hour to cover the changes in all sensor-parameters for a normal detection.

    Thanks a lot,
    Greeting from Manfred

    Reply
    • I think your program is doing exactly what it said:

      if ((millis() – lastTime) > timerDelay) {

      For the first pass lastTime == 0 and millis( ) is also a very small value. The millis() starts when your program starts so could be a few milliseconds at the very most. I think I would try adding if ((millis() – lastTime) > timerDelay)||(lastTime==0) {.

      Alternaively you need to do the action once at the end of your setup( ) routine, That is

      void setup( ){
      …previous code
      action( );
      }

      void loop( ){
      if ((millis() – lastTime) > timerDelay) {
      action( ); //where the new function action( ) is all the code that was in this loop
      }
      }

      Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.