BME680 Web Server with ESP32 ( Arduino IDE)

In this tutorial, we will learn to create a BME680 web server using ESP32 and Arduino IDE. The BME680 is used to measure ambient temperature, barometric pressure, relative humidity, and gas (VOC) or Indoor air quality (IAQ). We will learn how to display sensor values on a web page using ESP32. The sensor readings will update automatically on a web page. Because we will build a Server-Sent Events (SSE) server with an asynchronous web server library. The ESP Async Web Server library is pretty straightforward to build asynchronous web servers.

This weather station will show temperature, humidity, pressure, and IAQ readings on the web page. We will use Arduino IDE to build a responsive EP32 web server that can be accessed through any device which has a web browser, and the ESP32 should be connected to your local area network. That means the mobile or computer should be connected to the same network to which the ESP32 board is connected.

BME680 web server using Arduino IDE temperature pressure humidity gas

We have a similar guide for BME680 interfacing with ESP32:

Prerequisites

Before we start this lesson make sure you are familiar with and have the latest version of Arduino IDE installed and also have ESP32 add-on installed in Arduino IDE:

Install ESP32 add-on in Arduino IDE

ESP32 BME680 Web Server Introduction 

Have you ever seen a remote monitoring system where an HMI-based system displays remote sensor values? Did you ever try to make a web server that can control or display the values of different sensors on your local server?

If yes, then this tutorial is a step-by-step guide for getting started. We will learn how to interface the BME680 with an ESP32 and how to display temperature, humidity, pressure, and gas values on a web server.

  1. The client starts the server-sent events connection with ESP32 server as an HTTP request. This will cause the module to send events to the web page.
  2. ESP32 connected with BME680 receives sensor readings after every 3 seconds.
  3. These readings are marked as ‘events’ of the following names: ‘temperature’, ‘pressure’, humidity and ‘gas’.
  4. These events sent by ESP32 have event listeners associated with them on the client-side. All the updated readings are received by the client on the said events.
  5. As in the case with SSE, only the ESP32 SERVER will be able to send events to the webpage. The web page gets updated accordingly and the process continues.
bme680 server sent events web server arduino ide

BME680 Introduction

BME680 is a four-in-one low power-driven sensor, which has integrated temperature, pressure, humidity, and gas detection sensors. It runs on an operating voltage of 1.8-3.8V and communicates with other microcontrollers through I2C and SPI protocol. This sensor is used in areas such as tracking the quality of air, humidity indicators, weather trends, home automation, and controlling and GPS enhancements.

Operating Range and Accuracy

Some key features of BME680 include:

  • Temperature measurement: measures the ambient temperature with an accuracy of ±1.0°C and an operating range of -40 to 85 ºC.
  • Relative Humidity measurement: measures relative humidity with a fast response rate and an accuracy of ±3% and an operating range of 0-100%.
  • Pressure measurement: measures barometric pressure with ±1 hPa absolute accuracy and altitude with an accuracy of ±1 meter. The operating range of pressure ranges from 300-1100 hPa.
AccuracyOperating Range
Temperature±1.0°C-40 to 85 ºC
Humidity±3%0-100%
Pressure±1 hPa300-1100 hPa
  • Gas measurement: detects a wide range of gases including volatile organic compounds (VOCs) thus determining the indoor air quality.
  • Due to its compact size and low power operation, it is suitable in mobile applications, smartwatches, and navigation systems.

As compared to BME280, BME680 is a unique and updated sensor in the sense that it contains a small-sized MOX (metal oxide) sensor.

Gas Sensor

BME680 sensor can determine the number of pollutants/VOCs in the environment such as carbon monoxide, Ethane, Isoprene /2-methyl-1,3 Butadiene, Acetone, and Ethanol. The VOCs are detected by the adsorption of oxygen molecules onto the metal oxide layer. Its actual detection is done by the principle of changing the resistance of the MOX sensor. Whenever the MOX gets in contact with a pollutant in the air, the resistance of the sensor changes with the concentration of the pollutants present. This means that the higher number of pollutants in the air leads to significantly lower resistance of the sensor. Likewise, with the lower concentration of VOCs in the air, the resistance of the sensor is significantly higher.

