Hello folks,
past couple of days I’ve been tweaking my sketch for my personal setup with bunch of Wemos D1 minis around the house and BME680 sensors attached to them.
As I wanted fully “official” BSEC and IAQ data to be gathered and calculated, I’ve been reusing some bosh codes and some other codes around. Which none of them was complete package.
I found quite harsh to get BSEC working from various sources over the internet, and mine is now working reliable so I wanted to do some share
needed libraries:
- ArduinoJson (5.x) - it’s not compatible with 6.x
- PubSubClient
- Uptime-Library
- bsec
(from bosh generic or bosh-arduino) , bosh is currently down, I can provide my own if wanted
note: there is a need to copy bsec_serialized_configurations_iaq.c and bsec_serialized_configurations_iaq.h and bsec_iaq.config to the /Arduino/libraries/bsec root as for some reason BOSH haven’t put them there … and therefore their examples aren’t working
note2: it will not run on devices with 1MB Flash, as bsec is pretty big, so something like wemos is needed, but basically any esp8266 based units with enough flash would work.
note3: sensor is connected to 0x77 (SECONDARY, eg. without SDO to GND) if you use 0x76 change it in main program
Sketch folder contains main file and settings.h file which is basically ontly thing you need to adjust for your needs. If you are going to use more than one (as me) unit, this is very handy as you are changing just one line to make another unit in another room
Following program reads and adjust sensor and IAQ accurancy itself and after reaching 3 (calibrated) state it will save itself to EEPROM so when you restart or whatever your Wemos, it will recalibrate after reboot quite quickly.
As well BSEC is recalibrating itself while running so each 6 hours (configurable) it will save new state etc.
Each 2 mins (configurable) it will report to the MQTT broker to the topic home/your-device-config-name/climate/BME680 data from sensor in JSON format, which then can be easily integrated to OH (which I’m not going to explain in here).
MQTT output looks like this:
home/kitchen/climate/BME680 {"Uptime":"1T4:43:59","Temperature":25.6,"Humidity":57.7,"Pressure":978.8,"Gas":185.03,"IAQ":62.7,"sIAQ":82.3,"IAQ_Accuracy":3,"CO2e":822.671,"bVOC":1.188,"rawTemperature":25.7,"rawHumidity":57.3}
Progam maintain wifi and mqtt connection, reconnects as needed etc. So it’s fully autonomous and quite reliable in terms of IAQ.
It’s meant to be indoor, but I guess it can be used as outdoor (not yet tested) as well.
From observation of running BME680 and BME250 2cm from each other, it’s likely needed to add 1.3f temp offset to BME680 as build in heat compensation is not enough.
Your results might vary, but I’ve ran 5 BME680 and 3 BME280 at same place at same time without any heat from other chips and resuls are almost exactly the same 1.3-1.5 degree
Feel free to comment / update / hint following sketch
#include <EEPROM.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "uptime.h"
#include "bsec.h"
#include "bsec_serialized_configurations_iaq.h"
#include "settings.h"
WiFiClient wifiClient;
PubSubClient client(wifiClient);
#define STATE_SAVE_PERIOD UINT32_C(iaq_save_period * 60 * 60 * 1000)
#define MQTT_REPORT UINT32_C(mqtt_publish_period * 60 * 1000)
char wifi_hostname[50];
char mqtt_topic[100];
// Helper functions declarations
void checkIaqSensorStatus(void);
void errLeds(void);
void loadState(void);
void updateState(void);
// Create an object of the class Bsec
Bsec iaqSensor;
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint16_t stateUpdateCounter = 0;
uint16_t stateUpdateMqtt = 0;
String output;
// Entry point for the example
void setup(void)
{
// uniq name, topic, etc
sprintf(wifi_hostname, "iot-wemos-%s",device);
sprintf(mqtt_topic, "home/%s/climate/BME680",device);
EEPROM.begin(BSEC_MAX_STATE_BLOB_SIZE + 1); // 1st address for the length
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
Wire.begin();
// set address
iaqSensor.begin(BME680_I2C_ADDR_SECONDARY, Wire);
// set offset
iaqSensor.setTemperatureOffset(temp_offset);
// useless info
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);
Serial.println("");
checkIaqSensorStatus();
// set proprietary BOSH config
iaqSensor.setConfig(bsec_config_iaq);
checkIaqSensorStatus();
// check EEPROM
loadState();
// subscribe to data from sensor
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 setup_wifi()
{
delay(50);
Serial.println();
Serial.print("Connecting to SSID: ");
Serial.print(ssid);
WiFi.hostname(wifi_hostname);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// disable AP mode which is for some unknown reason ON by default
WiFi.softAPdisconnect (true);
Serial.println("connected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
}
void reconnect()
{
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("MQTT connection...");
// Attempt to connect
if (client.connect(wifi_hostname))
{
Serial.println("connected!"); \
}
else
{
Serial.print("failed");
Serial.print(client.state());
Serial.println(", try again in 5 seconds");
}
// Wait 5 seconds before retrying
delay(5000);
}
}
// Function that is looped forever
void loop(void)
{
uptime::calculateUptime();
// wifi disconnected
if (WiFi.status() != WL_CONNECTED){
setup_wifi();
}
// MQTT disconnected
if (!client.connected()){
reconnect();
}
client.loop();
{
unsigned long time_trigger = millis();
if (iaqSensor.run()) { // If new data is available
output = "";
output += " Temperature: " + String(iaqSensor.rawTemperature) + " (" + String(iaqSensor.temperature) + ")";
output += ", Pressure: " + String(iaqSensor.pressure / 1e2);
output += ", Humidity: " + String(iaqSensor.rawHumidity) + " (" + String(iaqSensor.humidity) + ")";
output += ", Gas:" + String(iaqSensor.gasResistance / 1e3);
output += ", IAQ:" + String(iaqSensor.iaqEstimate) + " (" + String(iaqSensor.staticIaq) + ")";
output += " (" + String(iaqSensor.iaqAccuracy);
output += "), CO2e:" + String(iaqSensor.co2Equivalent);
output += ", bVOC:" + String(iaqSensor.breathVocEquivalent);
Serial.println(output);
updateState();
// MQTT publish every (n) minutes
if (stateUpdateMqtt * MQTT_REPORT < millis()) {
StaticJsonBuffer<300> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["Uptime"] = String(uptime::getDays()) + "T" + String(uptime::getHours()) + ":" + String(uptime::getMinutes()) + ":" + String(uptime::getSeconds());
root["Temperature"] = decimals(iaqSensor.temperature,1);
root["Humidity"] = decimals(iaqSensor.humidity,1);
root["Pressure"] = decimals((iaqSensor.pressure / 1e2),1);
root["Gas"] = decimals((iaqSensor.gasResistance / 1e3),2);
root["IAQ"] = decimals(iaqSensor.iaqEstimate,1);
root["sIAQ"] = decimals(iaqSensor.staticIaq,1);
root["IAQ_Accuracy"] = iaqSensor.iaqAccuracy;
root["CO2e"] = decimals(iaqSensor.co2Equivalent,3);
root["bVOC"] = decimals(iaqSensor.breathVocEquivalent,3);
root["rawTemperature"] = decimals(iaqSensor.rawTemperature,1);
root["rawHumidity"] = decimals(iaqSensor.rawHumidity,1);
// use it as JSON
char data[300];
root.printTo(data, root.measureLength() + 1);
// mqtt report
client.publish(mqtt_topic, data);
Serial.println("MQTT Publish");
stateUpdateMqtt++;
}
} else {
checkIaqSensorStatus();
}
}
}
// 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);
}
void loadState(void)
{
if (EEPROM.read(0) == BSEC_MAX_STATE_BLOB_SIZE) {
// Existing state in EEPROM
Serial.println("Reading state from EEPROM");
for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++) {
bsecState[i] = EEPROM.read(i + 1);
//Serial.println(bsecState[i], HEX);
}
iaqSensor.setState(bsecState);
checkIaqSensorStatus();
} else {
// Erase the EEPROM with zeroes
Serial.println("Erasing EEPROM");
for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE + 1; i++)
EEPROM.write(i, 0);
EEPROM.commit();
}
}
void updateState(void)
{
bool update = false;
if (stateUpdateCounter == 0) {
/* First state update when IAQ accuracy is >= 3 */
if (iaqSensor.iaqAccuracy >= 3) {
update = true;
stateUpdateCounter++;
}
} else {
/* Update every STATE_SAVE_PERIOD minutes */
if ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) {
update = true;
stateUpdateCounter++;
}
}
if (update) {
iaqSensor.getState(bsecState);
checkIaqSensorStatus();
Serial.println("Writing state to EEPROM");
for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE ; i++) {
EEPROM.write(i + 1, bsecState[i]);
//Serial.println(bsecState[i], HEX);
}
EEPROM.write(0, BSEC_MAX_STATE_BLOB_SIZE);
EEPROM.commit();
}
}
float decimals(float input, int decimals)
{
float scale=pow(10,decimals);
return round(input*scale)/scale;
}
and settings.h
/*
* Device Setting
*/
const char* device = "short-and-uniq-device-name"; // lowercase
// reporting periods
const int iaq_save_period = 6; // hours
const int mqtt_publish_period = 2; // minutes
// temperature offset
const float temp_offset(0.0f);
// wifi & mqtt
const char* ssid = "YOUR WIFI SSID";
const char* password = "YOUR WIFI PSWD";
const char* mqtt_server = "YOUR MQTT BROKER IP";
if you find it usefull, cheers!
K.