Sauna BuddyJan R. Seyler
Created March 25, 2024 © GPL3+

Sauna Buddy

Your smart companion for the ideal sauna session - every time.

Intermediate3 hours14
Sauna Buddy

Things used in this project

Hardware components

Adafruit BME688
×1
XIAO ESP32C3
Seeed Studio XIAO ESP32C3
Micro controller
×1
Resistor 100k ohm
Resistor 100k ohm
×2
Raspberry Pi 3 Model B+
Raspberry Pi 3 Model B+
Used for recording raw data
×1

Software apps and online services

Bosch BME AI Studio
Software from Bosch Sensortec
bme68x-python-library
Bosch-BME68x-Library
Software library from Bosch Sensortec
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

ELEGOO NEPTUNE 4

Story

Read more

Custom parts and enclosures

Case

This file provides a CAD model of the case used in this project

Case Closure

This file provided a CAD model of the case closure used in this project

Code

Docker compose

YAML
Yaml file to create the needed container.
version: "3.8"

services:

  node-red:
    image: nodered/node-red:latest
    hostname: "x-nodered"
    environment:
      - TZ=Europe/Berlin
    ports:
      - 1880:1880
    volumes:
      - /NodeRed:/data
    networks:
      - iot-docker
    restart: unless-stopped
  
  hivemq:
    hostname: "x-hivemq"
    image: hivemq/hivemq4:latest
    ports:
      - 1883:1883
      - 8080:8080
    restart: unless-stopped
    networks:
      - iot-docker

  influxdb:
    image: influxdb:latest
    volumes:
      - ./influxDB:/var/lib/influxdb
    ports:
      - 8086:8086
    networks:
      - iot-docker  
    restart: unless-stopped
  
  grafana:
    image: grafana/grafana:latest
    hostname: "x-grafana"
    ports:
      - 3000:3000
    #environment:
      #- GF_INSTALL_PLUGINS=$GF_INSTALL_PLUGINS
    volumes:
      - ./Grafana:/var/lib/grafana
    networks:
      - iot-docker
    restart: unless-stopped

networks:
  iot-docker:

BME688_ESP32-C3 Sensor data to MQTT Broker

C/C++
Arduino code: Send the sensor data from the BME688 to the MQTT Broker
/*******************************************************************************
** Author:	Luka Kustura, Thilo Danner
** Date:	  22.03.2024												
** Purpose: Multivalue Sensor BME688 from Bosch Sensortec
**          Read out sensor data and send it via MQTT to Single Board Computer
*******************************************************************************/

 
#include <bsec2.h>
#include <WiFi.h>
#include <PubSubClient.h>


// Measuered by multimeter: 4,05 V
// Measured ADC: 2059 mV
// 4,05V / 2,059 V = 1,966974259349199 ==> 1,967
# define VoltageDivider 1.967

//#define uS_TO_S_FACTOR 1000000  /* Conversion factor for micro seconds to seconds */
//#define TIME_TO_SLEEP  120        /* Time ESP32 will go to sleep (in seconds) */

/* Macros used */
#define ERROR_DUR   1000
#define PRECISION   2
#define SIZE 16

#define JSON_BUFFER_SIZE 256
 
// Wifi Station Credentials
const char* ssid     = "Studienarbeit";
const char* password = "DHBW_2024";


IPAddress mqtt_server(192, 168, 12, 1);
const char* mqtt_username = "x";
const char* mqtt_password = "Budvar";
const char* clientID = "ESP32";
 
bool state = true;
bool changed = true;
 
void setup_wifi(void);
void checkBsecStatus(Bsec2 bsec);
void newDataCallback(const bme68xData data, const bsecOutputs outputs, Bsec2 bsec);
void reconnect(void);
void callback(char* topic, byte* payload, unsigned int length);
void send_data(char* topic, StringSumHelper data);

WiFiClient wifiClient;
PubSubClient client(wifiClient);

// Setup sensor...
Bsec2 envSensor;
 
