ESP32

BME680 Indoor Air Quality Monitoring with ESP32

IAQ Monitor using BME680 BSEC Library & ESP32 on Webserver

Overview: BME680 IAQ Monitoring on ESP32 Webserver

In this project, we will monitor the BME680 Indoor Air Quality Monitoring with the ESP32 webserver. We will use the advanced BSEC library for BME680 and monitor its parameters including IAQ on a webserver. So, that you can monitor the sensor values remotely from your local network. 

In our previous projects, we have interfaced Arduino with an integrated BME680 Environmental Sensor. Further, we made a BME680 based Indoor Air Quality Monitoring system with esp8266. This could calculate the IAQ value, i.e. Index of Air Quality, C02 equivalent, and percentage of (VOC) Volatile Organic Compound, and measure the environmental parameters like temperature, humidity, pressure, and altitude.

So in this tutorial, we will use a highly advanced BME680 Library called the BSEC library, which is now supported by NodeMCU ESP8266 and ESP32 Development Board. With the help of this library, we can measure the Temperature, Humidity, Pressure, value of IAQ, equivalent carbon dioxide, and Total volatile compound. ESP32 web server is used to monitor environmental data remotely. The ESP32 connects to your Local Wi-Fi network & uploads the data regularly to the webserver.


Components Required

The list of components we need to make this Local Area-based IAQ monitoring project can be found below. You can purchase all the components from the Amazon links.

S.NComponents NameQuantityGet Products from Amazon
1NodeMCU ESP8266 12E Board1https://amzn.to/3mTuL95
2BME680 Environmental Sensor1https://amzn.to/2R7LhXZ
3Jumper Wires4https://amzn.to/3vw9ZAt
4Breadboard1https://amzn.to/2NP2UKL

BME680 Environmental Sensor

The BME680 is a digital 4-in-1 sensor that can measure gas, humidity, pressure, and temperature measurement based on proven sensing principles. It is the upgraded version of its previous sensors like BMP180, BMP280, or BME280. The gas sensor on the BME680 can detect a wide variety of volatile organic compounds (VOCs) to monitor indoor air quality. The sensor has high linearity and high accuracy.

BME680 Environmental Sensor

The sensor operates from 1.7V to 3.6V. The standby power consumption of this module is 0.29 to 0.8 uA and while in sleep mode the power consumption is between 0.15 to 1 uA.

SensorAccuracyOperation Range
Temperature+/- 1.0ºC-40 to 85 ºC
Humidity+/- 3%0 to 100 %
Pressure+/- 1 hPa300 to 1100 hPa
Altitude+/- 1 M0 – 30,000ft

BME680 can measure the Air quality index (IAQ) from 0 to 500 PPM.

Air Quality Index

The default I2C address of the sensor is 0x76 but it can be changed to 0x77 simply by connecting SDO to 3.3v. To learn more about the BME680 Sensor, you can check BME680 Datasheet.


Circuit: Interfacing BME680 with ESP32

For BME680 Indoor Air Quality Monitoring with ESP32 webserver, the major component used is ESP32 Development Board and BME680. This board has a built-in Wi-Fi chip that can upload the data to the internet or server using a Wi-Fi Network.

Interfacing BME680 with ESP32

Here is a connection diagram between ESP32 and BME680 Sensor. Connect the BME680 SCL & SDA Pin to GPIO22  & GPIO21  default I2C pin of ESP32 Board. Supply the 3.3v power to VCC and SDO pin through 3.3V Pin of ESP32 Board.

This is important because we programmed the original code to use the alternative I2C address (0x77). You can access this I2C address from the BME680 sensor by connecting the SDO pin to the 3.3v.

You can try this connection on a breadboard or simply use a custom-designed PCB Board. I prefer a breadboard connection for testing the circuit. But, if you want to use this project to monitor IAQ value, I recommend you use PCB. You can simply download the Gerber file and order the PCB from https://www.NextPCB.com at a cheap price.

BME680 ESP32 PCB

NextPCB is the professional in PCB manufacturing Company. You can try their services at extremely low prices. Only $5 for 10 PCBs and $30 in total for 20 PCBs assembly. Besides this, the new members also get a $5 as a sign-up bonus. If you are a new user, you can order 10 PCBs for free using this platform.


Preparing Arduino IDE For BME680 BSEC Library

For calculating IAQ and other VOCs gas parameters, we use the BSEC Library for BME680. It is a complicated and advanced library, where BSEC means Bosch Sensortec Environment Cluster. They conceptualize this library to provide a higher-level processing signal. The library receives raw sensor values from the sensor API, then processes the BME680 signals to provide the requested output. Check the BSEC GitHub Repository for more details.

