Revert previous BSEC2 changes
This library proved to be just as useless for reliable IAQ as the original BSEC library (swinging wildly based on nothing at all), so revert back to tag 1.2 and ESP-IDF.
This commit is contained in:
parent
64bbb01ab0
commit
c32ff6064b
144
README.md
144
README.md
@ -59,6 +59,14 @@ The SuperSensor's voice functionality can be completely disabled if voice
|
||||
support is not desired. This defeats most of the point of the SuperSensor,
|
||||
but can be done if desired.
|
||||
|
||||
### Gas Ceiling
|
||||
|
||||
The AQ (air quality) calculation from the BME680 requires a "maximum"/ceiling
|
||||
threshold for the gas resistance value in clean air after some operation
|
||||
time. The value defaults to 200 kΩ to provide an initial baseline, but
|
||||
should be calibrated manually after setup as each sensor is different. See
|
||||
the section "Calibrating AQ" below for more details.
|
||||
|
||||
### Light Threshold Control
|
||||
|
||||
The SuperSensor features a "light presence" binary sensor based on the light
|
||||
@ -170,3 +178,139 @@ For detect, no occupancy will ever fire.
|
||||
For clear, no states will clear occupancy; with any detect option, this
|
||||
means that occupancy will be detected only once and never clear, which
|
||||
is likely not useful.
|
||||
|
||||
## Calibrating AQ
|
||||
|
||||
The Supersensor uses the Bosch BME680 combination temperature, humidity,
|
||||
pressure, and gas sensor to provide a wide range of useful information about
|
||||
the environmental conditions the sensor is placed in. However, this sensor
|
||||
can be tricky to work with.
|
||||
|
||||
While it's normally recommended to use the Bosch BSEC library with this
|
||||
sensor, in my ~6 month experience I found this library to be far more trouble
|
||||
than it was worth. Specifically, it's IAQ measurement is nearly useless, with
|
||||
a strong tendency to get stuck in an upward trend constantly "calibrating"
|
||||
itself to higher and higher baselines, to the point where nonsensical values
|
||||
were being read. After much research into this, I decided to abandon the
|
||||
library in version 1.1 and went with a more custom solution.
|
||||
|
||||
Instead of the BSEC, we use the stock BME680 ESPHome library, along with
|
||||
some calculations by thstielow on GitHub in their [IAQ project](https://github.com/thstielow/raspi-bme680-iaq).
|
||||
This provided some useful example code and formulae to calculate a useful
|
||||
Air Quality (AQ) value instead of the useless Bosch value.
|
||||
|
||||
However using this method requires some manual calibration of the sensor
|
||||
after putting it together but before final use, in order to get a somewhat
|
||||
accurate value out of the AQ component. If you don't care about the AQ value,
|
||||
you can skip this, but it is recommended to take full advantage of the sensor.
|
||||
|
||||
As a quick explainer, the code leverages a combination of the "Gas Resistance"
|
||||
value provided by the sensor, along with an absolute humidity calculated from
|
||||
the temperature and relative humidity of the sensor (included ESPHome sensor),
|
||||
along with two values (one configurable, one hard-coded) and several formulae
|
||||
to arrive at the resulting AQ value. For full details of the calculation,
|
||||
see the repository linked above, which was re-implemented faithfully here.
|
||||
|
||||
The first thing to note is that each BME680 sensor is wildly different in
|
||||
terms of gas resistance values. In the same air, I had sensors reading values
|
||||
that differed by nearly 200,000Ω, which necessitates a human-configurable
|
||||
baseline value. Further, the IAQ project recommends determining a linear
|
||||
slope value for this, but instead of trying to explain how to calculate this,
|
||||
I just went with the default slope value of 0.03 for this first iteration.
|
||||
|
||||
Thus, the main difficulty in getting a useful AQ score is finding the
|
||||
"Gas Resistance Ceiling" value. This value is configurable in the
|
||||
SuperSensor interface (Web or HomeAssistant), and should be calibrated as
|
||||
follows during the initial setup of the supersensor.
|
||||
|
||||
1. Find a known-clean room, for instance a well-ventilated, well-cleaned
|
||||
room in your house or similar. It should have fresh air (no stray VOCs) but
|
||||
also minimal drafts or outside exposure especially if there is a poor external
|
||||
AQ level. This will be your calibration reference room. Ideally, this room
|
||||
should be somewhere between 16C and 26C for optimal performance, so air
|
||||
conditioning (or a nice spring/fall day) is best.
|
||||
|
||||
2. Turn on the SuperSensor in this environment, and connect it to your
|
||||
HomeAssistant instance; this will be critical for viewing historical graphs
|
||||
during the following steps.
|
||||
|
||||
3. Let the SuperSensor run to "burn in" the gas sensor for at least 3-6 hours,
|
||||
or until the value for the Gas Resistance stabilizes. It is best to avoid much
|
||||
movement or activity in the selected calibration room to avoid disrupting
|
||||
the sensor during this time. It is also best to ensure that the ambient
|
||||
temperature changes as little as possible during this time.
|
||||
|
||||
4. Review the resulting graph of Gas Resistance over the burn-in period. You
|
||||
can usually ignore the first hour or two as the sensor was burning in, and
|
||||
focus instead on the last hour or so.
|
||||
|
||||
5. Make note of the highest mean value reached by the sensor during this time.
|
||||
This will be your baseline value for calibrating the Gas Resistance Ceiling.
|
||||
|
||||
6. Round the value up to the nearest 1000. For example, if the maximum value
|
||||
was 195732.1, round this to 196000.0.
|
||||
|
||||
7. Find the difference in the temperature of the BME680 temperature sensor
|
||||
from 20C, called ΔT below. I found this part by trial-and-error, so this is
|
||||
not precise, but as an example if the calibration room is reporting 26C, your
|
||||
ΔT value in the next step is 6. If your temperature was below 20C, use 0.
|
||||
|
||||
8. Use one of the following formulae to come up with your offset value, which
|
||||
depends on the maximum value range found in step 6.
|
||||
|
||||
* `<100,000`: 200 * ΔT = 0-1200
|
||||
* `100,000-200,000`: 500 * ΔT = 0-3000
|
||||
* `>200,000`: 1000 * ΔT = 0-6000
|
||||
|
||||
Again this value is rough, and might not even really be needed, but helps
|
||||
avoid weird issues with AQ values dropping suddenly later as temperature
|
||||
and humidity changes.
|
||||
|
||||
9. Add your offset value from step 8 to the rounded maximum from step 6.
|
||||
For example, 196000.0 with a ΔT of 5C (25C ambient) yields 201000.0
|
||||
|
||||
10. Divide the result from 9 by 1000 to give a number from 1-500. This
|
||||
is the value to enter as the "Gas Resistance Ceiling (kΩ)" for this
|
||||
sensor. This value will be saved in the NV-RAM of the ESP32 and preserved
|
||||
on reboots.
|
||||
|
||||
At this point, you should have a value that results in the "BME680 AQ"
|
||||
sensor reporting 100% AQ, i.e. clean air. You can now test to ensure
|
||||
that the value will correctly drop as VOCs are added.
|
||||
|
||||
1. Take a Sharpie permanent marker, Acetone nail polish remover, or some
|
||||
other VOC that the BME680 gas sensor can detect, and place it near the
|
||||
sensor. For example with a sharpie, remove the cap and place the tip
|
||||
about 1-2cm from the sensor, or place a small capful of nail polish
|
||||
remover about 3-5cm from the sensor.
|
||||
|
||||
2. Wait about 30 seconds.
|
||||
|
||||
3. You should see the AQ value drop precipitously, into the order of 50%
|
||||
or lower, and ideally closer to 0-20%. If the value remains higher than
|
||||
50% with this test, your calculated Gas Resistance Ceiling might be
|
||||
too low, and should be increased in increments of 1000.
|
||||
|
||||
4. Remove the VOC source (replace the cap, remove the capful of remover,
|
||||
etc.) and wait about 30-60 minutes.
|
||||
|
||||
5. You should see the AQ value and gas resistance return to their original
|
||||
values. If it is significantly lower than before, even after waiting 60+
|
||||
minutes, restart the calculation from step 5 in the previous section
|
||||
using this new value as the baseline.
|
||||
|
||||
At this point, the sensor should be calibrated enough for day-to-day
|
||||
casual home use, and will tell you if there is any significant
|
||||
VOC contamination in the air by dropping the AQ value from 100% to some
|
||||
lower value representing the approximate decrease in air quality. Since
|
||||
the sensor also factors in the absolute humidity (and via that, the
|
||||
ambient temperature) into the AQ calculation, high humidity will also
|
||||
drop the value, as this too impacts the air quality. Hopefully this
|
||||
is useful for your purposes.
|
||||
|
||||
If you find that the AQ value still doesn't represent known reality,
|
||||
you can also tweak the in-code value for `ph_slope` on line 522, as
|
||||
it's possible your sensor differs significantly here. As mentioned
|
||||
above this is still a work in progress to determine for myself, so
|
||||
future versions may alter this or include calibration of this value
|
||||
automatically, depending on how things go in my testing.
|
||||
|
146
supersensor.yaml
146
supersensor.yaml
@ -1,10 +1,10 @@
|
||||
---
|
||||
|
||||
###############################################################################
|
||||
# SuperSensor v1.x ESPHome configuration
|
||||
# SuperSensor v1.0 ESPHome configuration
|
||||
###############################################################################
|
||||
#
|
||||
# Copyright (C) 2023-2025 Joshua M. Boniface <joshua@boniface.me>
|
||||
# Copyright (C) 2023 Joshua M. Boniface <joshua@boniface.me>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -26,7 +26,7 @@ esphome:
|
||||
friendly_name: "Supersensor"
|
||||
project:
|
||||
name: joshuaboniface.supersensor
|
||||
version: "1.3"
|
||||
version: "1.1"
|
||||
on_boot:
|
||||
- priority: 600
|
||||
then:
|
||||
@ -55,21 +55,26 @@ dashboard_import:
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
# framework:
|
||||
# type: esp-idf
|
||||
# version: 4.4.8
|
||||
# platform_version: 5.4.0
|
||||
# sdkconfig_options:
|
||||
# CONFIG_ESP32_DEFAULT_CPU_FREQ_240: "y"
|
||||
# CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ: "240"
|
||||
# CONFIG_ESP32_DATA_CACHE_64KB: "y"
|
||||
# CONFIG_ESP32_DATA_CACHE_LINE_64B: "y"
|
||||
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
|
||||
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ: "240"
|
||||
# CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
|
||||
# CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
|
||||
framework:
|
||||
type: esp-idf
|
||||
version: 4.4.8
|
||||
platform_version: 5.4.0
|
||||
sdkconfig_options:
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_240: "y"
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ: "240"
|
||||
CONFIG_ESP32_DATA_CACHE_64KB: "y"
|
||||
CONFIG_ESP32_DATA_CACHE_LINE_64B: "y"
|
||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
|
||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ: "240"
|
||||
CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
|
||||
CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
|
||||
|
||||
globals:
|
||||
- id: gas_resistance_ceiling
|
||||
type: int
|
||||
restore_value: yes
|
||||
initial_value: "200"
|
||||
|
||||
- id: pir_hold_time
|
||||
type: int
|
||||
restore_value: yes
|
||||
@ -292,12 +297,12 @@ interval:
|
||||
# wake_word: !lambda return wake_word;
|
||||
|
||||
# Include the Espressif Audio Development Framework for VAD support
|
||||
#esp_adf:
|
||||
#external_components:
|
||||
# - source: github://pr#5230
|
||||
# components:
|
||||
# - esp_adf
|
||||
# refresh: 0s
|
||||
esp_adf:
|
||||
external_components:
|
||||
- source: github://pr#5230
|
||||
components:
|
||||
- esp_adf
|
||||
refresh: 0s
|
||||
|
||||
voice_assistant:
|
||||
microphone: mic
|
||||
@ -449,13 +454,6 @@ ld2410:
|
||||
# g8_move_threshold: 80
|
||||
# g8_still_threshold: 81
|
||||
|
||||
bme68x_bsec2_i2c:
|
||||
address: 0x77
|
||||
model: bme680
|
||||
operating_age: 28d
|
||||
sample_rate: LP
|
||||
supply_voltage: 3.3V
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "SuperSensor Occupancy"
|
||||
@ -507,25 +505,25 @@ binary_sensor:
|
||||
name: "LD2410C Still Target"
|
||||
|
||||
sensor:
|
||||
- platform: bme68x_bsec2
|
||||
- platform: bme680
|
||||
address: 0x77
|
||||
update_interval: 5s
|
||||
iir_filter: 127x
|
||||
temperature:
|
||||
name: "BME680 Temperature"
|
||||
id: bme680_temperature
|
||||
oversampling: 16x
|
||||
pressure:
|
||||
name: "BME680 Pressure"
|
||||
id: bme680_pressure
|
||||
oversampling: 16x
|
||||
humidity:
|
||||
name: "BME680 Relative Humidity"
|
||||
id: bme680_humidity
|
||||
iaq:
|
||||
name: "BME680 IAQ"
|
||||
id: bme680_iaq
|
||||
co2_equivalent:
|
||||
name: "BME680 CO2 Equivalent"
|
||||
id: bme680_co2e
|
||||
breath_voc_equivalent:
|
||||
name: "BME680 Breath VOC Equivalent"
|
||||
id: bme680_bco2e
|
||||
oversampling: 16x
|
||||
gas_resistance:
|
||||
name: "BME680 Gas Resistance"
|
||||
id: bme680_gas_resistance
|
||||
|
||||
- platform: absolute_humidity
|
||||
name: "BME680 Absolute Humidity"
|
||||
@ -533,6 +531,28 @@ sensor:
|
||||
humidity: bme680_humidity
|
||||
id: bme680_absolute_humidity
|
||||
|
||||
- platform: template
|
||||
name: "BME680 AQ"
|
||||
id: bme680_aq
|
||||
icon: "mdi:gauge"
|
||||
unit_of_measurement: "%"
|
||||
accuracy_decimals: 0
|
||||
update_interval: 5s
|
||||
# Calculation from https://github.com/thstielow/raspi-bme680-iaq
|
||||
lambda: |-
|
||||
float ph_slope = 0.03;
|
||||
float comp_gas = id(bme680_gas_resistance).state * pow(2.718281, (ph_slope * id(bme680_absolute_humidity).state));
|
||||
float gas_ratio = pow((comp_gas / (id(gas_resistance_ceiling) * 1000)), 2);
|
||||
if (gas_ratio > 1) {
|
||||
gas_ratio = 1.0;
|
||||
}
|
||||
float air_quality = gas_ratio * 100;
|
||||
int normalized_air_quality = (int)air_quality;
|
||||
if (normalized_air_quality > 100) {
|
||||
normalized_air_quality = 100;
|
||||
}
|
||||
return normalized_air_quality;
|
||||
|
||||
- platform: tsl2591
|
||||
address: 0x29
|
||||
update_interval: 1s
|
||||
@ -618,36 +638,29 @@ sensor:
|
||||
entity_category: diagnostic
|
||||
|
||||
text_sensor:
|
||||
- platform: bme68x_bsec2
|
||||
iaq_accuracy:
|
||||
name: "BME68x IAQ Accuracy"
|
||||
|
||||
- platform: template
|
||||
name: "BME68x IAQ Classification"
|
||||
name: "BME680 AQ Classification"
|
||||
icon: "mdi:air-filter"
|
||||
update_interval: 5s
|
||||
lambda: |-
|
||||
if ( int(id(bme680_iaq).state) <= 50) {
|
||||
int aq = int(id(bme680_aq).state);
|
||||
if (aq >= 90) {
|
||||
return {"Excellent"};
|
||||
}
|
||||
else if (int(id(bme680_iaq).state) >= 51 && int(id(bme680_iaq).state) <= 100) {
|
||||
else if (aq >= 80) {
|
||||
return {"Good"};
|
||||
}
|
||||
else if (int(id(bme680_iaq).state) >= 101 && int(id(bme680_iaq).state) <= 150) {
|
||||
return {"Lightly polluted"};
|
||||
else if (aq >= 70) {
|
||||
return {"Fair"};
|
||||
}
|
||||
else if (int(id(bme680_iaq).state) >= 151 && int(id(bme680_iaq).state) <= 200) {
|
||||
return {"Moderately polluted"};
|
||||
else if (aq >= 60) {
|
||||
return {"Moderate"};
|
||||
}
|
||||
else if (int(id(bme680_iaq).state) >= 201 && int(id(bme680_iaq).state) <= 250) {
|
||||
return {"Heavily polluted"};
|
||||
}
|
||||
else if (int(id(bme680_iaq).state) >= 251 && int(id(bme680_iaq).state) <= 350) {
|
||||
return {"Severely polluted"};
|
||||
}
|
||||
else if (int(id(bme680_iaq).state) >= 351) {
|
||||
return {"Extremely polluted"};
|
||||
else if (aq >= 50) {
|
||||
return {"Bad"};
|
||||
}
|
||||
else {
|
||||
return {"error"};
|
||||
return {"Terrible"};
|
||||
}
|
||||
|
||||
- platform: wifi_info
|
||||
@ -723,6 +736,21 @@ switch:
|
||||
entity_category: diagnostic
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
name: "Gas Resistance Ceiling (kΩ)"
|
||||
id: gas_resistance_ceiling_setter
|
||||
min_value: 10
|
||||
max_value: 500
|
||||
step: 1
|
||||
entity_category: config
|
||||
lambda: |-
|
||||
return id(gas_resistance_ceiling);
|
||||
set_action:
|
||||
then:
|
||||
- globals.set:
|
||||
id: gas_resistance_ceiling
|
||||
value: !lambda 'return int(x);'
|
||||
|
||||
# PIR Hold Time:
|
||||
# The number of seconds after motion detection for the PIR sensor to remain held on
|
||||
- platform: template
|
||||
|
Loading…
x
Reference in New Issue
Block a user