mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 21:44:55 +01:00
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:
parent
551e9c6111
commit
7dd16df846
5 changed files with 105 additions and 11 deletions
|
@ -8,6 +8,8 @@ from esphome.const import (
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_PARTS_PER_MILLION,
|
UNIT_PARTS_PER_MILLION,
|
||||||
UNIT_PARTS_PER_BILLION,
|
UNIT_PARTS_PER_BILLION,
|
||||||
|
CONF_BASELINE,
|
||||||
|
CONF_ECO2,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
CONF_TVOC,
|
CONF_TVOC,
|
||||||
CONF_HUMIDITY,
|
CONF_HUMIDITY,
|
||||||
|
@ -21,9 +23,6 @@ CCS811Component = ccs811_ns.class_(
|
||||||
"CCS811Component", cg.PollingComponent, i2c.I2CDevice
|
"CCS811Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
)
|
)
|
||||||
|
|
||||||
CONF_ECO2 = "eco2"
|
|
||||||
CONF_BASELINE = "baseline"
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,13 +3,16 @@ import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
|
CONF_BASELINE,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
|
CONF_ECO2,
|
||||||
|
CONF_TVOC,
|
||||||
ICON_RADIATOR,
|
ICON_RADIATOR,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_PARTS_PER_MILLION,
|
UNIT_PARTS_PER_MILLION,
|
||||||
UNIT_PARTS_PER_BILLION,
|
UNIT_PARTS_PER_BILLION,
|
||||||
|
UNIT_EMPTY,
|
||||||
ICON_MOLECULE_CO2,
|
ICON_MOLECULE_CO2,
|
||||||
CONF_TVOC,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
@ -17,10 +20,9 @@ DEPENDENCIES = ["i2c"]
|
||||||
sgp30_ns = cg.esphome_ns.namespace("sgp30")
|
sgp30_ns = cg.esphome_ns.namespace("sgp30")
|
||||||
SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice)
|
SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
CONF_ECO2 = "eco2"
|
|
||||||
CONF_BASELINE = "baseline"
|
|
||||||
CONF_ECO2_BASELINE = "eco2_baseline"
|
CONF_ECO2_BASELINE = "eco2_baseline"
|
||||||
CONF_TVOC_BASELINE = "tvoc_baseline"
|
CONF_TVOC_BASELINE = "tvoc_baseline"
|
||||||
|
CONF_STORE_BASELINE = "store_baseline"
|
||||||
CONF_UPTIME = "uptime"
|
CONF_UPTIME = "uptime"
|
||||||
CONF_COMPENSATION = "compensation"
|
CONF_COMPENSATION = "compensation"
|
||||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||||
|
@ -44,6 +46,13 @@ CONFIG_SCHEMA = (
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
STATE_CLASS_MEASUREMENT,
|
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.Optional(CONF_BASELINE): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ECO2_BASELINE): cv.hex_uint16_t,
|
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))
|
.extend(i2c.i2c_device_schema(0x58))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -76,6 +85,17 @@ async def to_code(config):
|
||||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||||
cg.add(var.set_tvoc_sensor(sens))
|
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:
|
if CONF_BASELINE in config:
|
||||||
baseline_config = config[CONF_BASELINE]
|
baseline_config = config[CONF_BASELINE]
|
||||||
cg.add(var.set_eco2_baseline(baseline_config[CONF_ECO2_BASELINE]))
|
cg.add(var.set_eco2_baseline(baseline_config[CONF_ECO2_BASELINE]))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "sgp30.h"
|
#include "sgp30.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sgp30 {
|
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
|
// if the sensor starts without any prior baseline value provided
|
||||||
const uint32_t IAQ_BASELINE_WARM_UP_SECONDS_WITHOUT_BASELINE = 43200;
|
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() {
|
void SGP30Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
|
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
|
||||||
|
|
||||||
|
@ -73,6 +81,21 @@ void SGP30Component::setup() {
|
||||||
return;
|
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
|
// Sensor baseline reliability timer
|
||||||
if (this->eco2_baseline_ > 0 && this->tvoc_baseline_ > 0) {
|
if (this->eco2_baseline_ > 0 && this->tvoc_baseline_ > 0) {
|
||||||
this->required_warm_up_time_ = IAQ_BASELINE_WARM_UP_SECONDS_WITH_BASELINE_PROVIDED;
|
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]);
|
uint16_t tvocbaseline = (raw_data[1]);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Current eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2baseline, tvocbaseline);
|
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();
|
this->status_clear_warning();
|
||||||
});
|
});
|
||||||
} else {
|
} 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)) {
|
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);
|
ESP_LOGE(TAG, "Error applying eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2_baseline, tvoc_baseline);
|
||||||
} else
|
} 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() {
|
void SGP30Component::dump_config() {
|
||||||
|
@ -207,8 +256,11 @@ void SGP30Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " Warm up time: %us", this->required_warm_up_time_);
|
ESP_LOGCONFIG(TAG, " Warm up time: %us", this->required_warm_up_time_);
|
||||||
}
|
}
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
LOG_SENSOR(" ", "eCO2", this->eco2_sensor_);
|
LOG_SENSOR(" ", "eCO2 sensor", this->eco2_sensor_);
|
||||||
LOG_SENSOR(" ", "TVOC", this->tvoc_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) {
|
if (this->humidity_sensor_ != nullptr && this->temperature_sensor_ != nullptr) {
|
||||||
ESP_LOGCONFIG(TAG, " Compensation:");
|
ESP_LOGCONFIG(TAG, " Compensation:");
|
||||||
LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_);
|
LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_);
|
||||||
|
@ -223,7 +275,7 @@ void SGP30Component::update() {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->seconds_since_last_store_ += this->update_interval_ / 1000;
|
||||||
this->set_timeout(50, [this]() {
|
this->set_timeout(50, [this]() {
|
||||||
uint16_t raw_data[2];
|
uint16_t raw_data[2];
|
||||||
if (!this->read_data_(raw_data, 2)) {
|
if (!this->read_data_(raw_data, 2)) {
|
||||||
|
@ -239,6 +291,11 @@ void SGP30Component::update() {
|
||||||
this->eco2_sensor_->publish_state(eco2);
|
this->eco2_sensor_->publish_state(eco2);
|
||||||
if (this->tvoc_sensor_ != nullptr)
|
if (this->tvoc_sensor_ != nullptr)
|
||||||
this->tvoc_sensor_->publish_state(tvoc);
|
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->status_clear_warning();
|
||||||
this->send_env_data_();
|
this->send_env_data_();
|
||||||
this->read_iaq_baseline_();
|
this->read_iaq_baseline_();
|
||||||
|
|
|
@ -3,16 +3,25 @@
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sgp30 {
|
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.
|
/// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors.
|
||||||
class SGP30Component : public PollingComponent, public i2c::I2CDevice {
|
class SGP30Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
|
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
|
||||||
void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
|
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_eco2_baseline(uint16_t eco2_baseline) { eco2_baseline_ = eco2_baseline; }
|
||||||
void set_tvoc_baseline(uint16_t tvoc_baseline) { tvoc_baseline_ = tvoc_baseline; }
|
void set_tvoc_baseline(uint16_t tvoc_baseline) { tvoc_baseline_ = tvoc_baseline; }
|
||||||
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
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_;
|
uint64_t serial_number_;
|
||||||
uint16_t featureset_;
|
uint16_t featureset_;
|
||||||
uint32_t required_warm_up_time_;
|
uint32_t required_warm_up_time_;
|
||||||
|
uint32_t seconds_since_last_store_;
|
||||||
|
SGP30Baselines baselines_storage_;
|
||||||
|
ESPPreferenceObject pref_;
|
||||||
|
|
||||||
enum ErrorCode {
|
enum ErrorCode {
|
||||||
COMMUNICATION_FAILED,
|
COMMUNICATION_FAILED,
|
||||||
|
@ -45,8 +57,12 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
|
||||||
sensor::Sensor *eco2_sensor_{nullptr};
|
sensor::Sensor *eco2_sensor_{nullptr};
|
||||||
sensor::Sensor *tvoc_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 eco2_baseline_{0x0000};
|
||||||
uint16_t tvoc_baseline_{0x0000};
|
uint16_t tvoc_baseline_{0x0000};
|
||||||
|
bool store_baseline_;
|
||||||
|
|
||||||
/// Input sensor for humidity and temperature compensation.
|
/// Input sensor for humidity and temperature compensation.
|
||||||
sensor::Sensor *humidity_sensor_{nullptr};
|
sensor::Sensor *humidity_sensor_{nullptr};
|
||||||
sensor::Sensor *temperature_sensor_{nullptr};
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
|
|
|
@ -77,6 +77,7 @@ CONF_AVAILABILITY = "availability"
|
||||||
CONF_AWAY = "away"
|
CONF_AWAY = "away"
|
||||||
CONF_AWAY_CONFIG = "away_config"
|
CONF_AWAY_CONFIG = "away_config"
|
||||||
CONF_BACKLIGHT_PIN = "backlight_pin"
|
CONF_BACKLIGHT_PIN = "backlight_pin"
|
||||||
|
CONF_BASELINE = "baseline"
|
||||||
CONF_BATTERY_LEVEL = "battery_level"
|
CONF_BATTERY_LEVEL = "battery_level"
|
||||||
CONF_BATTERY_VOLTAGE = "battery_voltage"
|
CONF_BATTERY_VOLTAGE = "battery_voltage"
|
||||||
CONF_BAUD_RATE = "baud_rate"
|
CONF_BAUD_RATE = "baud_rate"
|
||||||
|
@ -189,6 +190,7 @@ CONF_DUMP = "dump"
|
||||||
CONF_DURATION = "duration"
|
CONF_DURATION = "duration"
|
||||||
CONF_EAP = "eap"
|
CONF_EAP = "eap"
|
||||||
CONF_ECHO_PIN = "echo_pin"
|
CONF_ECHO_PIN = "echo_pin"
|
||||||
|
CONF_ECO2 = "eco2"
|
||||||
CONF_EFFECT = "effect"
|
CONF_EFFECT = "effect"
|
||||||
CONF_EFFECTS = "effects"
|
CONF_EFFECTS = "effects"
|
||||||
CONF_ELSE = "else"
|
CONF_ELSE = "else"
|
||||||
|
|
Loading…
Reference in a new issue