In this user guide, we will learn about web server with Server-Sent Events (SSE) protocol. Using this technique, we will build an ESP8266 controlled web server that will automatically update sensor readings regarding temperature, pressure, and humidity to all connected web clients. Our client will receive automatic updates from the ESP8266 board with SSE through an HTTP connection. This will be highly useful to send new sensor readings to the browser whenever they will be available. Without making any additional requests, the webpage will spontaneously update. Any appropriate sensor can be used such as DS18B20, BME680, LM35, and MPU6050 but for this article, we will use a BME280 sensor which is used to measure temperature, pressure, and humidity. We will use Arduino IDE to program our ESP8266 board which will be connected to a BME280 sensor.
We have a similar guide with ESP32:
ESP32 Server-Sent Events (SSE) Web Server (Arduino IDE)
Server-Sent Events (SSE) Introduction
Server-Sent Events also known as SSE is a protocol whereby clients receive updated data from a server through an HTTP connection. It involves an initial handshake between the client and server. The SSE connected is started from the client’s side and the server uses EventSource to update the client accordingly.
In a previous article, we built an ESP8266 web server through WebSocket protocol (ESP8266 NodeMCU WebSocket Server using Arduino IDE and LittleFS – Control GPIOs). In that web server, all the connected clients (smartphones, tablets, computers, laptops, etc.) were updated instantaneously whenever a change occurred. This time, however, we will look at a different way to notify events to our client through SSE protocol. One of the major differences between the two protocols is that whereas WebSocket has bi-directional server-client transmission, in Server-Sent Events only the server (ESP8266) can send updates to the client and not vice-versa. Hence SSE follows one-way communication only after the initial handshake. This can be seen in the diagram below:
Note: Make sure you use a web browser that supports SSE. For more information about compatible browsers follow the link for further information: Link
ESP8266 Server-Sent Events Project Overview
We will create a web server based on the Server-Sent Events (SSE) protocol. It will consist of a title, “ESP8266 & BME280 SSE Web server,” and three boxes each for temperature, pressure and humidity. Each box will contain a heading and its value underneath it. We aim to build a webpage that will display sensor readings obtained from BME280 connected with the ESP8266 module. This transmission of data from the server (ESP8266) to the client (web page) will occur through Server-Sent Events. These readings will automatically update after every 30 seconds to new values when the web page receives them. Through SSE protocol, it will be very easy to update the sensor readings on our web page without adding any additional requests.
Working Process
- The client starts the SSE connection. It sends an HTTP request to the ESP8266 module. This will cause the module to send events to the web page.
- ESP8266 connected with BME280 receives sensor readings after every 30 seconds.
- These readings are marked as ‘events’ of the following names: ‘temperature’, ‘pressure’ and ‘humidity’.
- These events sent by the module have event listeners associated with them on the client-side. All the updated readings are received by the client on the said events.
- As in the case with SSE, only the ESP8266 board will be able to send events to the webpage. The web page gets updated accordingly and the process continues.
Introduction to BME280 sensor
Recommended Readings:
- MicroPython: BME280 Web Server with ESP32/ESP8266 (Weather Station)
- MicroPython: BME280 with ESP32 and ESP8266 – Measure Temperature, Humidity, and Pressure
- ESP32 Send Sensor Readings to ThingSpeak using Arduino IDE (BME280)
The BME280 sensor is used to measure readings regarding ambient temperature, barometric pressure, and relative humidity. It is mostly used in web and mobile applications where low power consumption is key. This sensor uses I2C or SPI to communicate data with the micro-controllers. Although there are several different versions of BME280 available in the market, the one we will be studying uses the I2C communication protocol and SPI.
Connecting BME280 sensor with the ESP8266 development board
The connection of BME280 with the ESP8266 boards is very easy. We have to connect the VCC terminal with 3.3V, ground with the ground (common ground), SCL of the sensor with SCL of the module, and SDA of the sensor with the SDA pin of the ESP modules.
For ESP8266, the default I2C pins for SDA are GPIO4 (D2) and for SCL are GPIO5 (D1).
Required Components:
We will need the following components to connect our ESP8266 board with the BME280 sensor.
- ESP8266 board
- BME280 Sensor
- Connecting Wires
- Breadboard
Follow the schematic diagram below for the ESP8266 module and connect them accordingly.
In some BME280 sensors as seen in the above connection diagram, the SCK terminal means the SCL pin and is connected with its respective GPIO pin on the ESP board. Likewise, the SDI terminal means the SDA pin and is connected with its respective GPIO pin on the board. Vin is connected with a 3.3V pin on the module and both the ESP board and the sensor is commonly grounded.
Setting up Arduino IDE
We will use Arduino IDE to program our ESP8266 development board. Thus, you should have the latest version of Arduino IDE. Additionally, you also need to install the ESP8266 plugin.
If your IDE does not have the plugin installed you can visit the link: Installing ESP8266 library in Arduino IDE
Installing BME280 Libraries
As we are connecting the BME280 sensor with ESP8266 so we will have to install BME280 libraries to our module. We will require two libraries for this project:
- Adafruit BME280 library
- Adafruit Unified Sensor library
We will use the Library Manager in our Arduino IDE to install the latest versions of the libraries. Open your Arduino IDE and go to Sketch > Include Libraries > Manage Libraries. Type each library name in the search bar and install them both.
Installing ESPAsyncWebServer Library and ESPAsync TCP Library
We will need two additional libraries to build our web server. The ESPAsyncWebServer library will help us in creating our web server easily. With this library, we will set up an asynchronous HTTP server. ESPAsyncTCP is another library that we will be incorporating as it a dependency for the ESPAsyncWebServer library. Both of these libraries are not available in the Arduino library manager so we will have to download and load them in our ESP8266 board ourselves.
- To install the ESPAsyncWebServer library for free, click here to download. You will download the library as a .zip folder which you will extract and rename as ‘ESPAsyncWebServer.’ Then, transfer this folder to the installation library folder in your Arduino IDE.
- To install the ESPAsync TCP library for free, click here to download. You will download the library as a .zip folder which you will extract and rename as ‘ESPAsyncTCP.’ Then, transfer this folder to the installation library folder in your Arduino IDE.
Likewise, you can also go to Sketch > Include Library > Add .zip Library inside the IDE to add the libraries as well. After installation of the libraries, restart your IDE.
Arduino Sketch
Open your Arduino IDE and go to File > New. A new file will open. Copy the code given below in that file and save it. Replace the network credentials to successfully connect.
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
const char* ssid = "PTCL-BB"; //Replace with your SSID
const char* password = "*********"; //Repalce with your password
AsyncWebServer server(80);
// Create an Event Source on /events
AsyncEventSource events("/events");
unsigned long last_time = 0;
unsigned long Delay = 30000;
Adafruit_BME280 bme;
float temperature;
float humidity;
float pressure;
// Initialize BME280
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("BME280 sensor not connected properly!");
while (1);
}
}
void Obtain_SensorReadings(){
temperature = bme.readTemperature();
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();
Serial.println("IP Address: ");
Serial.println(WiFi.localIP());
}
String processor(const String& var){
Obtain_SensorReadings();
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>ESP8266 & BME280 SSE 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: Times New Roman; display: inline-block; text-align: center;}
p { font-size: 1.2rem;}
body { margin: 0;}
.topnav { overflow: hidden; background-color: #381d75; color: white; font-size: 1rem; }
.content { padding: 20px; }
.box { background-color: #e6eef2; outline: 1px solid;}
.boxes { 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>ESP8266 & BME280 SSE Web server</h1>
</div>
<div class="content">
<div class="boxes">
<div class="box">
<p><i class="fas fa-thermometer-three-quarters" style="color:#15ab3a;"></i> Temperature</p><p><span class="reading"><span id="temp">%TEMPERATURE%</span> °C</span></p>
</div>
<div class="box">
<p><i class="fas fa-arrow-circle-down" style="color:#070807;"></i> Pressure</p><p><span class="reading"><span id="pres">%PRESSURE%</span> hPa</span></p>
</div>
<div class="box">
<p><i class="fas fa-tint" style="color:#8ccbed;"></i> Humidity</p><p><span class="reading"><span id="hum">%HUMIDITY%</span> %</span></p>
</div>
</div>
</div>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events got Connected!");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events got 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('pressure', function(e) {
console.log("pressure", e.data);
document.getElementById("pres").innerHTML = e.data;
}, false);
source.addEventListener('humidity', function(e) {
console.log("humidity", e.data);
document.getElementById("hum").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());
}
client->send("hi!", NULL, millis(), 10000);
});
server.addHandler(&events);
server.begin();
}
void loop() {
if ((millis() - last_time) > Delay) {
Obtain_SensorReadings();
Serial.printf("Temperature = %.2f ºC \n", temperature);
Serial.printf("Humidity = %.2f percent \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());
last_time = millis();
}
}
How the Code Works?
Including Libraries
Firstly, we will include the necessary libraries. For this project, we are using five of them. As we have to connect our ESP8266 to a wireless network hence we need ESP8266WiFi.h library for that purpose. The other libraries are the ones that we recently downloaded and will be required for the building of the asynchronous web server and the functionality of the sensor respectively.
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
Setting Network Credentials
Next, we will create two global variables, one for the SSID and the other for the password. These will hold our network credentials which will be used to connect to our wireless network. Replace both of them with your credentials to ensure a successful connection.
const char* ssid = "Your_SSID"; //replace with your SSID
const char* password = "Your_Password"; //replace with your password
Creating an AsyncWebServer Object
The AsyncWebServer object will be used to set up the ESP8266 web server. We will pass the default HTTP port which is 80, as the input to the constructor. This will be the port where the server will listen to the requests.
AsyncWebServer server(80);
Defining an EventSource
Next, we will define an event source called ‘/events.’ You can use any appropriate name.
AsyncEventSource events("/events");
Adding delay
The web server will update sensor readings after every 30 seconds. We will incorporate this value in the ‘Delay’ variable in milliseconds.
unsigned long last_time = 0;
unsigned long Delay = 30000;
Creating BME280 Object
Additionally, we will also create an object of Adafruit_BME280 called ‘bme’ which we will use later on to initialize the sensor.
Adafruit_BME280 bme;
Sensor Data Variables
We will now create three variables to store the sensor readings individually. These will be called ‘temperature’, ‘humidity’, and ‘pressure.’
float temperature;
float humidity;
float pressure;
Initializing Sensor
The following function will initialize the BME280 sensor. If the connection between the module and the sensor is incorrect it will print that message on the serial monitor. This function will be called inside the setup() function.
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("BME280 sensor not connected properly!");
while (1);
}
}
Getting Sensor Readings (Obtain_SensorReadings())
Next, we will define a function in named Obtain_SensorReadings(). We will use the bme object on the following methods: readTemperature(), readHumidity() and readPressure() individually to access each reading. These sensor readings will be saved in the variables which we previously defined above: temperature, humidity and pressure.
void Obtain_SensorReadings(){
temperature = bme.readTemperature();
humidity = bme.readHumidity();
pressure = bme.readPressure()/ 100.0F;
}
Initializing WIFI connection (initWiFi())
In the following function, we will connect our ESP8266 board with the local network whose network credentials we already specified above using the WiFi.begin() function. After the connection will be established, the IP address of the module will get printed on the serial monitor. Through that IP address, we will be able to access our web page.
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();
Serial.println("IP Address: ");
Serial.println(WiFi.localIP());
}
Processor() function
Inside the processor() function, we will replace all the placeholders which we created on our HTML text with the current sensor readings. This will occur whenever the web page will be accessed and if the placeholder is found inside the HTML script. As you already know, we will update sensor readings on our web page after every 30 seconds. With the help of the processor() function, we will have a continuous display of readings until the new ones arrive and get displayed.
String processor(const String& var){
Obtain_SensorReadings();
if(var == "TEMPERATURE"){
return String(temperature);
}
else if(var == "HUMIDITY"){
return String(humidity);
}
else if(var == "PRESSURE"){
return String(pressure);
}
return String();
}
Building the Web Page (HTML, CSS and JavaScript)
We will create an index_html variable to store all the HTML, CSS and JavaScript text which will be required to build our web page. It is more convenient to have separate HTML, CSS and JavaScript files saved on the ESP8266 SPIFFS. All of these three files will later be linked together. For this article, we will not be using this approach but include everything inside our Arduino sketch instead.
We will start with the title of the web page. The <title> tag will indicate the beginning of the title and the </title> tag will indicate the ending. In between these tags, we will specify “ESP8266 & BME280 Web server” which will be displayed in the browser’s title bar.
<title>ESP8266 & BME280 SSE Web server</title>
Next, we will create a meta tag to make sure our web server is available for all browsers e.g., smartphones, laptops, computers etc.
<meta name="viewport" content="width=device-width, initial-scale=1">
CSS styling
Inside the index_html variable we have the <style> </style> tags which mark the beginning and end of the CSS styling file.
We will set the display text to font type Times New Roman and align it in the centre of the webpage. For all the different texts and the boxes, the font size, font type, colour, positioning and everything relating to the overall visuals of the web page will be is specified. This section of code shows the CSS styling which we will incorporate in our web page.
<style>
html {font-family: Times New Roman; display: inline-block; text-align: center;}
p { font-size: 1.2rem;}
body { margin: 0;}
.topnav { overflow: hidden; background-color: #381d75; color: white; font-size: 1rem; }
.content { padding: 20px; }
.box { background-color: #e6eef2; outline: 1px solid;}
.boxes { 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 Body
The next step will be to define the HTML web page body. This will go inside the <body></body> tags which mark the beginning and the ending of the script. This part will include the heading of the web page and the buttons. We will include the heading of our webpage inside the <h1></h1> tags and it will be the same as that of the web browser title.
<h1>ESP8266 & BME280 SSE Web server</h1>
Displaying Sensor Readings
Next, we will include several paragraphs to display texts and boxes for temperature, pressure and humidity readings.
Firstly, we will display the temperature readings in a box. We will specify the name, icon and colour in the first paragraph. The name of the first box will be ‘Temperature’ and an icon of a thermometer will be displayed beside it. We used the icons from Font Awesome Icons website. You can use any appropriate icon of your choice. In the next paragraph, we used a placeholder for the temperature reading inside the %% signs like %TEMPERATURE%. The processor() function will handle the task of replacing the placeholder with the current sensor reading.
Also, JavaScript will be in charge of handling the received data so that the reading will be updated correctly. This will be accomplished by using an id (<span id=”temp”>) which we will reference in our script.
<p><i class="fas fa-thermometer-three-quarters" style="color:#15ab3a;"></i> Temperature</p><p><span class="reading"><span id="temp">%TEMPERATURE%</span> °C</span></p>
Secondly, we will display the pressure readings in the second box. We will specify the name, icon and colour in the paragraph. The name of the second box will be ‘Pressure’ and an icon will also be displayed beside it. In the next paragraph, we used a placeholder for the pressure reading inside the %% signs like %PRESSURE%. The SI unit will also get displayed with the sensor reading.
Also, JavaScript will be in charge of handling the received data so that the reading will be updated correctly. This will be accomplished by using an id (<span id=”pres”>) which we will reference in our script.
<p><i class="fas fa-arrow-circle-down" style="color:#070807;"></i> Pressure</p><p><span class="reading"><span id="pres">%PRESSURE%</span> hPa</span></p>
Likewise, the same steps will be followed for the humidity readings in the third box.
<p><i class="fas fa-tint" style="color:#8ccbed;"></i> Humidity</p><p><span class="reading"><span id="hum">%HUMIDITY%</span> %</span></p>
Thus, whenever the client will receive a new event with the updated readings, the HTML elements with their respective ids ‘temp’, ‘hum’ and ‘pres’ will get updated to newer values.
JavaScript (Receiving and handling SSE events)
Inside the tags <script></script>, we will include the JavaScript to handle the SSE protocol. Its main aim will be to start the EventSource connection with the server and to handle the events which will be received from the server. We will mainly handle four kinds of occurrences:
- Connection
- Disconnection
- Messages sent by the server
- events
Firstly, we will define an event source and pass the /events URL inside it. This URL will send the updates to our web page.
if (!!window.EventSource) {
var source = new EventSource('/events');
Then, we will use the addEventListener() function to listen to incoming events. It will have two arguments. The first will be the event name and the second will be function(e).
The following are listeners for connection, disconnection and incoming messages from the server. We used the default event listeners in this case which were listed in the Documentation.
source.addEventListener('open', function(e) {
console.log("Events got Connected!");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events got Disconnected!");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
Next, we will include listeners for temperature, pressure and humidity. As you already know, we used the Obtain_SensorReadings() function to save the readings obtained from the BME280 sensor in their respective variables temperature, pressure and humidity. We will use the same variables here as well to listen to the incoming events.
Firstly, we will look at how event listener for temperature works. After every 30 seconds (Delay) the ESP8266 will receive new sensor readings from BME280. This event (temperature) will be sent to the web server. When the client will receive the particular event, it will print the reading on the browser console. Additionally, the new reading will also get saved to the element with the HTML id ‘temp.’
source.addEventListener('temperature', function(e) {
console.log("temperature", e.data);
document.getElementById("temp").innerHTML = e.data;
}, false);
Likewise, the event listener will work in a similar way for pressure and humidity as well. In this case we will use their respective variables and HTML ids like ‘pressure’ and ‘humidity’ and ‘pres’ and ‘hum’ respectively.
source.addEventListener('pressure', function(e) {
console.log("pressure", e.data);
document.getElementById("pres").innerHTML = e.data;
}, false);
source.addEventListener('humidity', function(e) {
console.log("humidity", e.data);
document.getElementById("hum").innerHTML = e.data;
}, false);
setup() function
Inside the setup() function, we will open a serial connection at a baud rate of 115200. Moreover, we will call the relevant functions to initialize the WIFI connection and the BME280 sensor.
Serial.begin(115200);
initWiFi();
initBME();
Handling Requests
We will configure the root / URL where our server will listen to HTTP GET requests.
The handling function will respond to the client by using the send_P() method on the request object. This method will take in four parameters. The first is 200 which is the HTTP status code for ‘ok’. The second is “text/html” which will correspond to the content type of the response. The third input is the text saved on the index_html variable. Finally, the last parameter is the processor function which will be sent as the response.
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
The event source will be set up on our ESP8266 board thorough the following lines of code:
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
client->send("hi!", NULL, millis(), 10000);
});
server.addHandler(&events);
To start the server, we will call the begin() on our server object.
server.begin();
loop() function
In the loop() function we will check if 30 seconds have passed since the sensor readings were updated. Then we will call the Obtain_SensorReadings() function. On the serial monitor, these current sensor readings will get displayed with their units.
Obtain_SensorReadings();
Serial.printf("Temperature = %.2f ºC \n", temperature);
Serial.printf("Humidity = %.2f percent \n", humidity);
Serial.printf("Pressure = %.2f hPa \n", pressure);
Serial.println();
Lastly, we will send all these updated sensor readings to the web browser. The web server will now get updated to new 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
Choose the correct board and COM port before uploading your code to the board.
Go to Tools > Board and select ESP8266 Module.
Next, go to Tools > Port and select the appropriate port through which your board is connected.
Click on the upload button to upload the code into the ESP8266 development board. After you have uploaded your code to the ESP8266 development board press its RST button.
In your Arduino IDE, open up the serial monitor and you will be able to see the IP address of your ESP8266 module. Copy that address into a web browser and press enter. The web server will look something like this:
You can view all three sensor readings After every 30 seconds the sensor readings will get updated on both the serial monitor and the web page.
Moreover, press Ctrl+Shift+J to open your browser console. You will be able to view the client receiving all the different events.
Conclusion
In conclusion, we were able to learn about server-sent events and built a web server using it. It helped us in continuously updating the sensor readings to newer ones without the hassle of adding further requests in the program code.
If you finds this ESP8266 NodeMCU web server project interesting, you may also like to read:
- Create Simple ESP8266 NodeMCU Web server in Arduino IDE
- ESP32/ESP8266 HTTP Authentication Web Server
- ESP8266 NodeMCU Asynchronous Web Server using Arduino IDE
- ESP8266 NodeMCU WebSocket Server using Arduino IDE
- ESP32/ESP8266 Web Server to Control Outputs with a Timer (Pulse Width)
- ESP8266 NodeMCU Web Server using LittleFS