bme680 gas sensor resistance output and working

Note: The BME680 gas sensor is not a CO2 or ethanol measurement sensor. It gets the relative idea of CO2 from VOC in the air But we can not use it for direct measurement of CO2.

Like all other gas sensors, BME680 also gives variable results each time. To ensure greater accuracy, we have to calibrate it against a known source and then plot its curve. As the sensitivity levels are not constant during the initial use of the sensor, it is initially recommended to run it for forty-eight hours which can later be tuned down to thirty minutes before each use.

BME680 Modules

Several types of BME680 sensors with different sizes and numbers of terminals are available in the market. But all modules provide data through SPI and I2C interfaces and we can use the same Arduino sketch to get sensor readings. Some of them are shown below:

different bme680 sensor modules
BME680 Sensor Modules

You can choose the module according to your convenience and the type of microcontroller you want to connect it with. In this article, we will use the BME680 sensor with 6 terminals, which is shown below. It has 4 SPI and two I2C terminals.

Pinout Diagram

The following figure shows the pinout of BME680 sensor:

bme680 pinout
BME680 Pinout
Pin NameDescription
VCCThis pin powers up the sensor.
GNDThis is the common ground pin for power and logic.
SCLWhen using I2C protocol this pin acts as SCL or serial clock pin and when using SPI protocol this pin acts as SCK or the serial pin for SPI. It is the input to the chip.
SDAWhen using I2C protocol this pin acts as SDA or Serial Data pin and when using SPI protocol this pin acts as SDI or Serial Data In also known as MOSI (‘Master out Slave in’). This is used as the I2C/SPI input to the sensor.
SDOThis is the SDO (Slave data out) /MISO (Master in slave out) pin for SPI communication. This pin is used as the SPI output from the sensor.
CSThis is the Chip Select pin used in SPI communication. Acts as an input to the sensor.

BME680 I2C and SPI Interface

In this section, we will see how to interface the BME680 sensor module with ESP32. As discussed earlier, the BME680 offers two interfaces such as I2C and SPI. We can use either SPI or I2C interface to connect a sensor with the ESP32. We will show the connections for both scenarios in the next section.

I2C Interface with ESP32

If you want to interface the BME680 sensor with ESP32 using the I2C interface, you should make the connections of the ESP32 board according to these SCL and SDA pins.

BME680ESP32
VCC VCC=3.3V
GNDGND
SCLGPIO22
SDAGPIO21

Connect the VCC pin of ESP32 with the 3.3V VCC pin of the module to power up. Both grounds of the two devices will be connected in common. The SCL pin of BME680 will be connected with the default SCL pin of ESP32. Likewise, the SDA pin will be connected with the default SDA pin of ESP32.

Note: If we use other GPIO pins of ESP32 for I2C communication, we can define other pins inside our Arduino sketch. We don’t have to use default I2C pins.

ESP32 I2C Pins

The I2C pins stated above are set in default. If we want to change the GPIO pins we have to set them in code. The diagrams below show the pinout for the ESP32.

ESP32 I2C Pins
ESP32 I2C Pins

More information on ESP32 pins is available here:

ESP32 GPIO Pins

SPI Interface with ESP32

If you want to interface the BME680 sensor with ESP32 using the SPI interface, you should make the connections of ESP32 with the sensor according to these MOSI, MISO, and SCK pins. Because SPI protocol uses MOSI/SDI, MISO/SDO, and SCK pins.

BME680ESP32
VCCVCC=3.3V
GNDGND
SCL (SCK for SPI)GPIO18
SDA (SDI / MOSI for SPI)GPIO23
SDO / MISOGPIO19
CSGPIO5

The VCC pin will be connected with the 3.3V from the ESP32 module to power up. Both the grounds of the two devices will be connected in common. The SCL pin of BME680 will be connected with the default SCK/CLK pin of ESP32. Likewise, the SDA pin will be connected with the default MOSI pin of ESP32.  The SDO pin will be connected with the default MISO pin of ESP32 and the CS pin of the sensor will also be connected with the default CS pin of ESP32.

Note: The above table lists the default SPI pins of ESP32. But if you want to use any other pins, you need to define them inside Arduino sketch