typedef struct {
  int IAQ;
  int IAQstatic;
  int IAQaccuracy;
  float temperature;
  float pressure;
  float humidity;
  float co2equiv;
  float breathVOCequiv;
  float gas;
  float stabStatus;
  float runStatus;
} bme_data;
 
bme_data output_data;
 

 
void setup(void)
{

  // esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  //set the resolution to 12 bits (0-4096)
  analogReadResolution(12);

  /* Desired subscription list of BSEC2 outputs */
  bsecSensor sensorList[] = {
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_RAW_TEMPERATURE,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_STATIC_IAQ,                             /*!< Unscaled indoor-air-quality estimate */ 
    BSEC_OUTPUT_CO2_EQUIVALENT,                         /*!< CO2 equivalent estimate [ppm] */   
    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,                  /*!< Breath VOC concentration estimate [ppm] */    	
    BSEC_OUTPUT_RAW_HUMIDITY,
    BSEC_OUTPUT_RAW_GAS,
    BSEC_OUTPUT_STABILIZATION_STATUS,
    BSEC_OUTPUT_RUN_IN_STATUS
  };
 
    /* Initialize the communication interfaces */
    Serial.begin(115200);
    Wire.begin();
 
    /* Valid for boards with USB-COM. Wait until the port is open */
    while(!Serial)  {
      delay(10);
    }

    Serial.println("Serial connection successful\n");
    setup_wifi();
 
    client.setServer(mqtt_server, 1883);
    client.setCallback(callback);
    /* Initialize the library and interfaces */
    if (!envSensor.begin(BME68X_I2C_ADDR_HIGH, Wire)) {
      checkBsecStatus(envSensor);
      for(;;);
    }
    else {
      Serial.println("Initialize the library and interfaces successful\n");
    }
 
    /* Subsribe to the desired BSEC2 outputs */
    //#define BSEC_SAMPLE_RATE_ULP    (0.0033333f)    /*!< Sample rate in case of Ultra Low Power Mode */
    //#define BSEC_SAMPLE_RATE_CONT   (1.0f)          /*!< Sample rate in case of Continuous Mode */
    //#define BSEC_SAMPLE_RATE_LP     (0.33333f)      /*!< Sample rate in case of Low Power Mode */
    
    if (!envSensor.updateSubscription(sensorList, ARRAY_LEN(sensorList), BSEC_SAMPLE_RATE_ULP)) {
      checkBsecStatus(envSensor);
    }
    else {
      Serial.println("Subsribe to the desired BSEC2 outputs successful\n");
    }
 
    if (client.connect(clientID)) {
      if(!client.publish("airquality/general", "ESP connected")) Serial.println("Failed sending over mqtt");
    }
    /* Whenever new data is available call the newDataCallback function */
    envSensor.attachCallback(newDataCallback);
 
    Serial.println("BSEC library version " + \
            String(envSensor.version.major) + "." \
            + String(envSensor.version.minor) + "." \
            + String(envSensor.version.major_bugfix) + "." \
            + String(envSensor.version.minor_bugfix));
 
   }
 
void loop(void)
{
    /* Call the run function often so that the library can 
     * check if it is time to read new data from the sensor  
     * and process it.
     */
    if (!envSensor.run()) checkBsecStatus(envSensor);
    if (!client.connected()) {
      reconnect();
    }
    client.loop();
    if(state != changed){
      Serial.println("Sending data");
    
      send_data("airquality/temp", (output_data.temperature));
      send_data("airquality/press", (output_data.pressure));
      send_data("airquality/hum", (output_data.humidity));
      send_data("airquality/gas", (output_data.gas));

      send_data("airquality/IAQ", (output_data.IAQ));
      send_data("airquality/IAQaccuracy", (output_data.IAQaccuracy));
      send_data("airquality/IAQstatic", (output_data.IAQstatic));

      send_data("airquality/CO2equivalent", (output_data.co2equiv));
      send_data("airquality/breathVOCequivalent", (output_data.breathVOCequiv));

      send_data("airquality/stabStatus", (output_data.stabStatus));
      send_data("airquality/runStatus", (output_data.runStatus));

      // Read out battery level (Voltage) from voltage divider on GPIO2
      float batteryVoltageLevel = analogReadMilliVolts(2)*VoltageDivider;
      send_data("airquality/batteryLevel", (batteryVoltageLevel));

      // delay(1000);  // Delay 1s for WIFI Transimission (no packet loss)
      // esp_deep_sleep_start();

      changed = state;
    }
    
}
 
