Bosch Sensortec Community

    cancel
    Showing results for 
    Search instead for 
    Did you mean: 
    SOLVED

    BME680 on ESPHome using Bosch's BSEC library

    BME680 on ESPHome using Bosch's BSEC library

    ChrisE
    Occasional Visitor

    Dear all,

    I am trying to integrate a Bosch BME680 sensor running on the precompiled, closed source "BSEC" library as a custom sensor for ESPHome for an ESP32 for getting an IAQ reading instead of the resistance reading only. In other words, I do not want to use the ESPHome-driver for the sensor but the precompiled Bosch BSEC library wrapped in a custom ESPHome sensor component.

    However, I cannot get ESPHome to compile/link the libraries correctly. What am I missing here?

    Thank you very much for getting me kickstarted with with the precompiled library on ESPHome.

    Best regards,

    Chris

     

    Configuration YAML:

    esphome:
      name: multisensor_1
      platform: ESP32 
      board: nodemcu-32s
      includes: 
        - 'CE_BSEC.h'
      libraries:
        - 'BSEC Software Library'
    
    i2c:
      sda: 21
      scl: 22
      scan: True
      id: id_i2c
    
    sensor:
    ## this is the BOSCH Implementation
      - platform: custom
        lambda: |-
          auto IAQSensor = new CE_BSEC();
          App.register_component(IAQSensor);
          return { IAQSensor->TSensor, IAQSensor->PSensor, IAQSensor->HSensor, IAQSensor->AQSensor };
        sensors:
        -  name: "BME680 Temperature"
           unit_of_measurement: 'V'
           accuracy_decimals: 1
        -  name: "BME680 Pressure"
           unit_of_measurement: 'hPa'
           accuracy_decimals: 1
        -  name: "BME680 relative Humidity"
           unit_of_measurement: '%'
           accuracy_decimals: 1
        -  name: "BME680 IAQ"
           accuracy_decimals: 1

     

    Custom sensor CE_BSEC.h

    #include "esphome.h"
    #include "bsec.h"
    
    class CE_BSEC : public PollingComponent, public Sensor {
     public:
      // constructor
      Bsec iaqSensor;
      Sensor *TSensor = new Sensor();
      Sensor *PSensor = new Sensor();
      Sensor *HSensor = new Sensor();
      Sensor *AQSensor = new Sensor();
    
      CE_BSEC() : PollingComponent(60000) { }
    
      void setup() override {
        // This will be called by App.setup()
        iaqSensor.begin(0x77, Wire);
      }
    
      void update() override {
        float Temp = iaqSensor.temperature;
        TSensor->publish_state(Temp);
        
        float Press = iaqSensor.pressure;
        PSensor->publish_state(Press);
        
        float Hum = iaqSensor.humidity;
        HSensor->publish_state(Hum);
        
        float Qual = iaqSensor.iaq;
        AQSensor->publish_state(Qual);
      }
    };

     

    Compiling .pioenvs/multisensor_1/src/main.cpp.o
    Linking .pioenvs/multisensor_1/firmware.elf
    .pioenvs/multisensor_1/lib0fa/libBSEC Software Library_ID6979.a(bsec.cpp.o):(.literal._ZN4Bsec11beginCommonEv+0x4): undefined reference to `bsec_init'
    .pioenvs/multisensor_1/lib0fa/libBSEC Software Library_ID6979.a(bsec.cpp.o):(.literal._ZN4Bsec11beginCommonEv+0x8): undefined reference to `bsec_get_version'
    .pioenvs/multisensor_1/lib0fa/libBSEC Software Library_ID6979.a(bsec.cpp.o): In function `Bsec::beginCommon()':
    /config/multisensor_1/.piolibdeps/multisensor_1/BSEC Software Library_ID6979/src/bsec.cpp:257: undefined reference to `bsec_init'
    .pioenvs/multisensor_1/lib0fa/libBSEC Software Library_ID6979.a(bsec.cpp.o): In function `Bsec::getVersion()':
    /config/multisensor_1/.piolibdeps/multisensor_1/BSEC Software Library_ID6979/src/bsec.cpp:257: undefined reference to `bsec_get_version'
    collect2: error: ld returned 1 exit status
    *** [.pioenvs/multisensor_1/firmware.elf] Error 1

     

    18 REPLIES 18

    Hi, 

    Can you please share some more details about your working solution?
    I'm stuck with ESPhome and BSEC library as I don't get any other values than 25 from  iaqSensor.iaq.

    Thanks in advance,

    Andreas

    I am using the BME680 connected in I2C with ESP32 with ESPHOME and BSEC library installed.

    But I have the same problems.

    I can read temperature, humidity, pressure and gas resistance, but the IAQ is always 25 and the accuracy of the IAQ is 0.
    Any suggestions ?

    Thank

    In case it helps anyone this is my setup which I've had running without issue for a while now on a couple of ESP8266 wemos d1 minis.

    bsecsensor.h

     

    #include <esphome.h>
    #include <bsec.h>
    #include <Wire.h>
    #include <EEPROM.h>
    const uint8_t bsec_config_iaq[] = {
        #include "config/generic_33v_3s_28d/bsec_iaq.txt"
    };
    
    #define STATE_SAVE_PERIOD   UINT32_C(360 * 60 * 1000) // 360 minutes - 4 times a day
    
    static const char *TAG = "bsec";
    
    class BSECSensor : public Component {
        private:
            Bsec iaqSensor;
            bsec_library_return_t bsecStatus = BSEC_OK;
            int8_t bme680Status = BME680_OK;
            uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
            uint16_t stateUpdateCounter = 0;
            uint8_t sensorPushNum = 0;
    
            bool checkIaqSensorStatus(void) {
                if (iaqSensor.status != BSEC_OK && iaqSensor.status != bsecStatus) {
                    if (iaqSensor.status < BSEC_OK) {
                        ESP_LOGE(TAG, "BSEC error code: %d", iaqSensor.status);
                        status_set_error();
                    } else {
                        ESP_LOGW(TAG, "BSEC warning code: %d", iaqSensor.status);
                        status_set_warning();
                    }
                }
    
                if (iaqSensor.bme680Status != BME680_OK && iaqSensor.bme680Status != bme680Status) {
                    if (iaqSensor.bme680Status < BME680_OK) {
                        ESP_LOGE(TAG, "BME680 error code: %d", iaqSensor.bme680Status);
                        status_set_error();
                    } else {
                        ESP_LOGW(TAG, "BME680 warning code: %d", iaqSensor.bme680Status);
                        status_set_warning();
                    }
                }
    
                bsecStatus = iaqSensor.status;
                bme680Status = iaqSensor.bme680Status;
    
                if (bsecStatus < BSEC_OK || bme680Status < BME680_OK) {
                    return false;
                }
    
                status_clear_error();
                status_clear_warning();
                return true;
            }
    
            void loadState(void) {
                if (EEPROM.read(0) == BSEC_MAX_STATE_BLOB_SIZE) {
                    // Existing state in EEPROM
                    ESP_LOGI(TAG, "Reading state from EEPROM");
                    for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++) {
                        bsecState[i] = EEPROM.read(i + 1);
                    }
    
                    iaqSensor.setState(bsecState);
                    checkIaqSensorStatus();
    
                    // Don't immediately update after initial load
                    stateUpdateCounter++;
                } else {
                    // Erase the EEPROM with zeroes
                    ESP_LOGI(TAG, "Erasing EEPROM");
                    for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE + 1; i++) {
                        EEPROM.write(i, 0);
                    }
                    EEPROM.commit();
                }
            }
    
            void updateState(void) {
                if (
                    // First state update when IAQ accuracy is >= 3
                    (stateUpdateCounter == 0 && iaqSensor.staticIaqAccuracy >= 3)
                    // Then update every STATE_SAVE_PERIOD minutes
                    || ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis())
                ) {
                    stateUpdateCounter++;
                } else {
                    return;
                }
    
                iaqSensor.getState(bsecState);
                if (!checkIaqSensorStatus()) {
                    return;
                }
    
                ESP_LOGI(TAG, "Writing state to EEPROM");
                for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE ; i++) {
                    EEPROM.write(i + 1, bsecState[i]);
                }
    
                EEPROM.write(0, BSEC_MAX_STATE_BLOB_SIZE);
                EEPROM.commit();
            }
    
        public:
            Sensor *temperature = new Sensor();
            Sensor *humidity = new Sensor();
            Sensor *pressure = new Sensor();
            Sensor *gasResistance = new Sensor();
            Sensor *staticIaq = new Sensor();
            Sensor *staticIaqAccuracy = new Sensor();
            Sensor *co2Equivalent = new Sensor();
            Sensor *breathVocEquivalent = new Sensor();
    
            void setup() override {
                EEPROM.begin(BSEC_MAX_STATE_BLOB_SIZE + 1);
                iaqSensor.begin(BME680_I2C_ADDR_PRIMARY, Wire);
    
                ESP_LOGI(
                    TAG, "BSEC Version: %d.%d.%d.%d",
                    iaqSensor.version.major, iaqSensor.version.minor,
                    iaqSensor.version.major_bugfix, iaqSensor.version.minor_bugfix
                );
    
                iaqSensor.setConfig(bsec_config_iaq);
    
                // Load state from EEPROM
                loadState();
    
                // Subscribe to sensor values
                bsec_virtual_sensor_t sensorList[7] = {
                    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
                    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
                    BSEC_OUTPUT_RAW_PRESSURE,
                    BSEC_OUTPUT_RAW_GAS,
                    BSEC_OUTPUT_STATIC_IAQ,
                    BSEC_OUTPUT_CO2_EQUIVALENT,
                    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
                };
                iaqSensor.updateSubscription(sensorList, 7, BSEC_SAMPLE_RATE_LP);
            }
    
            void setTemperatureOffset(float temp) {
                iaqSensor.setTemperatureOffset(temp);
            }
    
            void loop() override {
                if (checkIaqSensorStatus() && iaqSensor.run()) {
                    sensorPushNum = 1;
                    updateState();
                }
    
                // In order not to block here, spread the sensor state pushes
                // across subsequent calls otherwise we end up with API disconnects
                if (sensorPushNum > 0 && sensorPushNum <=  {
                    switch (sensorPushNum++) {
                        case 1: temperature->publish_state(iaqSensor.temperature); break;
                        case 2: humidity->publish_state(iaqSensor.humidity); break;
                        case 3: pressure->publish_state(iaqSensor.pressure); break;
                        case 4: gasResistance->publish_state(iaqSensor.gasResistance); break;
                        case 5: staticIaq->publish_state(iaqSensor.staticIaq); break;
                        case 6: staticIaqAccuracy->publish_state(iaqSensor.staticIaqAccuracy); break;
                        case 7: co2Equivalent->publish_state(iaqSensor.co2Equivalent); break;
                        case 8: breathVocEquivalent->publish_state(iaqSensor.breathVocEquivalent); break;
                    }
                }
            }
    };

     

     

     

    .bsecsensor.yaml

    Note: I have to specifiy a specific patched arduino version due to the ESP8266 memory layout per the library readme. If you're going to use this please fork the Arduino repo and modify the platform_packages directive accordingly as I can't guarantuee what state my copy will be in long term. I imagine on an ESP32 it would "just work" with no need specify any arduino options.

     

    esphome:
      name: ${location_id}_climate
      platform: ESP8266
      board: d1_mini
      includes: 
        - bsecsensor.h
      libraries:
        - 'BSEC Software Library'
      arduino_version: '2.5.2'
      platformio_options:
        # Use patched arduino to modify BSEC library memory location
        # https://github.com/BoschSensortec/BSEC-Arduino-library#4-additional-core-specific-modifications
        platform_packages: framework-arduinoespressif8266 @ https://github.com/trvrnrth/Arduino.git
    
    wifi:
      ssid: !secret wifi_ssid
      password: !secret wifi_password
    
      ap:
        ssid: ${location} Climate Sensor
        password: !secret ap_password
    
    captive_portal:
    
    logger:
      level: INFO
      esp8266_store_log_strings_in_flash: False
    
    api:
    
    ota:
    
    status_led:
      pin:
        number: 2
        inverted: True
    
    i2c:
      sda: 4
      scl: 5
      scan: False
    
    sensor:
      - platform: custom
        lambda: |-
          auto sensor = new BSECSensor();
          sensor->setTemperatureOffset(${temp_offset});
          App.register_component(sensor);
          return {
            sensor->temperature,
            sensor->humidity,
            sensor->pressure,
            sensor->gasResistance,
            sensor->staticIaq,
            sensor->staticIaqAccuracy,
            sensor->co2Equivalent,
            sensor->breathVocEquivalent,
          };
        sensors:
        - name: ${location} Temperature
          unit_of_measurement: '°C'
          accuracy_decimals: 1
          icon: mdi:thermometer
          filters: 
            - median
        - name: ${location} Humidity
          unit_of_measurement: '%'
          accuracy_decimals: 1
          icon: mdi:water-percent
          filters: 
            - median
        - name: ${location} Pressure
          unit_of_measurement: 'hPa'
          icon: mdi:gauge
          filters: 
            - median
        - name: ${location} Gas Resistance
          unit_of_measurement: 'Ω'
          icon: mdi:gas-cylinder
          filters: 
            - median
        - name: ${location} Static IAQ
          unit_of_measurement: IAQ
          accuracy_decimals: 1
          icon: mdi:gauge
          filters: 
            - median
        - id: ${location_id}_raw_static_iaq_accuracy
          internal: True
          on_value:
            - text_sensor.template.publish:
                id: ${location_id}_static_iaq_accuracy
                state: !lambda |-
                  if (x == 0) return "Stabilizing";
                  if (x == 1) return "Uncertain";
                  if (x == 2) return "Calibrating";
                  if (x == 3) return "Calibrated";
                  return "Invalid";
        - name: ${location} CO2 Equivalent
          unit_of_measurement: 'ppm'
          accuracy_decimals: 1
          icon: mdi:test-tube
          filters: 
            - median
        - name: ${location} Breath VOC Equivalent
          unit_of_measurement: 'ppm'
          accuracy_decimals: 1
          icon: mdi:test-tube
          filters: 
            - median
    
    text_sensor:
      - platform: template
        id: ${location_id}_static_iaq_accuracy
        name: ${location} Static IAQ Accuracy
        icon: mdi:checkbox-marked-circle-outline

     

     

    test_climate.yaml

     

    substitutions:
      location_id: test
      location: Test
      temp_offset: '0'
    
    <<: !include .bsecsensor.yaml

     

     

     

    Thank you very much trvrnrth, function !!!! 😀

    I got some errors, but I was able to continue.

    There is an error in this line

    if (sensorPushNum > 0 && sensorPushNum <=  

     I think there is a missing value after <=

     

    Another mistake is here

    - text_sensor.template.publish:

     

    here I get: Unable to find action with the name 'text_sensor.template.publish'.

     

    But in any case I found a way to get it running.

    So again thank you.

     

    Good to hear it's working for you!

    There seems to be some emoji detection or something mangling things. The line in error should read:

     

    if (sensorPushNum > 0 && sensorPushNum <= 8 ) {

     

     

    Weird that the text sensor publish isn't working for you as that's being used as detailed in the docs. You might want to double check your editor hasn't messed up the indentation there.

    Icon--AD-black-48x48Icon--address-consumer-data-black-48x48Icon--appointment-black-48x48Icon--back-left-black-48x48Icon--calendar-black-48x48Icon--center-alignedIcon--Checkbox-checkIcon--clock-black-48x48Icon--close-black-48x48Icon--compare-black-48x48Icon--confirmation-black-48x48Icon--dealer-details-black-48x48Icon--delete-black-48x48Icon--delivery-black-48x48Icon--down-black-48x48Icon--download-black-48x48Ic-OverlayAlertIcon--externallink-black-48x48Icon-Filledforward-right_adjustedIcon--grid-view-black-48x48IC_gd_Check-Circle170821_Icons_Community170823_Bosch_Icons170823_Bosch_Icons170821_Icons_CommunityIC-logout170821_Icons_Community170825_Bosch_Icons170821_Icons_CommunityIC-shopping-cart2170821_Icons_CommunityIC-upIC_UserIcon--imageIcon--info-i-black-48x48Icon--left-alignedIcon--Less-minimize-black-48x48Icon-FilledIcon--List-Check-grennIcon--List-Check-blackIcon--List-Cross-blackIcon--list-view-mobile-black-48x48Icon--list-view-black-48x48Icon--More-Maximize-black-48x48Icon--my-product-black-48x48Icon--newsletter-black-48x48Icon--payment-black-48x48Icon--print-black-48x48Icon--promotion-black-48x48Icon--registration-black-48x48Icon--Reset-black-48x48Icon--right-alignedshare-circle1Icon--share-black-48x48Icon--shopping-bag-black-48x48Icon-shopping-cartIcon--start-play-black-48x48Icon--store-locator-black-48x48Ic-OverlayAlertIcon--summary-black-48x48tumblrIcon-FilledvineIc-OverlayAlertwhishlist