As you can see, we will use the default I2C and default SPI pins of the ESP32 module to connect with the respective pins of the sensor. If you want to change the pins according to your preference that can be handled in the program code.

Which Interface to use? 

So far we have discussed SPI and I2C interfaces of BME680 and learned to interface the sensor with ESP32 using both interfaces. But the question is which communication protocol should we use to get reading from BME680. As we have seen SPI requires four pins to communicate. On the contrary, I2C requires two pins. However, SPI is generally faster in speed as compared to I2C. Hence, it’s a tradeoff between speed and the number of pins. In short, it depends on your choice and application. We will use the I2C interface to get sensor readings with the default I2C pins of ESP32. 

MicroPython BME680 Sensor with ESP32 and ESP8266

Required Hardware:

We will need the following components to connect our ESP32 board with the BME680 sensor.

  1. ESP32
  2. BME680 Module
  3. Connecting Wires
  4. Breadboard
  5. 0.96 inch OLED Display

BME680 Connection Diagram with ESP32

Follow the schematic diagram given below to make connections of ESP32 with BME680 to interface through I2C communication.

bme680 with esp32 schematic diagram

Follow the schematic diagram given below to make connections of ESP32 with BME680 to interface through SPI communication:

BM680 with ESP32 SPI interface

Installing BME680 Library in Arduino

We will use Arduino IDE to program our ESP32 board with BME680. Make sure you have the ESP32 add-on already installed before proceeding further.

Arduino comes up with an in-built library for BME680 which we will initiate. To use the functions of the BME680 library we will also be installing the ‘Adafruit_Sensor Library.’ Follow the steps to ensure the successful installation of the libraries.

  • In your Arduino IDE, click on Sketch > Include Library > Manage Library

The Library manager window will open up. In the search bar type ‘adafruit bme680.’ Click on the install button as shown.

Installing BME680 library in Arduino IDE

Now, we will install Adafruit_Sensor Library. Again, click on Sketch > Include Library > Manage Library.

Type ‘adafruit unified sensor’ in the search bar and install the library which is shown in the blue box below.

Adafruit unified sensor library install

Installing ESPAsyncWebServer Libraries

The ESPAsyncWebServer library will help us in creating our web server easily. With this library, we will set up an asynchronous HTTP server. AsyncTCP is another library that we will be incorporating as it a dependency for the ESPAsyncWebServer library. This library will not be used directly inside our program code and only acts as the base for the first library.

Both of these libraries are not available in the Arduino library manager so we will have to download and load them in the IDE ourselves. We will use GitHub to download the respective libraries and then place them in the library folder of our Arduino IDE.
Click ESPAsyncWebServer library and AsyncTCP library to open the respective GitHub pages for the libraries.
The webpage when you open the ESPAsyncWeb Server link will look something like this.

Asynchronous Web Server ESPAsyncWebServer Library
Downloading ESPAsyncWebServer library

Click the Code button and go to the Download Zip option as highlighted in the figure. Your zip file will get downloaded to your computer right away. After the download is complete, extract the .zip file to the Arduino library folder. A similar process will apply to the installation of the AsyncTCP library as well. Make sure you rename the extracted files as ESPAsyncWebServer and AsyncTCP accordingly.

You can also go to Sketch > Include Library > Add .zip Library inside the IDE to add the libraries as well. Through this procedure now we will be able to use the functionalities of the libraries inside our Arduino IDE.

ESP32 BME680 Web Server Code

Copy the following sketch and replace the wifi credentials with your own.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
#include <WiFi.h>
#include "ESPAsyncWebServer.h"

// Change according to your network credentials
const char* ssid = "Enter your WiFi name here";
const char* password = "Enter your WiFi password";