void newDataCallback(const bme68xData data, const bsecOutputs outputs, Bsec2 bsec)
{
    if (!outputs.nOutputs)  {
        return;
    }
    Serial.println("BSEC outputs:\n\ttimestamp = " + String((int) (outputs.output[0].time_stamp / INT64_C(1000000))));
    for (uint8_t i = 0; i < outputs.nOutputs; i++)  {
        const bsecData output  = outputs.output[i];
        switch (output.sensor_id) {
            case BSEC_OUTPUT_IAQ:
                Serial.println("\tIAQ = " + String(output.signal));
                output_data.IAQ = int(output.signal);
                Serial.println("\tiaq accuracy = " + String((int) output.accuracy));
                output_data.IAQaccuracy = int(output.accuracy);
                break;
            case BSEC_OUTPUT_RAW_TEMPERATURE:
                Serial.println("\ttemperature = " + String(output_data.temperature));
                output_data.temperature = float(output.signal);
                break;
            case BSEC_OUTPUT_RAW_PRESSURE:
                Serial.println("\tpressure = " + String(output_data.pressure));
                output_data.pressure = float(output.signal);
                break;
            case BSEC_OUTPUT_RAW_HUMIDITY:
                Serial.println("\thumidity = " + String(output_data.humidity));
                output_data.humidity = float(output.signal);
                break;
            case BSEC_OUTPUT_RAW_GAS:
                Serial.println("\tgas = " + String(output_data.gas));
                output_data.gas = float(output.signal);
                break;
            case BSEC_OUTPUT_STABILIZATION_STATUS:
                Serial.println("\tstabilization status = " + String(output.signal));
                output_data.stabStatus = float(output.signal);
                break;
            case BSEC_OUTPUT_RUN_IN_STATUS:
                Serial.println("\trun in status = " + String(output.signal));
                output_data.runStatus = float(output.signal);
                break;
            case BSEC_OUTPUT_STATIC_IAQ:
                Serial.println("\tstatic IAQ = " + String(output.signal));
                output_data.IAQstatic = float(output.signal);
                break;
            case BSEC_OUTPUT_CO2_EQUIVALENT:
                Serial.println("\tCO2 equivalent [ppm] = " + String(output.signal));
                output_data.co2equiv = float(output.signal);
                break;
            case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
                Serial.println("\tBreath VOC equivalent [ppm] = " + String(output.signal));
                output_data.breathVOCequiv = float(output.signal);
                break;
            default:
                break;
        }
    }
    state = !state;
}
 
void checkBsecStatus(Bsec2 bsec)
{
    if (bsec.status < BSEC_OK)  {
        Serial.println("BSEC error code: " + String(bsec.status));
    }
    else if (bsec.status > BSEC_OK) {
        Serial.println("BSEC warning code: " + String(bsec.status));
    }
    if (bsec.sensor.status < BME68X_OK) {
        Serial.println("BME68X error code: " + String(bsec.sensor.status));
    }
    else if (bsec.sensor.status > BME68X_OK)  {
        Serial.println("BME68X warning code: " + String(bsec.sensor.status));
    }
}
 
void setup_wifi(){
  /*
   * From https://blog.byschiller.de/tutorials/esp32/mqtt-getting-started
   */
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
 
  // connection to WIFi Network 
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }
 
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println("MAC address: ");
  Serial.println(WiFi.macAddress());
}
 
