Sgp30 sensor improvements (#1510)

Co-authored-by: Umberto73 <huub@eikens.com>
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
Huub Eikens 2021-07-12 23:21:54 +02:00 committed by GitHub
parent 551e9c6111
commit 7dd16df846
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 105 additions and 11 deletions

View file

@ -8,6 +8,8 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
CONF_BASELINE,
CONF_ECO2,
CONF_TEMPERATURE,
CONF_TVOC,
CONF_HUMIDITY,
@ -21,9 +23,6 @@ CCS811Component = ccs811_ns.class_(
"CCS811Component", cg.PollingComponent, i2c.I2CDevice
)
CONF_ECO2 = "eco2"
CONF_BASELINE = "baseline"
CONFIG_SCHEMA = (
cv.Schema(
{

View file

@ -3,13 +3,16 @@ import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_BASELINE,
DEVICE_CLASS_EMPTY,
CONF_ECO2,
CONF_TVOC,
ICON_RADIATOR,
STATE_CLASS_MEASUREMENT,
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
UNIT_EMPTY,
ICON_MOLECULE_CO2,
CONF_TVOC,
)
DEPENDENCIES = ["i2c"]
@ -17,10 +20,9 @@ DEPENDENCIES = ["i2c"]
sgp30_ns = cg.esphome_ns.namespace("sgp30")
SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice)
CONF_ECO2 = "eco2"
CONF_BASELINE = "baseline"
CONF_ECO2_BASELINE = "eco2_baseline"
CONF_TVOC_BASELINE = "tvoc_baseline"
CONF_STORE_BASELINE = "store_baseline"
CONF_UPTIME = "uptime"
CONF_COMPENSATION = "compensation"
CONF_HUMIDITY_SOURCE = "humidity_source"
@ -44,6 +46,13 @@ CONFIG_SCHEMA = (
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema(
UNIT_EMPTY, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY
),
cv.Optional(CONF_TVOC_BASELINE): sensor.sensor_schema(
UNIT_EMPTY, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY
),
cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean,
cv.Optional(CONF_BASELINE): cv.Schema(
{
cv.Required(CONF_ECO2_BASELINE): cv.hex_uint16_t,
@ -58,7 +67,7 @@ CONFIG_SCHEMA = (
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(cv.polling_component_schema("1s"))
.extend(i2c.i2c_device_schema(0x58))
)
@ -76,6 +85,17 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc_sensor(sens))
if CONF_ECO2_BASELINE in config:
sens = await sensor.new_sensor(config[CONF_ECO2_BASELINE])
cg.add(var.set_eco2_baseline_sensor(sens))
if CONF_TVOC_BASELINE in config:
sens = await sensor.new_sensor(config[CONF_TVOC_BASELINE])
cg.add(var.set_tvoc_baseline_sensor(sens))
if CONF_STORE_BASELINE in config:
cg.add(var.set_store_baseline(config[CONF_STORE_BASELINE]))
if CONF_BASELINE in config:
baseline_config = config[CONF_BASELINE]
cg.add(var.set_eco2_baseline(baseline_config[CONF_ECO2_BASELINE]))

View file

@ -1,5 +1,6 @@
#include "sgp30.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace sgp30 {
@ -22,6 +23,13 @@ const uint32_t IAQ_BASELINE_WARM_UP_SECONDS_WITH_BASELINE_PROVIDED = 3600;
// if the sensor starts without any prior baseline value provided
const uint32_t IAQ_BASELINE_WARM_UP_SECONDS_WITHOUT_BASELINE = 43200;
// Shortest time interval of 1H for storing baseline values.
// Prevents wear of the flash because of too many write operations
const uint32_t SHORTEST_BASELINE_STORE_INTERVAL = 3600;
// Store anyway if the baseline difference exceeds the max storage diff value
const uint32_t MAXIMUM_STORAGE_DIFF = 50;
void SGP30Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
@ -73,6 +81,21 @@ void SGP30Component::setup() {
return;
}
// Hash with compilation time
// This ensures the baseline storage is cleared after OTA
uint32_t hash = fnv1_hash(App.get_compilation_time());
this->pref_ = global_preferences.make_preference<SGP30Baselines>(hash, true);
if (this->pref_.load(&this->baselines_storage_)) {
ESP_LOGI(TAG, "Loaded eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", this->baselines_storage_.eco2,
baselines_storage_.tvoc);
this->eco2_baseline_ = this->baselines_storage_.eco2;
this->tvoc_baseline_ = this->baselines_storage_.tvoc;
}
// Initialize storage timestamp
this->seconds_since_last_store_ = 0;
// Sensor baseline reliability timer
if (this->eco2_baseline_ > 0 && this->tvoc_baseline_ > 0) {
this->required_warm_up_time_ = IAQ_BASELINE_WARM_UP_SECONDS_WITH_BASELINE_PROVIDED;
@ -110,6 +133,31 @@ void SGP30Component::read_iaq_baseline_() {
uint16_t tvocbaseline = (raw_data[1]);
ESP_LOGI(TAG, "Current eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2baseline, tvocbaseline);
if (eco2baseline != this->eco2_baseline_ || tvocbaseline != this->tvoc_baseline_) {
this->eco2_baseline_ = eco2baseline;
this->tvoc_baseline_ = tvocbaseline;
if (this->eco2_sensor_baseline_ != nullptr)
this->eco2_sensor_baseline_->publish_state(this->eco2_baseline_);
if (this->tvoc_sensor_baseline_ != nullptr)
this->tvoc_sensor_baseline_->publish_state(this->tvoc_baseline_);
// Store baselines after defined interval or if the difference between current and stored baseline becomes too
// much
if (this->store_baseline_ &&
(this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL ||
abs(this->baselines_storage_.eco2 - this->eco2_baseline_) > MAXIMUM_STORAGE_DIFF ||
abs(this->baselines_storage_.tvoc - this->tvoc_baseline_) > MAXIMUM_STORAGE_DIFF)) {
this->seconds_since_last_store_ = 0;
this->baselines_storage_.eco2 = this->eco2_baseline_;
this->baselines_storage_.tvoc = this->tvoc_baseline_;
if (this->pref_.save(&this->baselines_storage_)) {
ESP_LOGI(TAG, "Store eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", this->baselines_storage_.eco2,
this->baselines_storage_.tvoc);
} else {
ESP_LOGW(TAG, "Could not store eCO2 and TVOC baselines");
}
}
}
this->status_clear_warning();
});
} else {
@ -171,7 +219,8 @@ void SGP30Component::write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_b
if (!this->write_bytes(SGP30_CMD_SET_IAQ_BASELINE >> 8, data, 7)) {
ESP_LOGE(TAG, "Error applying eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2_baseline, tvoc_baseline);
} else
ESP_LOGI(TAG, "Initial eCO2 and TVOC baselines applied successfully!");
ESP_LOGI(TAG, "Initial baselines applied successfully! eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2_baseline,
tvoc_baseline);
}
void SGP30Component::dump_config() {
@ -207,8 +256,11 @@ void SGP30Component::dump_config() {
ESP_LOGCONFIG(TAG, " Warm up time: %us", this->required_warm_up_time_);
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "eCO2", this->eco2_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "eCO2 sensor", this->eco2_sensor_);
LOG_SENSOR(" ", "TVOC sensor", this->tvoc_sensor_);
LOG_SENSOR(" ", "eCO2 baseline sensor", this->eco2_sensor_baseline_);
LOG_SENSOR(" ", "TVOC baseline sensor", this->tvoc_sensor_baseline_);
ESP_LOGCONFIG(TAG, "Store baseline: %s", YESNO(this->store_baseline_));
if (this->humidity_sensor_ != nullptr && this->temperature_sensor_ != nullptr) {
ESP_LOGCONFIG(TAG, " Compensation:");
LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_);
@ -223,7 +275,7 @@ void SGP30Component::update() {
this->status_set_warning();
return;
}
this->seconds_since_last_store_ += this->update_interval_ / 1000;
this->set_timeout(50, [this]() {
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
@ -239,6 +291,11 @@ void SGP30Component::update() {
this->eco2_sensor_->publish_state(eco2);
if (this->tvoc_sensor_ != nullptr)
this->tvoc_sensor_->publish_state(tvoc);
if (this->get_update_interval() != 1000) {
ESP_LOGW(TAG, "Update interval for SGP30 sensor must be set to 1s for optimized readout");
}
this->status_clear_warning();
this->send_env_data_();
this->read_iaq_baseline_();

View file

@ -3,16 +3,25 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/preferences.h"
#include <cmath>
namespace esphome {
namespace sgp30 {
struct SGP30Baselines {
uint16_t eco2;
uint16_t tvoc;
} PACKED;
/// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors.
class SGP30Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
void set_eco2_baseline_sensor(sensor::Sensor *eco2_baseline) { eco2_sensor_baseline_ = eco2_baseline; }
void set_tvoc_baseline_sensor(sensor::Sensor *tvoc_baseline) { tvoc_sensor_baseline_ = tvoc_baseline; }
void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; }
void set_eco2_baseline(uint16_t eco2_baseline) { eco2_baseline_ = eco2_baseline; }
void set_tvoc_baseline(uint16_t tvoc_baseline) { tvoc_baseline_ = tvoc_baseline; }
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
@ -34,6 +43,9 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice {
uint64_t serial_number_;
uint16_t featureset_;
uint32_t required_warm_up_time_;
uint32_t seconds_since_last_store_;
SGP30Baselines baselines_storage_;
ESPPreferenceObject pref_;
enum ErrorCode {
COMMUNICATION_FAILED,
@ -45,8 +57,12 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice {
sensor::Sensor *eco2_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
sensor::Sensor *eco2_sensor_baseline_{nullptr};
sensor::Sensor *tvoc_sensor_baseline_{nullptr};
uint16_t eco2_baseline_{0x0000};
uint16_t tvoc_baseline_{0x0000};
bool store_baseline_;
/// Input sensor for humidity and temperature compensation.
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};

View file

@ -77,6 +77,7 @@ CONF_AVAILABILITY = "availability"
CONF_AWAY = "away"
CONF_AWAY_CONFIG = "away_config"
CONF_BACKLIGHT_PIN = "backlight_pin"
CONF_BASELINE = "baseline"
CONF_BATTERY_LEVEL = "battery_level"
CONF_BATTERY_VOLTAGE = "battery_voltage"
CONF_BAUD_RATE = "baud_rate"
@ -189,6 +190,7 @@ CONF_DUMP = "dump"
CONF_DURATION = "duration"
CONF_EAP = "eap"
CONF_ECHO_PIN = "echo_pin"
CONF_ECO2 = "eco2"
CONF_EFFECT = "effect"
CONF_EFFECTS = "effects"
CONF_ELSE = "else"