BSEC library is supported by a 32, 16 & 8-Bit controller. It doesn’t support most of the Arduino Boards but supports ARM Controllers, ESP8266, ESP32, MSP430 & Raspberry Pi.

BME680-BSEC-Library

You can install the library from the library manager as well.

Solving BSEC Library Compilation issue

Before using this library you need to change some system files as per the instructions.

1. Go up to the following folder:

C:\Users\username\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6

2. Open the file platform.txt.

3. Look for the following piece of code on line 58

# These can be overridden in platform.local.txt
compiler.c.extra_flags=compiler.c.elf.extra_flags=compiler.S.extra_flags=compiler.cpp.extra_flags=compiler.ar.extra_flags=compiler.objcopy.eep.extra_flags=compiler.elf2hex.extra_flags=

3. Now we need to add this little piece of code at the bottom on line no.67

compiler.libraries.ldflags=

4. Now look for the following piece of code on line 87:

## Combine gc-sections, archives, and objectsrecipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {build.exception_flags} -Wl,-Map "-Wl,{build.path}/{build.project_name}.map" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} -o "{build.path}/{build.project_name}.elf" -Wl,--start-group {object_files} "{archive_file_path}" {compiler.c.elf.libs} -Wl,--end-group  "-L{build.path}"

5. We need to add these lines to the above code:

{compiler.libraries.ldflags}

You may find difficulty in finding the exact lines. So I would suggest deleting the entire above code from line 87 and replace with the following code.

## Combine gc-sections, archives, and objectsrecipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {build.exception_flags} -Wl,-Map "-Wl,{build.path}/{build.project_name}.map" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} -o "{build.path}/{build.project_name}.elf" -Wl,--start-group {object_files} "{archive_file_path}" {compiler.c.elf.libs} {compiler.libraries.ldflags} -Wl,--end-group  "-L{build.path}"

6. The last step is to save the file. That’s it, now you can close it.


Source Code: BME680 Indoor Air Quality Monitoring

Here is BME680 Indoor Air Quality Monitoring with ESP32 Webserver program code to retrieve BME680 IAQ value & other gas parameters. You can use this code for Indoor Air Quality Monitoring ESP32 webserver.

Before uploading the code make sure to change your Wi-Fi Network credentials.

// Replace with your network credentials
const char* ssid = "xxxxxx-xxxxx";  // Enter SSID here
const char* password = "xxxx-xxxx-xxx-xxx";  //Enter Password here

Copy final program code for IAQ Monitoring on Webserver from below.

#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include "bsec.h"
#include <WiFi.h>
#include <WebServer.h>

#define SEALEVELPRESSURE_HPA (1013.25)
 
Bsec iaqSensor;
String output;
void checkIaqSensorStatus(void);
void errLeds(void);

float temperature;
float humidity;
float pressure;
float IAQ;
float carbon;
float VOC;
const char* IAQsts;
 
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
// Replace with your network credentials
const char* ssid = "The_IoT_Projects";  // Enter SSID here
const char* password = "Qwertyuiop";  //Enter Password here

WebServer server(80);
 
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(100);
  Wire.begin();
 
  Serial.println(F("Starting..."));
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
 
  Serial.println("OLED begun");
 
  display.display();
  delay(100);
  display.clearDisplay();
  display.display();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setRotation(0);
  Serial.println("Connecting to ");
  Serial.println(ssid);
  

  //Connect to your local wi-fi network
  WiFi.begin(ssid, password);

  //check wi-fi is connected to wi-fi network
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());
  

  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  
  Serial.println("HTTP server started");
   
  iaqSensor.begin(BME680_I2C_ADDR_SECONDARY, Wire);
  output = "\nBSEC library version " + String(iaqSensor.version.major) + "." + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix) + "." + String(iaqSensor.version.minor_bugfix);
 
  Serial.println(output);
  checkIaqSensorStatus();
  bsec_virtual_sensor_t sensorList[10] = {
    BSEC_OUTPUT_RAW_TEMPERATURE,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_RAW_HUMIDITY,
    BSEC_OUTPUT_RAW_GAS,
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_STATIC_IAQ,
    BSEC_OUTPUT_CO2_EQUIVALENT,
    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
  };
 
  iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP);
  checkIaqSensorStatus();
  }
 