void callback(char* topic, byte* payload, unsigned int length) {
    /*
     * ESP should only publish, not receive
     */
}
 
void reconnect() {
  /* Loop until we're reconnected */
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("arduinoClient")) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}


void send_data(const char* topic, float output_data)  {
  char buffer[SIZE+1];
  sprintf(buffer, "%f", output_data);
  if (!client.publish(topic, (buffer))) {
    Serial.println("Failed sending over mqtt");
  }
}

Bash script for starting data record

SH
#!bin/bash

cd ~
cd bme68x-python-library/tools/bmerawdata

lxterminal -e python3 bmerawdata.py

Deploy AI model to the µC

C/C++
/**
 * Copyright (C) 2021 Bosch Sensortec GmbH
 *
 * SPDX-License-Identifier: BSD-3-Clause
 * 
 */

/*******************************************************************************
** Author:	Luka Kustura, Thilo Danner
** Date:	  22.03.2024												
** Purpose: Deploy the neuronal network - Sensor BME688 from Bosch Sensortec
**          Differ between normal air and coffee beans
*******************************************************************************/


/**
 * basic_config_state.ino sketch :
 * This is an example for integration of BSEC2x library using configuration setting and has been 
 * tested with Adafruit ESP8266 Board
 * 
 * For quick integration test, example code can be used with configuration file under folder
 * Bosch_BSEC2_Library/src/config/FieldAir_HandSanitizer (Configuration file added as simple
 * code example for integration but not optimized on classification performance)
 * Config string for H2S and NonH2S target classes is also kept for reference (Suitable for
 * lab-based characterization of the sensor)
 */


#include <bsec2.h>
#define CLASSIFICATION	1
/* Note : 
          For the classification output from BSEC algorithm set OUTPUT_MODE macro to CLASSIFICATION.
*/
#define OUTPUT_MODE CLASSIFICATION

