Homeyduino BME 680 BSEC library

Okey, this is a bit complicated, but still. If you want to build an indoor air quality sensor and measure VOC, the BME680 sensor (Gas Sensor BME680 | Bosch Sensortec) is probably the way to go. However the Bosch BSEC library which converts the gas resistance value to an IAQ value is a bit complicated. I am really a beginner coding Arduino code but still managed it, so here’s the guide to it, if you want to do it yourself. See also this forum post if you want to get inspired how to do it with an enclosure and TFT display HomeyDuino CO2 sensor - #8 by Lars_Machiels.

The output values from the sensor is temperature, pressure, humidity, IAQ, staticIAQ, and two calculated values breathVOC and eCO2 in ppm, which should work as human detectors. However I haven’t managed to use custom capabilities in Homeyduino, so in my code I used measure_rain for sIAQ and measure_CO for breathVOC, hehe. However, since both these values are calculated it is probably meaningless to display all those values.

  • Static IAQ:

    • The main difference between IAQ and static IAQ (sIAQ) relies in the scaling factor calculated based on the recent sensor history. The sIAQ output has been optimized for stationary applications (e.g. fixed indoor devices) whereas the IAQ output is ideal for mobile application (e.g. carry-on devices).
  • bVOCeq estimate:

    • The breath VOC equivalent output (bVOCeq) estimates the total VOC concentration [ppm] in the environment. It is calculated based on the sIAQ output and derived from lab tests.
  • CO2eq estimate:

    • Estimates a CO2-equivalent (CO2eq) concentration [ppm] in the environment. It is also calculated based on the sIAQ output and derived from VOC measurements and correlation from field studies.

So before you start, I would recommend to connect the BME680 sensor to the Arduino and run some I2C scanner code, so you are sure it is connected to 0x76 for the BME680_I2C_ADDR_PRIMARY setting or 0x77 for BME680_I2C_ADDR_SECONDARY otherwise you just end up with error messages in the Serial monitor.

Then you have to change the code in platform.txt in C:\Users[username]\AppData\Local\Arduino15\packages\esp8266\hardware\2.6.2 according to this GitHub - boschsensortec/BSEC-Arduino-library: Arduino library for BSEC to simplify integration into compatible platforms. To report issues, go to https://community.bosch-sensortec.com/t5/Bosch-Sensortec-Community/ct-p/bst_community (you have to change this back when you are done with this project/sensor).

And in eagle.app.v6.common.ld.h typically found in {YourESP8266PPackageDirectory}\tools\sdk\ld (same root folder) according to this GitHub - boschsensortec/BSEC-Arduino-library: Arduino library for BSEC to simplify integration into compatible platforms. To report issues, go to https://community.bosch-sensortec.com/t5/Bosch-Sensortec-Community/ct-p/bst_community

Then add this library in Arduino https://github.com/BoschSensortec/BSEC-Arduino-library/archive/master.zip

And upload this code to your esp8266:

#include "bsec.h"
#include <Homey.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>

// Helper functions declarations
void checkIaqSensorStatus(void);
void errLeds(void);

// Create an object of the class Bsec
Bsec iaqSensor;

String output;

// Entry point for the example

unsigned long previousMillis = 0;
const unsigned long interval = 300000; //Interval in milliseconds

void wifi() {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.begin("SSID", "password");
    uint8_t timeout = 30;
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
      if (timeout<1) break;
    }
    if (WiFi.status() == WL_CONNECTED) {
      //Print IP address
      Serial.print("Connected to WiFi! (");
      Serial.print(WiFi.localIP());
      Serial.println(")");
    }
  }
}


void setup(void)
{
  Serial.begin(115200);
  Wire.begin();
  Homey.begin("bme680nodemcu");
  Homey.setClass("sensor");
  Homey.addCapability("measure_temperature");
  Homey.addCapability("measure_humidity");
  Homey.addCapability("measure_pressure");
  Homey.addCapability("measure_co");
  Homey.addCapability("measure_co2");
  Homey.addCapability("measure_rain");

  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();

  // Print the header
  output = "Timestamp [ms], raw temperature [°C], pressure [hPa], raw relative humidity [%], gas [Ohm], IAQ, IAQ accuracy, temperature [°C], relative humidity [%], Static IAQ, CO2 equivalent, breath VOC equivalent";
  Serial.println(output);
}

// Function that is looped forever
void loop(void)
{
  wifi();
  Homey.loop();
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
    updateSensor();
    }
    

  
  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();
  }
 
}

void updateSensor() {  
  Homey.setCapabilityValue("measure_temperature", (float) iaqSensor.temperature );
  Homey.setCapabilityValue("measure_pressure", (float) iaqSensor.pressure/100 );
  Homey.setCapabilityValue("measure_humidity", (float) iaqSensor.humidity );
  Homey.setCapabilityValue("measure_co2", (int) iaqSensor.co2Equivalent );
  Homey.setCapabilityValue("measure_rain", (int) iaqSensor.staticIaq ); 
  Homey.setCapabilityValue("measure_co", (float) iaqSensor.breathVocEquivalent );
  Serial.println("Homey values sent");
}

// 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 errLeds(void)
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  digitalWrite(LED_BUILTIN, LOW);
  delay(100);
}
1 Like