ESP8266, BME280, CCS811 Sensor Build

ChaseThe first sensor array will consist of an ESP8266 with a BME280 and a CCS811. As I described in AirPatrol the upstairs sensor array, called Chase, is a downscaled version of Marshall for added monitoring. It is the simplest of my three sensor arrays with fewest sensors. Therefore, it is also ideal as my first build.

Materials

Below are lists of materials and tools I used for the build. First a few words on my choise of the main components.

I don’t need the added capabilities of the ESP32 so the ESP8266 is chosen as the main board.

The BME280 I selected over the more popular DHT11 and DHT22 sensors as it has a better reputation regarding lifetime in humid environments. Additionally, the BME280 offers temperature, humidity, and pressure readings whereas the DHT22 only offers temperature and humidity.

The CCS811 should be able to measure Total Volatile Organic Compounds (TVOC) and estimated CO2 levels. And I already had it on stock.

Materials:

Tools:

  • Wire cutters.
  • Soldering iron.
  • Hot glue gun.

Optional:

Design

First things first – design! A few sketches on how the BME280 and the CCS811 should be wired to the ESP8266.

ESP8266, BME280 design

ESO8266, CCS811 design

That was pretty straight forward, but I’ll do a combined one anyway of how both sensors should be wired to the ESP8266 simultaneous.

ESP8266, BME280, CCS811 design

Hardware

I wired it all up on a breadboard based on the diagrams to get everything up and running there. All the software development was done while I had the hardware wired on the breadboard. Only when everything was running exactly like I wanted it I soldered everything. The software is described below.

ESP8266 breadboard wirering

I drilled a hole in a cheap electricity junction box, which I got from the local hardware store, to allow the sensors to “sniff” the environment. To prevent spiders or others to take residence in the box I hot glued some mesh over the hole.

I used some ribbon cables that was in my scrap heap – this is mostly for my own reference as you will probably not have the same cable available, but this is how the wires were connected.

Cable nomenclature

A 5V 600mA power supply was used and I drilled a small hole in the box for the cable. I tied a knot on the cable to ensure that the cable could not be pulled out of the box and I hot glued it as well. The wires were connected to Vin and a GND pin.

After soldering all the sensors and heat shrink tuning places in risk of shorting out I hot glued all the components into the box. The CCS811 closest the the opening as it needs better access to the outside environment to function.

ESP8266, BME280, CCS881 final assembly

Software

For the software I made a separate file for each sensor to isolate the implementations and to allow easier code sharing between sensor arrays. Both sensors has Adafruit drivers available making life a whole lot easier.

BME280 Software

The code here is pretty straight forward. Be aware of the I2C address of your particular sensor. That is set to 0x76 with bme.begin(0x76U). Pressure is less important to me than the other measurements so I’ll keep going if that one is missing. Oh yeah, and always check for NULL points – even in your own code.

  1. #include <Arduino.h>
  2. #include <Adafruit_Sensor.h>
  3. #include <Adafruit_BME280.h>
  4.  
  5. static Adafruit_BME280 bme;
  6.  
  7. /***************************************************/
  8. bool bme280_init(void) {
  9.   return bme.begin(0x76U);
  10. }
  11.  
  12. /***************************************************/
  13. bool bme280_read(float *temp, float *hum, float *pres) {
  14.   bool result = true;
  15.  
  16.   if ((NULL == temp) || (NULL == hum) || (NULL == pres)) {
  17.     result = false;
  18.   }
  19.  
  20.   if (true == result) {
  21.     *temp = bme.readTemperature();
  22.     *hum = bme.readHumidity();
  23.     *pres = bme.readPressure() / 100.0F;
  24.  
  25.     if (isnan(*hum) || isnan(*temp)) {
  26.       result = false;
  27.     }
  28.   }
  29.  
  30.   return result;
  31. }

CCS811 Software

The read frequency of my system is 60 seconds so I’ll setup the CCS811 sensor to this with ccs.setDriveMode(CCS811_DRIVE_MODE_60SEC). Also, that sensor will give more precise results if it is adjusted for temperature and humidity. So, if I have this data available I’ll provide it to the sensor.

