Skip to content

BME680 Environmental Sensor (BSEC2)

WiFi Fan Controller with an Adafruit BME680 environmental sensor connected over Qwiic / STEMMA QT

This is the same Adafruit BME680 as the plain BME680 example, but run through the Bosch BSEC2 library. BSEC2 processes the raw gas reading into an air-quality index (IAQ), a CO2 equivalent, and a breath VOC equivalent, which the open driver cannot produce.

It comes with two strings attached: a license you accept by importing it, and a framework requirement. Those two sections below are the ones you actually have to read. The rest is copy-paste.

The BSEC2 module and the open BME680 module are mutually exclusive. Pick one, never import both.

BSEC2 ships as a precompiled Arduino library, so it only builds on the arduino framework.

In the normal case you do not have to do anything. The shipped fancontroller firmware already pins arduino:

esp32:
board: esp32-s2-saola-1
framework:
type: arduino # already set in every production config

So if you build on the stock config (or on the full example at the bottom of this page), you are done.

Plug the sensor into the Qwiic connector, then include the module as a package and give it a role name. As with the open module, the BME680 hangs on a cable and measures wherever you put it, so name it for its role (Air, Intake, Room), not for the chip:

packages:
hardware: ... # your hardware-rev-*.yaml
bme680:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/bme680_bsec2.yaml
vars:
bme680_name: "Air"

The learned state is written to flash every 6 hours (state_save_interval), so a reboot does not send the sensor back to accuracy 0. Do not shorten that interval to speed things up: it does not speed up learning, and frequent flash writes wear the chip out.

EntityUnitNotes
IAQ0 to 500 index0 = clean, 500 = heavily polluted. Only meaningful once IAQ Accuracy is 3
CO2 EquivalentppmEstimated, not measured (see below)
Breath VOC EquivalentppmEstimated VOC load
PressurehPaBarometric pressure
IAQ Accuracy0 to 3 text sensorThe calibration state, tagged as a diagnostic

Temperature and humidity are also produced but set internal by default, because they duplicate the onboard HDC1080. Expose them with !extend if you want them (see below).

bme680_sample_rate sets how hard the sensor works:

  • LP (low power): a reading every 3 seconds. The default.
  • ULP (ultra low power): a reading every 300 seconds.

For a fixed rack sensor, ULP is the better choice: 5-minute resolution is plenty for air quality, it runs the gas heater far less, and it draws less current. The tradeoff is a longer burn-in, since the algorithm sees fewer samples.

files:
- path: modules/bme680_bsec2.yaml
vars:
bme680_sample_rate: "ULP"

Set these under the package’s files: entry with vars:, or as top-level substitutions: in your config.

VariableDefaultPurpose
bme680_nameAirRole name and entity prefix, for example Intake or Room
bme680_address0x77I2C address. Set 0x76 if you moved the solder jumper
bme680_temp_offset0°C subtracted from the temperature to correct self-heating
bme680_sample_rateLPLP (every 3s) or ULP (every 300s)

These are the component ids the module defines. They are the surface you can reach with !extend and !remove from your own config.

IDTypeNotes
bme_bsecbme68x_bsec2_i2cThe BSEC2 device
bme_iaqsensorIAQ index
bme_co2eqsensorCO2 equivalent
bme_voceqsensorBreath VOC equivalent
bme_pressuresensorPressure
bme_temperaturesensorTemperature, internal by default
bme_humiditysensorHumidity, internal by default
bme_iaq_accuracytext_sensorCalibration state

To publish the temperature and humidity after all, extend them and drop internal:

sensor:
- id: !extend bme_temperature
name: "Air Temperature"
internal: false
- id: !extend bme_humidity
name: "Air Humidity"
internal: false
  • Requires the hardware package (it provides the shared I2C bus bus_a). This module is not standalone.
  • I2C address: 0x77 by default, 0x76 with the solder jumper. Neither collides with the onboard HDC1080 at 0x40.
  • Bus speed: this module does not change the shared bus speed. If you combine several modules, mind the shared clock speed and the added pull-up load. See Combining Expansion Modules.

A complete, compilable config: the hardware package and one BSEC2 sensor named “Air”, set to ultra-low-power. Note the arduino framework.

substitutions:
wifi_ssid: "YourNetwork"
wifi_password: "YourPassword"
packages:
hardware:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files: [hardware-rev-3.1.yaml]
bme680:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/bme680_bsec2.yaml
vars:
bme680_name: "Air"
bme680_sample_rate: "ULP"
esphome:
name: my-fancontroller
friendly_name: My Fan Controller
esp32:
board: esp32-s2-saola-1
framework:
type: arduino # required by BSEC2
logger:
api:
ota:
- platform: esphome
wifi:
ssid: ${wifi_ssid}
password: ${wifi_password}
ap:
ssid: "Fancontroller Fallback"
captive_portal:
SymptomLikely cause
Compile error mentioning BSECThe config runs on esp-idf. BSEC2 only builds on arduino
IAQ jumps around or sticks near 25Burn-in. IAQ Accuracy is still below 3. Wait it out
IAQ, CO2, VOC look wrong right after flashingSame thing: the algorithm has no baseline yet
Temperature reads 1 to 2 °C highSelf-heating from the gas heater. Raise bme680_temp_offset
Sensor missing from the I2C scan logCable not seated, or the jumper is on 0x76 while bme680_address still says 0x77
You want real CO2, not an estimateThe BME680 cannot measure it. Use an NDIR sensor like the SCD41