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
... View more