const uint8_t bsec_config[2063] = {
  2,0,5,2,189,1,0,0,0,0,0,0,247,7,0,0,176,0,1,0,0,168,19,73,64,49,119,76,0,0,97,69,0,0,97,69,137,65,0,191,205,204,204,
  190,0,0,64,191,225,122,148,190,10,0,3,0,0,0,96,64,23,183,209,56,0,0,0,0,0,0,0,0,0,0,0,0,205,204,204,189,0,0,0,0,0,0,0,
  0,0,0,128,63,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,128,63,0,0,0,0,0,0,0,0,0,0,128,63,0,0,0,0,0,0,
  128,63,82,73,157,188,95,41,203,61,118,224,108,63,155,230,125,63,191,14,124,63,0,0,160,65,0,0,32,66,0,0,160,65,0,0,32,
  66,0,0,32,66,0,0,160,65,0,0,32,66,0,0,160,65,8,0,2,0,0,0,72,66,16,0,3,0,10,215,163,60,10,215,35,59,10,215,35,59,13,0,
  5,0,0,0,0,0,100,254,131,137,87,88,0,9,0,7,240,150,61,0,0,0,0,0,0,0,0,28,124,225,61,52,128,215,63,0,0,160,64,0,0,0,0,0,
  0,0,0,205,204,12,62,103,213,39,62,230,63,76,192,0,0,0,0,0,0,0,0,145,237,60,191,251,58,64,63,177,80,131,64,0,0,0,0,0,0,
  0,0,93,254,227,62,54,60,133,191,0,0,64,64,12,0,10,0,0,0,0,0,0,0,0,0,45,5,11,0,0,0,2,15,132,6,63,126,206,143,62,12,40,250,
  59,213,1,163,190,50,249,179,190,216,97,189,61,228,51,60,190,209,201,141,62,77,29,76,62,22,94,143,62,29,160,74,190,243,139,
  189,62,0,0,0,0,90,240,71,190,230,122,143,190,216,212,251,189,0,0,0,0,159,10,105,62,193,38,24,63,220,1,134,190,181,52,193,
  62,181,52,193,190,0,0,0,0,0,0,0,0,220,185,111,189,231,85,76,63,190,145,112,60,43,122,7,191,174,8,16,191,81,12,60,191,22,
  253,102,62,51,6,162,62,232,174,137,191,8,106,252,190,84,93,228,190,133,75,250,62,81,142,73,191,82,240,38,191,198,125,182,
  189,70,209,24,191,197,181,220,190,1,216,157,62,24,14,90,191,201,139,221,189,87,92,74,190,32,205,20,63,43,101,55,190,14,0,
  164,190,46,215,114,61,151,208,209,59,46,223,220,188,221,251,161,62,5,125,3,191,193,28,116,60,7,242,124,188,81,173,29,190,
  232,67,80,190,47,98,193,190,14,255,23,191,91,14,232,189,3,211,139,190,63,33,191,189,249,34,157,190,39,185,0,62,53,67,47,
  191,216,158,98,62,157,17,182,62,243,233,151,187,47,206,75,190,237,199,9,188,153,195,127,190,178,124,139,188,13,169,107,
  61,201,46,252,62,87,181,4,191,187,99,38,62,168,190,243,62,101,156,209,62,172,85,181,188,73,150,10,63,159,169,234,190,193,
  110,201,62,227,195,62,62,210,100,185,62,32,202,54,62,162,229,182,189,184,197,39,61,202,234,233,62,22,0,9,63,143,223,124,61,
  136,138,24,62,87,62,108,62,221,236,50,63,172,177,176,62,18,139,172,190,244,185,249,190,30,163,193,61,230,242,244,61,111,81,
  152,190,123,126,211,62,157,244,164,62,167,168,121,62,5,78,168,62,148,160,165,62,20,149,28,190,146,102,118,61,208,85,39,190,
  3,21,163,189,112,129,151,62,101,141,41,190,220,61,4,62,21,169,62,62,164,188,60,63,11,87,40,188,176,173,179,190,122,254,152,
  188,25,92,12,62,63,235,165,189,97,162,166,58,18,212,85,62,98,75,85,190,26,226,66,62,87,25,180,62,243,156,91,63,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,208,195,188,107,20,
  43,63,149,216,239,190,85,119,2,60,105,76,153,62,187,196,149,189,153,43,33,190,187,174,93,190,57,79,7,63,126,211,31,62,5,96,129,
  190,91,26,65,63,101,40,185,190,156,240,221,190,229,253,212,189,197,43,170,190,83,184,47,190,219,252,125,62,148,91,246,62,135,
  163,46,190,17,116,109,63,189,150,160,190,41,81,202,62,48,17,204,62,149,120,52,63,116,66,166,62,53,128,66,190,132,227,81,
  190,172,154,151,190,151,100,90,63,153,16,73,62,254,179,238,189,14,15,148,190,25,71,21,61,193,112,0,62,132,167,40,63,197,
  18,244,189,25,58,123,62,125,206,181,190,120,37,124,62,76,8,39,63,156,95,153,188,16,91,158,190,201,222,37,63,245,38,217,62,
  82,186,8,62,181,142,239,190,96,255,16,62,253,72,40,190,185,158,25,62,188,114,128,63,189,52,136,62,100,163,149,190,171,64,
  118,63,37,156,205,62,128,201,23,63,94,123,92,61,49,64,93,62,112,200,237,190,143,93,188,62,207,122,139,62,214,103,100,190,
  54,132,154,188,190,207,23,62,43,49,177,190,71,47,165,62,133,187,120,190,220,95,29,190,223,151,27,61,187,41,19,62,72,252,26,
  190,166,134,9,188,239,75,158,190,71,251,185,189,90,127,144,61,138,188,178,61,22,84,125,190,57,174,166,62,44,173,113,62,30,
  20,160,61,63,213,84,63,91,30,131,62,166,172,111,190,154,114,121,63,30,188,29,63,153,254,99,63,133,189,142,188,107,150,250,
  190,183,89,126,191,189,91,30,63,26,73,244,62,254,95,51,190,50,245,170,60,225,42,29,63,10,131,61,62,59,197,142,62,25,184,211,
  189,82,130,212,61,192,18,98,62,166,83,140,62,12,92,24,191,131,58,50,63,0,0,0,0,0,0,0,0,186,220,249,189,136,12,46,191,0,0,0,
  0,0,0,0,0,27,218,207,189,232,188,168,62,0,0,0,0,0,0,0,0,171,68,52,191,103,55,174,62,0,0,0,0,0,0,0,0,128,128,239,190,104,33,
  165,62,0,0,0,0,0,0,0,0,164,38,83,191,66,116,79,63,0,0,0,0,0,0,0,0,56,200,245,62,68,206,192,190,0,0,0,0,0,0,0,0,59,191,59,63,
  152,122,21,191,0,0,0,0,0,0,0,0,148,220,158,62,81,229,132,191,0,0,0,0,0,0,0,0,17,169,249,190,20,157,138,63,0,0,0,0,0,0,0,0,
  10,10,2,96,128,227,74,143,79,143,71,114,143,209,72,43,244,78,71,206,24,40,72,144,157,5,72,158,60,237,71,204,111,73,71,7,226,
  11,72,144,109,36,72,0,0,0,0,0,0,0,0,0,0,0,0,19,175,129,74,63,162,182,70,3,220,62,72,198,206,88,70,227,241,82,71,61,22,35,71,
  3,21,10,71,0,70,48,70,85,81,203,70,3,140,254,70,0,0,128,63,0,0,128,63,0,0,128,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,145,1,254,0,2,1,5,48,117,100,0,44,1,112,23,151,7,132,3,197,0,92,4,144,1,64,1,64,1,144,1,48,117,48,
  117,48,117,48,117,100,0,100,0,100,0,48,117,48,117,48,117,100,0,100,0,48,117,48,117,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,8,7,100,0,
  100,0,100,0,100,0,48,117,48,117,48,117,100,0,100,0,100,0,48,117,48,117,100,0,100,0,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,44,1,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,112,23,112,23,112,23,112,23,8,7,8,7,8,7,8,7,112,23,112,23,112,23,112,23,112,23,112,
  23,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,112,23,112,23,112,23,
  112,23,255,255,255,255,220,5,220,5,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,220,5,220,5,220,5,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,48,117,0,1,0,64,0,2,0,64,0,
  2,0,2,0,31,0,32,0,2,0,31,0,32,0,100,0,64,1,170,0,64,1,240,0,240,0,240,0,64,1,64,1,64,1,10,1,0,0,0,0,0,35,175,0,0
};