//Uncomment if using SPI
/*#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/

Adafruit_BME680 bme; // I2C
//Adafruit_BME680 bme(BME_CS); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);

float temp;
float hum;
float pressure;
float gas_resistance;

AsyncWebServer server(80);
AsyncEventSource events("/events");

unsigned long lastTime = 0;  
unsigned long timerDelay = 3000;  // send readings timer

void BME680ReadingsSample(){
  // Tell BME680 to begin measurement.
  unsigned long endTime = bme.beginReading();
  if (endTime == 0) {
    Serial.println(F("Failed to begin reading :("));
    return;
  }
  if (!bme.endReading()) {
    Serial.println(F("Failed to complete reading :("));
    return;
  }
  temp = bme.temperature;
  pressure = bme.pressure / 100.0;
  hum = bme.humidity;
  gas_resistance = bme.gas_resistance / 1000.0;
}

String processor(const String& var){
  BME680ReadingsSample();
  //Serial.println(var);
  if(var == "TEMPERATURE"){
    return String(temp);
  }
  else if(var == "HUMIDITY"){
    return String(hum);
  }
  else if(var == "PRESSURE"){
    return String(pressure);
  }
 else if(var == "GAS"){
    return String(gas_resistance);
  }
}

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>BME680 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: #4B1D3F; color: white; font-size: 1.7rem; }
    .content { padding: 20px; }
    .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
    .cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
    .reading { font-size: 2.8rem; }
    .card.temperature { color: #0e7c7b; }
    .card.humidity { color: #17bebb; }
    .card.pressure { color: #3fca6b; }
    .card.gas { color: #d62246; }
  </style>
</head>
<body>
  <div class="topnav">
    <h3>BME680 WEB SERVER</h3>
  </div>
  <div class="content">
    <div class="cards">
      <div class="card temperature">
        <h4><i class="fas fa-thermometer-half"></i> TEMPERATURE</h4><p><span class="reading"><span id="temp">%TEMPERATURE%</span> &deg;C</span></p>
      </div>
      <div class="card humidity">
        <h4><i class="fas fa-tint"></i> HUMIDITY</h4><p><span class="reading"><span id="hum">%HUMIDITY%</span> &percnt;</span></p>
      </div>
      <div class="card pressure">
        <h4><i class="fas fa-angle-double-down"></i> PRESSURE</h4><p><span class="reading"><span id="pres">%PRESSURE%</span> hPa</span></p>
      </div>
      <div class="card gas">
        <h4><i class="fas fa-wind"></i> GAS</h4><p><span class="reading"><span id="gas">%GAS%</span> K&ohm;</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);
 
 source.addEventListener('gas', function(e) {
  console.log("gas", e.data);
  document.getElementById("gas").innerHTML = e.data;
 }, false);
}
</script>
</body>
</html>)rawliteral";

void setup() {
  Serial.begin(115200);

  // Set the device as a Station and Soft Access Point simultaneously
  WiFi.mode(WIFI_AP_STA);
  
  // Set device as a Wi-Fi Station
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Setting as a Wi-Fi Station..");
  }
  Serial.print("Station IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  // Init BME680 sensor
  if (!bme.begin()) {
    Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
    while (1);
  }
  // Set up oversampling and filter initialization
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320*C for 150 ms

  // 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) {
    BME680ReadingsSample();
    Serial.printf("Temperature Value = %.2f ºC \n", temp);
    Serial.printf("Humidity % = %.2f % \n", hum);
    Serial.printf("Pressure Value = %.2f hPa \n", pressure);
    Serial.printf("Gas Resistance = %.2f KOhm \n", gas_resistance);
    Serial.println();

    // Send Events to the Web Server with the Sensor Readings
    events.send("ping",NULL,millis());
    events.send(String(temp).c_str(),"temperature",millis());
    events.send(String(hum).c_str(),"humidity",millis());
    events.send(String(pressure).c_str(),"pressure",millis());
    events.send(String(gas_resistance).c_str(),"gas",millis());
    
    lastTime = millis();
  }
}

How does the Code Work?

We will include all the necessary libraries e.g., Wire, SPI, Adaruit_BME680, and Adafruit_Sensor. Additionally, the WiFi and the ESPAsyncWebServer libraries are included to connect to WiFi and create asynchronous web servers, respectively.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
#include <WiFi.h>
#include "ESPAsyncWebServer.h"

We will specify our network credentials through which ESP32 will connect to the WiFi router. The WiFi name and password will be saved in character variables named ssid and password. Make sure to replace the credentials with the ones for your router local network.

// Change according to your network credentials
const char* ssid = "Enter your WiFi name here";
const char* password = "Enter your WiFi password";

Then, we will define the Adafruit_BME680 object named bme by setting it on the default I2C GPIO pins of ESP32. If using SPI protocol instead, make sure to comment this line and uncomment the lines which are initializing the SPI pins.

//Uncomment if using SPI
/*#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/

Adafruit_BME680 bme; // I2C
//Adafruit_BME680 bme(BME_CS); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);

Next, we will declare variables to hold BME680 sensor readings such as temp, pressure, hum, and gas_Resistance. These are declared as floating types because we will be dealing with numerical values which could have floating decimal points. In other words, the values returned by the BME60 sensor are floating point values

float temp;
float hum;
float pressure;
float gas_resistance;

Here, we define two variables lastTime and timerDelay. We will include a delay of 3000ms or 3 seconds before updating the sensor readings to BME680 web server page. The updating time is stored in the variable ‘timerDelay.’

unsigned long lastTime = 0;  
unsigned long timerDelay = 3000;  // send readings timer

Create Server Sent Event Source

After that, we will create an object of the class AsyncWebServer. This will help us in the setup of the server routes. We have assigned it a port 80 which is the HTTP default port.

AsyncWebServer server(80);

As mentioned earlier, We will use Server-Sent Events known as SSE to update new sensor readings automatically from the BME680 web server and display them on the web page. We will create a new event source on /events. The Server-Sent Events (SSE) will update sensor reading automatically whenever a new reading is available without the need to refresh web browser page.

AsyncEventSource events("/events");

Get BME680 Sensor Readings

Next, we will obtain the sensor data for temperature, humidity, pressure, and gas by using the BME680ReadingsSample() function. If statements will be used to check for any potential failures in the setup which would be printed on the serial monitor. The sensor readings will then be saved in their respective variables named temp, pressure, hum, and gas_Resistance and accessed through the bme object.

void BME680ReadingsSample(){
  // Tell BME680 to begin measurement.
  unsigned long endTime = bme.beginReading();
  if (endTime == 0) {
    Serial.println(F("Failed to begin reading :("));
    return;
  }
  if (!bme.endReading()) {
    Serial.println(F("Failed to complete reading :("));
    return;
  }
  temp = bme.temperature;
  pressure = bme.pressure / 100.0;
  hum = bme.humidity;
  gas_resistance = bme.gas_resistance / 1000.0;
}

HTML Placeholders Replacement Function

We will also create a processor() function. This function will take care of the placeholders inside the HTML text by setting them up for the current sensor readings. Until the next set of readings come up, the current sensor readings will be displayed on the web page through this function.

String processor(const String& var){
  BME680ReadingsSample();
  //Serial.println(var);
  if(var == "TEMPERATURE"){
    return String(temp);
  }
  else if(var == "HUMIDITY"){
    return String(hum);
  }
  else if(var == "PRESSURE"){
    return String(pressure);
  }
 else if(var == "GAS"){
    return String(gas_resistance);
  }
}

HTML and CSS File

In this HTML document, we use cards, paragraphs, links, icons, headings, and title tags to create a web page. This web page displays temperature, humidity,  gas (VOC) or Indoor air quality (IAQ), and pressure readings of the BME680 sensor.

On a web page, we display icons of temperature, humidity, pressure, and gas by icons from the Font Awesome Icons website. To create a font, we will use the fontawesome.com website. Go to this link (fontawesome). You should include the following line in your HTML document to use icons from fontawesome:

 <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">

To create the web page, we will use index_html. To handle the events sent by the web browser client, we will use the following code for each sensor reading. Below is the example code for the line which displays the humidity readings.

<h4><i class="fas fa-tint"></i> HUMIDITY</h4><p><span class="reading"><span id="hum">%HUMIDITY%</span> &percnt;</span></p>

In this case, we are using the id hum to update new sensor readings regarding humidity. For other sensor variables, we are using the id pres, gas, and temp. This id describes which CSS style to apply to each reading and also JavaScript code uses this id to access and change elements related to that id.

Handle Server Events

To handle events, we will initialize a new object of EventSource called /events.

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

After initializing the server events, we will listen to all the messages received from the server through addEventListener(). In this block of code, we are creating addEventListener for open, error, and message. These event listeners are set in default as mentioned 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);

Now, create an addEventListener for the sensor readings. For example, for humidity, we will add the event listener with the name humidity. This will send this event to the web page whenever a new reading is accessible.

source.addEventListener('humidity', function(e)

Inside the function, the following lines send the new humidity readings event to the web browser client whenever the new value of humidity is available. This humidity reading is related to the id hum.

 {
  console.log("humidity", e.data);
  document.getElementById("hum").innerHTML = e.data;
 }, false);

Likewise, the same addEventListener() function will be defined for pressure, temperature, and gas with their respective IDs.

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

setup() Function

In the setup() function, we will first initiate the serial communication with the serial monitor with a baud rate of 115200 by using the begin() function. 

Serial.begin(115200);

By using the WiFi.begin() function, the ESP32 will connect to the local network with the given network credentials as parameters. This will then print the IP address which is assigned to ESP32 and we will use this IP address to access the web server later.


  // Set the device as a Station and Soft Access Point simultaneously
  WiFi.mode(WIFI_AP_STA);
  
  // Set device as a Wi-Fi Station
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Setting as a Wi-Fi Station..");
  }
  Serial.print("Station IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.println();

This block of code will set the oversampling for the readings and also initialize the gas heater and the low-pass IIR filter.

// Init BME680 sensor
  if (!bme.begin()) {
    Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
    while (1);
  }
  // Set up oversampling and filter initialization
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320*C for 150 ms

Handle Clients Requests

When a user accesses the web page with an ESP32 IP address on the root / URL ESP32 serve will respond with the data stored inside index_html. The processor will be one of the arguments so that new sensor readings are updated and replaced with placeholders.

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

Server Event Source

This block of code will create 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);

By using the server.begin() function, we will start the server.

server.begin();

In loop(), we will obtain the new readings from the sensor by using the getBME680Readings() function. These will then be displayed in the serial monitor through their respective variables.

BME680ReadingsSample();
Serial.printf("Temperature Value = %.2f ºC \n", temp);
Serial.printf("Humidity % = %.2f % \n", hum);
Serial.printf("Pressure Value = %.2f hPa \n", pressure);
Serial.printf("Gas Resistance = %.2f KOhm \n", gas_resistance);
Serial.println();

These updated readings will be sent to the web page through events.send

// Send Events to the Web Server with the Sensor Readings
    events.send("ping",NULL,millis());
    events.send(String(temp).c_str(),"temperature",millis());
    events.send(String(hum).c_str(),"humidity",millis());
    events.send(String(pressure).c_str(),"pressure",millis());
    events.send(String(gas_resistance).c_str(),"gas",millis());

Demonstration

Copy the above code to Arduino IDE. Choose the correct board and COM port before uploading your code to the ESP32 board. Go to Tools > Board and select ESP32 Dev Module.

Next, go to Tools > Port and select the appropriate port through which your board is connected.

Now click on the upload button to upload the code to ESP32. After that open the Arduino serial monitor and press the enable button on ESP32:

You will receive the IP address of the ESP32 development board printed on your serial monitor. Copy that address and paste it into a new browser window. Press Enter. The web server will display sensor readings for temperature, pressure, humidity, and gas which will be updated by new ones after every 3 seconds.

BME680 web server ESP32 Arduino demo desktop

If you access the web server from your mobile device, you will see the BME680 reading as follows:

BME680 web server ESP32 Arduino demo mobile

Conclusion

In conclusion, this tutorial has provided a comprehensive guide on how to create a BME680 web server using ESP32 and Arduino IDE. The BME680 sensor is capable of measuring ambient temperature, barometric pressure, relative humidity, and indoor air quality (IAQ) or gas (VOC) levels. By building a server-sent events (SSE) server with an asynchronous web server library, the sensor readings can be displayed and updated automatically on a web page. This project allows users to access and monitor the sensor data from any device with a web browser, as long as it is connected to the same local network as the ESP32 board.

You may like to read ESP32 web server tutorials with other sensors:

Leave a Comment