Note that this sensor needs a 20 minute warm-up time before it starts giving measurements. This is also why I dropped putting the ESP8266 in Deep Sleep mode in between readings. The sensor would start a new warm-up after every wake up after a Deep Sleep resulting in data never being available from this sensor. This could be solved by not powering the sensor from the ESP8266. I might explore this further sometime in the future.

  1. #include <Arduino.h>
  2. #include <Adafruit_CCS811.h>
  3.  
  4. static Adafruit_CCS811 ccs;
  5.  
  6. /***************************************************/
  7. bool ccs811_init(void) {
  8.   bool result = true;
  9.  
  10.   if (ccs.begin()) {
  11.     ccs.setDriveMode(CCS811_DRIVE_MODE_60SEC);
  12.   } else {
  13.     result = false;
  14.   }
  15.  
  16.   return result;
  17. }
  18.  
  19. /***************************************************/
  20. bool ccs811_read(uint16_t* eCO2, uint16_t* tvoc, float temp, float hum) {
  21.   bool result = true;
  22.  
  23.   if ((NULL == eCO2) || (NULL == tvoc)) {
  24.     result = false;
  25.   }
  26.  
  27.   if (true == result) {
  28.     result = false;
  29.     if (!isnan(temp) && !isnan(hum)) {
  30.       ccs.setEnvironmentalData((uint8_t)hum, (double)temp);
  31.     }
  32.  
  33.     if (0U == ccs.readData()) {
  34.       uint16_t tempCo2 = ccs.geteCO2();
  35.       uint16_t tempTvoc = ccs.getTVOC();
  36.       if ((tempCo2 + tempTvoc) > 0U) {
  37.         *eCO2 = tempCo2;
  38.         *tvoc = tempTvoc;
  39.         result = true;
  40.       }
  41.     }
  42.   }
  43.  
  44.   return result;
  45. }

Publisher Software

I created a module for handling publishing of data which also handles WiFi connection. In the code below insert your own SSID and WiFi password. To connect to an InfluxDB like the one described in Collect, Present, Alert then make sure to change the IP address and port number to where your server is running.

Note that line two should be #include <WiFi.h> if running on an ESP32.

  1. #include <Arduino.h>
  2. #include <ESP8266WiFi.h>
  3. #include <WiFiUDP.h>
  4.  
  5. // WiFi credentials.
  6. WiFiUDP udp;
  7. const char* WIFI_SSID = "<YOUR SSID>";
  8. const char* WIFI_PASS = "<YOUR PASSWORD>";
  9.  
  10. // IP address and port of InfluxDB
  11. byte DB_HOST[] = {192U, 168U, 0U, 1U};
  12. int DB_DATA_PORT = 65000U;
  13.  
  14. /***************************************************/
  15. bool publisher_init(void) {
  16.   bool result = true;
  17.   uint8_t retries = 0U;
  18.  
  19.   WiFi.mode(WIFI_STA);
  20.   WiFi.disconnect();
  21.   delay(100U);
  22.   WiFi.begin(WIFI_SSID, WIFI_PASS);
  23.  
  24.   while ((WiFi.status() != WL_CONNECTED) && (retries < 10U)) {
  25.     result = false;
  26.     if (WiFi.status() == WL_CONNECT_FAILED) {
  27.       Serial.println("ERROR: Failed to connect to WIFI.");
  28.     }
  29.     retries++;
  30.     delay(2000U);
  31.   }
  32.  
  33.   if (WiFi.status() == WL_CONNECTED) {
  34.     result = true;
  35.     Serial.print("INFO: IP address: ");
  36.     Serial.println(WiFi.localIP());
  37.   }
  38.  
  39.   return result;
  40. }
  41.  
  42. /***************************************************/
  43. bool publisher_sendData(String data) {
  44.   bool result = true;
  45.   if (WiFi.status() != WL_CONNECTED) {
  46.     result = publisher_init();
  47.   }
  48.  
  49.   if (true == result) {
  50.     udp.beginPacket(DB_HOST, DB_DATA_PORT);
  51.     udp.print(data);
  52.     udp.endPacket();
  53.   }
  54.  
  55.   return result;
  56. }