/* Macros used */
#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) /* 360 minutes - 4 times a day */
#define ERROR_DUR 1000

/**
 * @brief : This function checks the BSEC status, prints the respective error code. Halts in case of error
 * @param[in] bsec  : Bsec2 class object
 */
void checkBsecStatus(Bsec2 bsec);

/**
 * @brief : This function updates/saves BSEC state
 * @param[in] bsec  : Bsec2 class object
 */
void updateBsecState(Bsec2 bsec);

/**
 * @brief : This function is called by the BSEC library when a new output is available
 * @param[in] input     : BME68X sensor data before processing
 * @param[in] outputs   : Processed BSEC BSEC output data
 * @param[in] bsec      : Instance of BSEC2 calling the callback
 */
void newDataCallback(const bme68xData data, const bsecOutputs outputs, Bsec2 bsec);

/**
 * @brief : This function retrieves the existing state
 * @param : Bsec2 class object
 */
bool loadState(Bsec2 bsec);

/**
 * @brief : This function writes the state into EEPROM
 * @param : Bsec2 class object
 */
bool saveState(Bsec2 bsec);

/* Create an object of the class Bsec2 */
Bsec2 envSensor;
#ifdef USE_EEPROM
static uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE];
#endif
/* Gas estimate names will be according to the configuration classes used */
const String gasName[] = { "Field Air", "Hand sanitizer", "Undefined 3", "Undefined 4"};

