PCBWay Main Content Banner
ESP8266

Indoor Air Quality Monitoring with BME680 & ESP8266 Webserver

IAQ Monitor using BME680 BSEC Libray & ESP8266 on Webserver

Overview: BME680 IAQ Monitoring on webserver & OLED Display

In this project, we will make the Indoor Air Quality Monitoring with BME680 & ESP8266 Webserver and 0.96″ SSD1306 OLED Display. We will use the advanced BSEC library for BME680 and monitor its parameters including IAQ on OLED Display and ESP8266 web server simultaneously. 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 an IoT-based Indoor Air Quality Monitoring system on the Blynk IoT Cloud. But the drawback of that project was, we could not calculate the IAQ value i.e. Index of Air Quality, C02 equivalent, and percentage of (VOC) Volatile Organic Compound. we could only measure the environmental parameters like temperature, humidity, pressure, altitude, dew point, and Gas Resistance. 

Indoor Air Quality Monitoring with BME680 & ESP8266 Webserver

So in this tutorial, we will use a highly advanced BME680 Library called 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. SSD1306 0.96” OLED Display is used to monitor environmental data. We can also use ESP8266 Webserver to remotely monitor those values. The ESP8266 connects to your WiFi network & uploads the data regularly to the webserver. In this way, we can use BME680 Sensor with ESP8266 for monitoring Indoor Air Quality or IAQ from any device on your network.


Components Required

The list of components that we need for making 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
30.96" I2C OLED Display1https://amzn.to/32WRubSv
4Jumper Wires4https://amzn.to/3vw9ZAt
5Breadboard1https://amzn.to/2NP2UKL

BME680 Integrated 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. The BME680 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 Breakout Board

The sensor operates between 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-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 & OLED with ESP8266

For Indoor Air Quality Monitoring with BME680 & ESP8266 Webserver, The main component used is NodeMCU ESP8266 Development Board. You can also use any other ESP8266 board like Wemos D1 Mini. All these boards have ESP8266-12E Chip which has a built-in WiFi chip that can upload the data to the internet or server using a WiFi Network.

Interfacing BME680 with ESP8266 and OLED Display to monitor on Webserver

Here is a connection diagram between NodeMCU ESP8266, BME680 Sensor, and OLED Display. Connect the BME680 and OLED SCL & SDA Pin to D1 & D2 default I2C pin of NodeMCU Board. Supply the 3.3v power to both BME680 and OLED Display through 3.3V Pin of ESP8266 Board.

Connect the SDO to 3.3V. Connecting the SDO pin from the BME680 to the 3.3V is important because the original code was programmed 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 for monitoring IAQ value I recommend you to use PCB. You can simply download the Gerber file and order the PCB from https://www.PCBWay.com at a cheap price.

BME680, OLED and ESP8266 Custom PCB

PCBWay is quite professional in PCB manufacturing. 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 sign-up bonus. That means the new users can order 10 PCBs for free.


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. This library is conceptualized 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 modify some system files as per the instructions.

1. Go up to the following folder:

C:\Users\username\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.0

2. Open the file platform.txt.

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

# These can be overridden in platform.local.txtcompiler.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.

compiler.libraries.ldflags=

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

## 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 112 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 final step is to save the file. That’s it, now you can close it.


Source Code: Indoor Air Quality Monitoring BME680

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

Before uploading the code make sure to change your WiFi 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 <ESP8266WiFi.h>
#include <ESP8266WebServer.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 = "xxx-xxxx-xxx";  // Enter SSID here
const char* password = "xxx-xxx-xxx-xxx";  //Enter Password here

ESP8266WebServer 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 NodeMCU ESP8266 12-E 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 NodeMCU 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 webserver using ESP8266

The following parameters will be displayed 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)

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


Indoor Air Quality Monitoring using BME680 & ESP8266 on Webserver

The ESP8266 Board will try connecting to the Wifi Network using the given SSID & Password. The BME680 IAQ data is uploaded to the  ESP8266 after the interval of every second. The data can be monitored on a 9.96″ OLED Display and Serial Monitor as well as on smartphones or pc.

Indoor Air Quality Monitoring with BME680 & ESP8266 Webserver

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 ESP8266 and OLED display to monitor the indoor air quality as well as outdoor air quality. It’s a very simple and nice way of monitoring the environmental air quality in the Local Area Network.


Video Tutorial & Guide

Conclusion

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

Alsan Parajuli

I am a WordPress enthusiast, a hardworking and highly positive person. I always believes in practicality rather than theoretical knowledge. With my curiosity and fast learning skills, I managed to learn everything on my own. I love coding, editing, writing and rummaging around Internet. I am passionate about IoT Projects, Digital marketing, website designing, and reviewing. Moreover, I had been contributing to WordPress Biratnagar as an active member since 2018.

Related Articles

Leave a Reply

Back to top button