void loop() {
  // put your main code here, to run repeatedly
  server.handleClient();
  display.setCursor(0,0);
  display.clearDisplay();
 
  unsigned long time_trigger = millis();
  if (iaqSensor.run()) { // If new data is available
    output = String(time_trigger);
    output += ", " + String(iaqSensor.rawTemperature);
    output += ", " + String(iaqSensor.pressure);
    output += ", " + String(iaqSensor.rawHumidity);
    output += ", " + String(iaqSensor.gasResistance);
    output += ", " + String(iaqSensor.iaq);
    output += ", " + String(iaqSensor.iaqAccuracy);
    output += ", " + String(iaqSensor.temperature);
    output += ", " + String(iaqSensor.humidity);
    output += ", " + String(iaqSensor.staticIaq);
    output += ", " + String(iaqSensor.co2Equivalent);
    output += ", " + String(iaqSensor.breathVocEquivalent);
    Serial.println(output);
  } else {
    checkIaqSensorStatus();
  }
 
  Serial.print("Temperature = "); 
  Serial.print(iaqSensor.temperature); 
  Serial.println(" *C");
  display.print("Temperature: "); 
  display.print(iaqSensor.temperature); 
  display.println(" *C");
 
  Serial.print("Pressure = "); 
  Serial.print(iaqSensor.pressure / 100.0); 
  Serial.println(" hPa");
  display.print("Pressure: "); 
  display.print(iaqSensor.pressure / 100); 
  display.println(" hPa");
 
  Serial.print("Humidity = "); 
  Serial.print(iaqSensor.humidity); 
  Serial.println(" %");
  display.print("Humidity: "); 
  display.print(iaqSensor.humidity); 
  display.println(" %");
 
  Serial.print("IAQ = "); 
  Serial.print(iaqSensor.staticIaq); 
  Serial.println(" PPM");
  display.print("IAQ: "); 
  display.print(iaqSensor.staticIaq); 
  display.println(" PPM");
 
  Serial.print("CO2 equiv = "); 
  Serial.print(iaqSensor.co2Equivalent); 
  Serial.println(" PPM");
  display.print("CO2eq: "); 
  display.print(iaqSensor.co2Equivalent); 
  display.println(" PPM");
 
  Serial.print("Breath VOC = "); 
  Serial.print(iaqSensor.breathVocEquivalent); 
  Serial.println(" PPM");
  display.print("VOC: "); 
  display.print(iaqSensor.breathVocEquivalent); 
  display.println(" PPM");

  if ((iaqSensor.staticIaq > 0)  && (iaqSensor.staticIaq  <= 50)) {
    IAQsts = "Good";
    Serial.print("IAQ: Good");
    display.print("IAQ: Good"); 
  }
  if ((iaqSensor.staticIaq > 51)  && (iaqSensor.staticIaq  <= 100)) {
    IAQsts = "Average";
    Serial.print("IAQ: Average");
    display.print("IAQ: Average");
  }
  if ((iaqSensor.staticIaq > 101)  && (iaqSensor.staticIaq  <= 150)) {
    IAQsts = "Little Bad";
    Serial.print("IAQ: Little Bad");
    display.print("IAQ: Little Bad");
  }
  if ((iaqSensor.staticIaq > 151)  && (iaqSensor.staticIaq  <= 200)) {
    IAQsts = "Bad";
    Serial.print("IAQ: Bad");
    display.print("IAQ: Bad");
  }
  if ((iaqSensor.staticIaq > 201)  && (iaqSensor.staticIaq  <= 300)) {
    IAQsts = "Worse";
    Serial.print("IAQ: Worse");
    display.print("IAQ: Worse");
  }
  if ((iaqSensor.staticIaq > 301)  && (iaqSensor.staticIaq  <= 500)) {
    IAQsts = "Very Bad";
    Serial.print("IAQ: Very Bad");
    display.print("IAQ: Very Bad");
  }
  if ((iaqSensor.staticIaq > 500)){
  IAQsts = "Very Very Bad";
  Serial.print("IAQ: Very Very Bad");
  display.print("IAQ: Very Very Bad");
  }
  Serial.println();
  display.display();
  delay(2000);
}
 
// Helper function definitions
void checkIaqSensorStatus(void)
{
  if (iaqSensor.status != BSEC_OK) {
    if (iaqSensor.status < BSEC_OK) {
      output = "BSEC error code : " + String(iaqSensor.status);
      Serial.println(output);
      for (;;)
        errLeds(); /* Halt in case of failure */
    } else {
      output = "BSEC warning code : " + String(iaqSensor.status);
      Serial.println(output);
    }
  }
 
  if (iaqSensor.bme680Status != BME680_OK) {
    if (iaqSensor.bme680Status < BME680_OK) {
      output = "BME680 error code : " + String(iaqSensor.bme680Status);
      Serial.println(output);
      for (;;)
        errLeds(); /* Halt in case of failure */
    } else {
      output = "BME680 warning code : " + String(iaqSensor.bme680Status);
      Serial.println(output);
    }
  }
}