/* Entry point for the example */
void setup(void)
{
#if (OUTPUT_MODE == CLASSIFICATION)
  /* Desired subscription list of BSEC2 outputs */
    bsecSensor sensorList[] = {
            BSEC_OUTPUT_RAW_TEMPERATURE,
            BSEC_OUTPUT_RAW_PRESSURE,
            BSEC_OUTPUT_RAW_HUMIDITY,
            BSEC_OUTPUT_RAW_GAS,
            BSEC_OUTPUT_RAW_GAS_INDEX,
            BSEC_OUTPUT_GAS_ESTIMATE_1,
            BSEC_OUTPUT_GAS_ESTIMATE_2,
            BSEC_OUTPUT_GAS_ESTIMATE_3,
            BSEC_OUTPUT_GAS_ESTIMATE_4
    };
#elif (OUTPUT_MODE == REGRESSION)
	/* Desired subscription list of BSEC2 outputs */
    bsecSensor sensorList[] = {
            BSEC_OUTPUT_RAW_TEMPERATURE,
            BSEC_OUTPUT_RAW_PRESSURE,
            BSEC_OUTPUT_RAW_HUMIDITY,
            BSEC_OUTPUT_RAW_GAS,
            BSEC_OUTPUT_RAW_GAS_INDEX,
            BSEC_OUTPUT_REGRESSION_ESTIMATE_1,
            BSEC_OUTPUT_REGRESSION_ESTIMATE_2,
            BSEC_OUTPUT_REGRESSION_ESTIMATE_3,
            BSEC_OUTPUT_REGRESSION_ESTIMATE_4
    };	
#endif

    Serial.begin(115200);

  #ifdef USE_EEPROM
    EEPROM.begin(BSEC_MAX_STATE_BLOB_SIZE + 1);
  #endif
    Wire.begin();
    /* Valid for boards with USB-COM. Wait until the port is open */
    while (!Serial) delay(10);
   
    /* Initialize the library and interfaces */
    if (!envSensor.begin(BME68X_I2C_ADDR_LOW, Wire))  {
        checkBsecStatus(envSensor);
    }
    /* Load the configuration string that stores information on how to classify the detected gas */
    if (!envSensor.setConfig(bsec_config))  {
      checkBsecStatus (envSensor);
    }
    /* Copy state from the EEPROM to the algorithm */
    if (!loadState(envSensor))  {
      checkBsecStatus (envSensor);
    }
    /* Subscribe for the desired BSEC2 outputs */
    if (!envSensor.updateSubscription(sensorList, ARRAY_LEN(sensorList), BSEC_SAMPLE_RATE_SCAN))  {
      checkBsecStatus (envSensor);
    }
    /* Whenever new data is available call the newDataCallback function */
    envSensor.attachCallback(newDataCallback);
    Serial.println("\nBSEC library version " + \
            String(envSensor.version.major) + "." \
            + String(envSensor.version.minor) + "." \
            + String(envSensor.version.major_bugfix) + "." \
            + String(envSensor.version.minor_bugfix));
}

/* Function that is looped forever */
void loop(void)
{
    /* Call the run function often so that the library can
     * check if it is time to read new data from the sensor
     * and process it.
     */
    if (!envSensor.run()) {
        checkBsecStatus (envSensor);
    }
}


