Article image

6 min Print version

DIY air quality sensor

I'm currently looking for a house to buy, so I needed an portable air quality sensor, that can tell my if something smells fishy.
Sure, I can use my nose, which is a good indicator, but sometime a sensor with logging would be nice in the days after.

So I hacked this project.

Components:

  • ESP32 (LOLIN Lite)
  • Salvaged LiPo battery from an old Laptop
  • 128x64 pixel monochrome OLED display
  • BM280 temperature/air pressure/humidity sensor
  • CCS 811 air quality sensor
  • Cheap Breadboard

The setup is quite easy, just wire power, ground, the 2 I2C wires to the display and the sensors. I used the default pins 19 (SCL) an 23(SDA) and put them on the bus bar on the bottom. Power and ground on the top bar.
With a bit of extra wires to connect some of the sides of the breadboard and bit of battery cabling, you'll get this:

Not perfect, but "good enough".

Why a ESP and not just a small Arduino?

Simple: Bluetooth, that's the logging part from the intro. Paired with my mobile phone and a Bluetooth serial app, I got my logging including timestamps a.s.o. When compared to the images taken, I can see each room with it's measured values. No need for manual notes.

Also the LOLIN module comes with all needed components to run on LiPo batteries including a way to charge them. No extra power components needed.

A few notes

The sensors might not be the best around, but they are cheap.
The VOC sensor has a small caveat, it needs to "burn in" (be powered on) for at least 48 hours to make accurate measures (according to the data sheet) and need to run for 20-30 minutes before the values are "correct".
For me this is no big deal, turning the device on when leaving the house is enough, the battery powerful enough to keep it running for probably days.

The code

#include "BluetoothSerial.h"
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_CCS811.h>
#include <Fonts/FreeSans9pt7b.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     -1
BluetoothSerial SerialBT;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_BME280 bme;
Adafruit_CCS811 ccs;

#define ICON_WIDTH 8
#define ICON_HEIGHT 16
static const unsigned char PROGMEM temp[] =
{ B00011100,
  B00100010,
  B00100011,
  B00100010,
  B00101011,
  B00101010,
  B00101011,
  B00101010,
  B00101010,
  B00101010,
  B01001001,
  B01011101,
  B01000001,
  B00111110,
  B00000000,
  B00000000};
  static const unsigned char PROGMEM height[] =
{ B01111110,
  B01000010,
  B01000010,
  B01111010,
  B01000010,
  B01000010,
  B01111010,
  B01000010,
  B01000010,
  B01111010,
  B01000010,
  B01000010,
  B01111010,
  B01000010,
  B01000010,
  B01111110};
  static const unsigned char PROGMEM humid[] =
{ B00000000,
  B00011000,
  B00100100,
  B00100100,
  B01000010,
  B01000010,
  B10000001,
  B10000001,
  B10100101,
  B10001001,
  B10010001,
  B10100101,
  B10000001,
  B01000010,
  B00111100,
  B00000000};

void setup() {
  Serial.begin(115200);
  SerialBT.begin("AQM"); //Bluetooth device name
 
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed"));
    while (1) delay(100);
  }
  display.clearDisplay();              // clear display
display.setTextSize(1);              // 2:1 pixel scale
  display.setFont(&FreeSans9pt7b);     // set font
  display.setTextColor(SSD1306_WHITE); // draw white text
  display.setCursor(0, 15);            // start at top-left corner
  if (!bme.begin(0x76, &Wire)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
    display.write("no BME280 sensor");
    display.display();
    while (1) delay(100);
  }
  if(!ccs.begin()){
    Serial.println("Failed to start CCS sensor! Please check your wiring.");
    display.write("no CCS811 sensor");
    display.display();
    while (1) delay(100);
  }
  ccs.setEnvironmentalData(bme.readHumidity(), bme.readTemperature());
  display.write("waiting for CCS811 sensor");
  Serial.println("Waiting for sensor");
  display.display();
  while(!ccs.available()){
    delay(100);
  }
  // draw initial icons and static info
  display.clearDisplay();
  display.drawBitmap(0, 0, temp, ICON_WIDTH, ICON_HEIGHT, 1);
  display.drawBitmap(60, 0, height, ICON_WIDTH, ICON_HEIGHT, 1);
  display.drawBitmap(0, 16, humid, ICON_WIDTH, ICON_HEIGHT, 1);
  display.drawCircle(48, 5, 2, SSD1306_WHITE);
  display.drawCircle(47, 21, 2, SSD1306_WHITE);
  display.drawCircle(53, 29, 2, SSD1306_WHITE);
  display.drawLine(47,28, 53,22, SSD1306_WHITE);
}

char line[128];       // line buffer
uint16_t eCO2 = 0;    // CO2 value in parts per million
uint16_t TVOCppb = 0; // RVOC in parts per billion

void loop() {
  uint8_t ccRead = ccs.readData(); // read CCS sensor
  if (!ccRead) {
    eCO2 = ccs.geteCO2();
    TVOCppb = ccs.getTVOC();
  }
  float temperature = bme.readTemperature();   // in °C
  float pressure = bme.readPressure() / 100.0; // in hPa / 1 milli Bar
  float humidity = bme.readHumidity();         // in %
 
  // send status to USB serial and bluetooth
  snprintf(line,127, "T: %3.1f°C P: %6.2fhPa H: %4.2f%% eCO2:%i ppm, tvoc:%i ppb\n", temperature, pressure, humidity, eCO2, TVOCppb);
  Serial.print(line);
  SerialBT.print(line);

  // draw to display
  // clear text / draw bar graph area
  display.fillRect(8, 0, 37, 32, SSD1306_BLACK);
  display.fillRect(8+64, 0, 56, 32, SSD1306_BLACK);
  display.fillRect(0, 32, 128, 32, SSD1306_BLACK);
  display.fillRect(64, 32, 64.0 * eCO2 / 8192, 16, SSD1306_WHITE);
  display.fillRect(64, 48, 64.0 * TVOCppb / 1187, 16, SSD1306_WHITE);
  // draw temperature
  display.setCursor(9, 15);
  snprintf(line,127, "%3.1f", temperature);
  display.write(line);
  // draw pressure
  snprintf(line,127, "%6.1f", pressure);
  display.setCursor(74, 15);
  display.write(line);
  // draw humidity
  display.setCursor(9, 31);
  snprintf(line,127, "%3.1f", humidity);
  display.write(line);
  // draw CO2 level
  display.setCursor(9, 46);
  snprintf(line,127, "%04i",eCO2);
  display.write(line);
  // draw TVOC
  display.setCursor(9, 61);
  snprintf(line,127, "%04i",TVOCppb);
  display.write(line);
  // update display
  display.display();
  // zzzZZZZzzz
  delay(10000);
}

I'm using a lot of already made libraries, available in the Arduino library:

  • Adafruit BME 280
  • Adafruit CCS811
  • Adafruit SSD1306

No need to reinvent the wheel.

Final words:

Feel free to copy and modify the source code, some credits would be nice.
And if someone has nice 8x16 icons (mono) for CO2 and VOC, send them my way.