main.cpp Software

The two sensor softwares just presented will be used for the main program. Measurements will be stored in an InfluxDB as described in Collect, Present, Alert. Therefore, the sensor reading as stored in a string called line as InfluxDB Line Protocol.

The format of the InfluxDB Line Protocol is in short:

  • The name of the measurement.
  • A tag which in this case is location as I’ll collect the same kind of measurements from different locations.
  • A field called value which holds the measured value.
  1. /***************************************************/
  2. /********************** SETUP **********************/
  3. /***************************************************/
  4. void setup()
  5. {
  6.   /* Setup serial communication. */
  7.   Serial.begin(9600U);
  8.   delay(1000U);
  9.  
  10.   /* Perform initializations. */
  11.   if (!publisher_init()) {
  12.     Serial.println("ALARM: Failed to start publisher - check WiFi.");
  13.     while (1);
  14.   }
  15.  
  16.   if (!bme280_init()) {
  17.     Serial.println("CRITICAL: Failed to start BME280 temp/hum/bar sensor - check wiring.");
  18.     while (1);
  19.   }
  20.  
  21.   if ( !ccs811_init() ) {
  22.     Serial.println("CRITICAL: Failed to start CCS811 CO2/TVOC sensor - check wiring.");
  23.     while(1);
  24.   }
  25. }
  26.  
  27. /***************************************************/
  28. /**********************  LOOP  *********************/
  29. /***************************************************/
  30. void loop()
  31. {
  32.   String line = "";
  33.  
  34.   float temp = NAN;
  35.   float hum = NAN;
  36.   float pres = NAN;
  37.   if (bme280_read(&temp, &hum, &pres)) {
  38.     line = String(line + "temperature,location=upstairs value=" + String(temp) + "\n");
  39.     line = String(line + "humidity,location=upstairs value=" + String(hum) + "\n");
  40.     line = String(line + "pressure,location=upstairs value=" + String(pres) + "\n");
  41.   }
  42.  
  43.   uint16_t eCO2 = 0U;
  44.   uint16_t tvoc = 0U;
  45.   if (ccs811_read(&eCO2, &tvoc, temp, hum)) {
  46.     line = String(line + "carbondioxid,location=upstairs value=" + String(eCO2) + "\n");
  47.     line = String(line + "tvoc,location=upstairs value=" + String(tvoc) + "\n");
  48.   }
  49.  
  50.   if (line != "") {
  51.     Serial.println(line);
  52.     publisher_sendData(line);
  53.   }
  54.  
  55.   // Now wait
  56.   delay(60000U);
  57. }

Conclusion

With the sensor in place and the software running my data started ticking is as expected. Then this was the result in Grafana after a bit of setup.

Grafana Dashboard

With regards to future expansions I might look into possible Deep Sleep options with the CCS811 sensor so I can save a bit of power.

ESP32 Bonus

If you’re using an ESP32 instead of an ESP8266 then these wiring diagram will work along with the same software as above.

ESP32, BME280 design

ESP32, CCS811 design

2 Replies to “ESP8266, BME280, CCS811 Sensor Build”

  1. Hi,
    thanks for the presentation. I am not sure if in this setup the temperature readings will be influenced by the heat produced by the boards themselves, since they do not go to deep sleep, and the housing is closed except for a single opening. I have to admit that I know nothing about the CCS811 and it’s power consumption, but the ESP alone will produces quite some heat if not put to deep sleep. And I have found articles about false readings of the BME280 caused already by its own heat dissipation when run permanently.

    1. Hi,

      You are probably right – but I had the setup running for weeks on a breadboard before assembly into the housing. I didn’t see any offset in temperature after housing was added. The CCS811 isn’t bad. I did a build with a MICS6814 and that was insane and offset the temperature by a good 5-8 degrees C: ESP32, PMS5003, BME280, MICS6814 Sensor Build

      The CCS811 needs constant power to give accurate readings, but I should look into modem sleep. That should reduce power consumption, and thus heat, and still keep the CCS811 powered. But the again I’m looking to replace the BME280 and CCS811 with a BME680.

Leave a Reply

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