void updateBsecState(Bsec2 bsec)
{
    static uint16_t stateUpdateCounter = 0;
    bool update = false;
    if (!stateUpdateCounter || (stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) {
        /* Update every STATE_SAVE_PERIOD minutes */
        update = true;
        stateUpdateCounter++;
    }
    if (update && !saveState(bsec))
        checkBsecStatus(bsec);
}

void newDataCallback(const bme68xData data, const bsecOutputs outputs, Bsec2 bsec)
{
    if (!outputs.nOutputs)
        return;
    Serial.println("BSEC outputs:\n\ttimestamp = " + String((int) (outputs.output[0].time_stamp / INT64_C(1000000))));
    uint8_t index = 0;
    for (uint8_t i = 0; i < outputs.nOutputs; i++)  {
      const bsecData output  = outputs.output[i];
      switch (output.sensor_id) {
            case BSEC_OUTPUT_RAW_TEMPERATURE:
                Serial.println("\ttemperature = " + String(output.signal));
                break;
            case BSEC_OUTPUT_RAW_PRESSURE:
                Serial.println("\tpressure = " + String(output.signal));
                break;
            case BSEC_OUTPUT_RAW_HUMIDITY:
                Serial.println("\thumidity = " + String(output.signal));
                break;
            case BSEC_OUTPUT_RAW_GAS:
                Serial.println("\tgas resistance = " + String(output.signal));
                break;
            case BSEC_OUTPUT_RAW_GAS_INDEX:
                Serial.println("\tgas index = " + String(output.signal));
                break;
            case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
                Serial.println("\tcompensated temperature = " + String(output.signal));
                break;
            case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
                Serial.println("\tcompensated humidity = " + String(output.signal));
                break;
            case BSEC_OUTPUT_GAS_ESTIMATE_1:
            case BSEC_OUTPUT_GAS_ESTIMATE_2:
            case BSEC_OUTPUT_GAS_ESTIMATE_3:
            case BSEC_OUTPUT_GAS_ESTIMATE_4:
                index = (output.sensor_id - BSEC_OUTPUT_GAS_ESTIMATE_1);
                if (index == 0) // The four classes are updated from BSEC with same accuracy, thus printing is done just once.
                {
                  Serial.println("\taccuracy = " + String(output.accuracy));
                }
                Serial.println("\tclass " + String(index + 1) + " probability : " + String(output.signal * 100) + "%");
                break;
            default:
                break;
        }
    }
    updateBsecState(envSensor);
}

void checkBsecStatus(Bsec2 bsec)
{
    if (bsec.status < BSEC_OK)  {
        Serial.println("BSEC error code : " + String(bsec.status));
    } else if (bsec.status > BSEC_OK) {
        Serial.println("BSEC warning code : " + String(bsec.status));
    }
    if (bsec.sensor.status < BME68X_OK) {
        Serial.println("BME68X error code : " + String(bsec.sensor.status));
    } else if (bsec.sensor.status > BME68X_OK)  {
        Serial.println("BME68X warning code : " + String(bsec.sensor.status));
    }
}

bool loadState(Bsec2 bsec)
{
#ifdef USE_EEPROM
    if (EEPROM.read(0) == BSEC_MAX_STATE_BLOB_SIZE)
    {
        /* Existing state in EEPROM */
        Serial.println("Reading state from EEPROM");
        Serial.print("State file: ");
        for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++)
        {
            bsecState[i] = EEPROM.read(i + 1);
            Serial.print(String(bsecState[i], HEX) + ", ");
        }
        Serial.println();

        if (!bsec.setState(bsecState))
            return false;
    } else
    {
        /* Erase the EEPROM with zeroes */
        Serial.println("Erasing EEPROM");

        for (uint8_t i = 0; i <= BSEC_MAX_STATE_BLOB_SIZE; i++)
            EEPROM.write(i, 0);

        EEPROM.commit();
    }
#endif
    return true;
}

bool saveState(Bsec2 bsec)
{
#ifdef USE_EEPROM
    if (!bsec.getState(bsecState))
        return false;

    Serial.println("Writing state to EEPROM");
    Serial.print("State file: ");

    for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++)
    {
        EEPROM.write(i + 1, bsecState[i]);
        Serial.print(String(bsecState[i], HEX) + ", ");
    }
    Serial.println();

    EEPROM.write(0, BSEC_MAX_STATE_BLOB_SIZE);
    EEPROM.commit();
#endif
    return true;
}

Credits

Sauna Buddy
1 project • 0 followers
Contact
Jan R. Seyler
13 projects • 15 followers
Contact

Comments

Please log in or sign up to comment.