void handle_OnConnect() {

  temperature = iaqSensor.temperature;
  humidity = iaqSensor.humidity;
  pressure = iaqSensor.pressure / 100.0;
  IAQ = iaqSensor.staticIaq;
  carbon = iaqSensor.co2Equivalent;
  VOC = iaqSensor.breathVocEquivalent;
  
  server.send(200, "text/html", SendHTML(temperature, humidity, pressure, IAQ, carbon, VOC, IAQsts));
}

void handle_NotFound() {
  server.send(404, "text/plain", "Not found");
}

String SendHTML(float temperature, float humidity, float pressure, float IAQ, float carbon, float VOC, const char* IAQsts) {
String html = "<!DOCTYPE html>";
html += "<html>";
html += "<head>";
html += "<title>BME680 Webserver</title>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
html += "<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.7.2/css/all.min.css'>";
html += "<link rel='stylesheet' type='text/css' href='styles.css'>";
html += "<style>";
html += "body { background-color: #fff; font-family: sans-serif; color: #333333; font: 12px Helvetica, sans-serif box-sizing: border-box;}";
html += "#page { margin: 18px; background-color: #fff;}";
html += ".container { height: inherit; padding-bottom: 18px;}";
html += ".header { padding: 18px;}";
html += ".header h1 { padding-bottom: 0.3em; color: #f4a201; font-size: 25px; font-weight: bold; font-family: Garmond, 'sans-serif'; text-align: center;}";
html += "h2 { padding-bottom: 0.2em; border-bottom: 1px solid #eee; margin: 2px; text-align: center;}";
html += ".box-full { padding: 18px; border 1px solid #ddd; border-radius: 1em 1em 1em 1em; box-shadow: 1px 7px 7px 1px rgba(0,0,0,0.4); background: #fff; margin: 18px; width: 300px;}";
html += "@media (max-width: 494px) { #page { width: inherit; margin: 5px auto; } #content { padding: 1px;} .box-full { margin: 8px 8px 12px 8px; padding: 10px; width: inherit;; float: none; } }";
html += "@media (min-width: 494px) and (max-width: 980px) { #page { width: 465px; margin 0 auto; } .box-full { width: 380px; } }";
html += "@media (min-width: 980px) { #page { width: 930px; margin: auto; } }";
html += ".sensor { margin: 10px 0px; font-size: 2.5rem;}";
html += ".sensor-labels { font-size: 1rem; vertical-align: middle; padding-bottom: 15px;}";
html += ".units { font-size: 1.2rem;}";
html += "hr { height: 1px; color: #eee; background-color: #eee; border: none;}";
html += "</style>";

//Ajax Code Start
  html += "<script>\n";
  html += "setInterval(loadDoc,1000);\n";
  html += "function loadDoc() {\n";
  html += "var xhttp = new XMLHttpRequest();\n";
  html += "xhttp.onreadystatechange = function() {\n";
  html += "if (this.readyState == 4 && this.status == 200) {\n";
  html += "document.body.innerHTML =this.responseText}\n";
  html += "};\n";
  html += "xhttp.open(\"GET\", \"/\", true);\n";
  html += "xhttp.send();\n";
  html += "}\n";
  html += "</script>\n";
  //Ajax Code END
  
html += "</head>";
html += "<body>";
html += "<div id='page'>";
html += "<div class='header'>";
html += "<h1>BME680 IAQ Monitoring System</h1>";
html += "</div>";
html += "<div id='content' align='center'>";
html += "<div class='box-full' align='left'>";
html += "<h2>";
html += "IAQ Status: ";
html += IAQsts;
html += "</h2>";
html += "<div class='sensors-container'>";

//For Temperature
html += "<div class='sensors'>";
html += "<p class='sensor'>";
html += "<i class='fas fa-thermometer-half' style='color:#0275d8'></i>";
html += "<span class='sensor-labels'> Temperature </span>";
html += temperature;
html += "<sup class='units'>°C</sup>";
html += "</p>";
html += "<hr>";
html += "</div>";

//For Humidity
html += "<p class='sensor'>";
html += "<i class='fas fa-tint' style='color:#0275d8'></i>";
html += "<span class='sensor-labels'> Humidity </span>";
html += humidity;
html += "<sup class='units'>%</sup>";
html += "</p>";
html += "<hr>";

//For Pressure
html += "<p class='sensor'>";
html += "<i class='fas fa-tachometer-alt' style='color:#ff0040'></i>";
html += "<span class='sensor-labels'> Pressure </span>";
html += pressure;
html += "<sup class='units'>hPa</sup>";
html += "</p>";
html += "<hr>";

//For VOC IAQ
html += "<div class='sensors'>";
html += "<p class='sensor'>";
html += "<i class='fab fa-cloudversify' style='color:#483d8b'></i>";
html += "<span class='sensor-labels'> IAQ </span>";
html += IAQ;
html += "<sup class='units'>PPM</sup>";
html += "</p>";
html += "<hr>";

//For C02 Equivalent
html += "<p class='sensor'>";
html += "<i class='fas fa-smog' style='color:#35b22d'></i>";
html += "<span class='sensor-labels'> Co2 Eq. </span>";
html += carbon;
html += "<sup class='units'>PPM</sup>";
html += "</p>";
html += "<hr>";

//For Breath VOC
html += "<p class='sensor'>";
html += "<i class='fas fa-wind' style='color:#0275d8'></i>";
html += "<span class='sensor-labels'> Breath VOC </span>";
html += VOC;
html += "<sup class='units'>PPM</sup>";
html += "</p>";


html += "</div>";
html += "</div>";
html += "</div>";
html += "</div>";
html += "</div>";
html += "</body>";
html += "</html>";
return html;
}
 
void errLeds(void)
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  digitalWrite(LED_BUILTIN, LOW);
  delay(100);
}

Now, go to the tools menu and select the ESP32 Development Board from the list. Then select the COM port & finally hit the upload button to upload the code.

Open the Serial Monitor now. You will find the IP address of your ESP32 Board. Simply copy the IP address and paste it into a web browser. A beautiful web page loads and you will be able to monitor the BME680 sensor readings remotely from the same network.

BME680 Indoor Air Quality Monitoring with ESP32

It will display the following parameters on the webserver in every second:

  • IAQ Status: Depending upon static IAQ Index value (see the IAQ Index and Air Quality Table above)
  • Temperature in °C
  • Relative Humidity in %
  • Pressure in hPa
  • IAQ index in PPM (begins at 25 after startup and takes 15-20 minutes to get stable readings).
  • CO2 equivalent (estimation of the CO2 equivalent in ppm in the environment)
  • Breath VOC equivalent output (estimates the total VOC concentration in ppm in the environment)
Indoor Air Quality Monitoring with BME680 & ESP8266 Webserver

After the BME680 sensor reading gets stable, you can check the correct value of IAQ, CO2 & VOC.


BME680 Indoor Air Quality Monitoring with ESP32

ESP32 Board will try connecting to the Wi-Fi Network using the given SSID & Password. BME680 IAQ data is uploaded to the ESP32 after the interval of every second. We can monitor the data on a Serial Monitor and on smartphones or PC.

BME680 webserver using ESP8266

The data changes whenever the sensor pushes some values. The beautiful widgets for pressure, temperature, humidity, IAQ, CO2 & VOC will appear here. This is how you can use BME680 with ESP32 to monitor the indoor air quality and outdoor air quality. It’s a very simple and nice way of monitoring the environmental air quality in the Local Area Network.


Conclusion

So, that’s all about BME680 Indoor Air Quality Monitoring with the ESP32 Webserver project. I hope the project was informative and helpful. If yes comment down below:

https://youtu.be/bINRrzQjVts

Related Articles

6 Comments

  1. Pingback: IoT Surveillance CCTV Camera using ESP32 CAM & Blynk |
  2. Hi Alsan, I have an error in the pressure measurement, real 1012hPa measured 936hPa. you use a variable
    #define SEALEVELPRESSURE_HPA (1013.25), which I don’t see where it applies.
    Also tell you that with the latest software versions it is not necessary to apply the corrections to the esp32 library for the compilation.

  3. Hello Alsan, I have discovered the problem of the pressure discrepancy, the weather forecasts give the equivalent pressure at sea level, the corrected pressure at a height H(m) is:
    P(hPa)=P(sealevel)(1-0.0000225577*H(m))^5.2259

  4. Hello Alsan,

    Great tutorial but I have a problem with my platform.txt file. Can you send me the platform.txt file?

    best regards

  5. I have been trying to solve this for some time now and cannot figure it out.

    I keep getting this error:
    Compilation error: ‘class Bsec’ has no member named ‘status’; did you mean ‘bsecStatus’?

    And I have searched and tried things without a solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button