Fix SGP30 incorrect baseline reading/writing (#936)

* Split the SGP30 baseline into 2 values

- According to the SGP30 datasheet, each eCO2 and TVOC baseline is a 2-byte value (MSB first)
- The current implementation ignores the MSB of each of the value
- Update the schema to allow 2 different baseline values (optional, but both need to be specified for the baseline to apply)

* Make both eCO2 and TVOC required if the optional baseline is defined

* Make dump_config() looks better
This commit is contained in:
Panuruj Khambanonda (PK) 2020-01-12 07:42:18 -08:00 committed by Otto Winter
parent 92d93d658c
commit 170d52e0db
3 changed files with 37 additions and 22 deletions

View File

@ -12,6 +12,8 @@ SGP30Component = sgp30_ns.class_('SGP30Component', cg.PollingComponent, i2c.I2CD
CONF_ECO2 = 'eco2' CONF_ECO2 = 'eco2'
CONF_TVOC = 'tvoc' CONF_TVOC = 'tvoc'
CONF_BASELINE = 'baseline' CONF_BASELINE = 'baseline'
CONF_ECO2_BASELINE = 'eco2_baseline'
CONF_TVOC_BASELINE = 'tvoc_baseline'
CONF_UPTIME = 'uptime' CONF_UPTIME = 'uptime'
CONF_COMPENSATION = 'compensation' CONF_COMPENSATION = 'compensation'
CONF_HUMIDITY_SOURCE = 'humidity_source' CONF_HUMIDITY_SOURCE = 'humidity_source'
@ -22,7 +24,10 @@ CONFIG_SCHEMA = cv.Schema({
cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION,
ICON_PERIODIC_TABLE_CO2, 0), ICON_PERIODIC_TABLE_CO2, 0),
cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
cv.Optional(CONF_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_BASELINE): cv.Schema({
cv.Required(CONF_ECO2_BASELINE): cv.hex_uint16_t,
cv.Required(CONF_TVOC_BASELINE): cv.hex_uint16_t,
}),
cv.Optional(CONF_COMPENSATION): cv.Schema({ cv.Optional(CONF_COMPENSATION): cv.Schema({
cv.Required(CONF_HUMIDITY_SOURCE): cv.use_id(sensor.Sensor), cv.Required(CONF_HUMIDITY_SOURCE): cv.use_id(sensor.Sensor),
cv.Required(CONF_TEMPERATURE_SOURCE): cv.use_id(sensor.Sensor) cv.Required(CONF_TEMPERATURE_SOURCE): cv.use_id(sensor.Sensor)
@ -44,7 +49,9 @@ def to_code(config):
cg.add(var.set_tvoc_sensor(sens)) cg.add(var.set_tvoc_sensor(sens))
if CONF_BASELINE in config: if CONF_BASELINE in config:
cg.add(var.set_baseline(config[CONF_BASELINE])) baseline_config = config[CONF_BASELINE]
cg.add(var.set_eco2_baseline(baseline_config[CONF_ECO2_BASELINE]))
cg.add(var.set_tvoc_baseline(baseline_config[CONF_TVOC_BASELINE]))
if CONF_COMPENSATION in config: if CONF_COMPENSATION in config:
compensation_config = config[CONF_COMPENSATION] compensation_config = config[CONF_COMPENSATION]

View File

@ -74,9 +74,9 @@ void SGP30Component::setup() {
} }
// Sensor baseline reliability timer // Sensor baseline reliability timer
if (this->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;
this->write_iaq_baseline_(this->baseline_); this->write_iaq_baseline_(this->eco2_baseline_, this->tvoc_baseline_);
} else { } else {
this->required_warm_up_time_ = IAQ_BASELINE_WARM_UP_SECONDS_WITHOUT_BASELINE; this->required_warm_up_time_ = IAQ_BASELINE_WARM_UP_SECONDS_WITHOUT_BASELINE;
} }
@ -106,10 +106,10 @@ void SGP30Component::read_iaq_baseline_() {
return; return;
} }
uint8_t eco2baseline = (raw_data[0]); uint16_t eco2baseline = (raw_data[0]);
uint8_t tvocbaseline = (raw_data[1]); uint16_t tvocbaseline = (raw_data[1]);
ESP_LOGI(TAG, "Current eCO2 & TVOC baseline: 0x%04X", uint16_t((eco2baseline << 8) | (tvocbaseline & 0xFF))); ESP_LOGI(TAG, "Current eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2baseline, tvocbaseline);
this->status_clear_warning(); this->status_clear_warning();
}); });
} else { } else {
@ -159,18 +159,19 @@ void SGP30Component::send_env_data_() {
} }
} }
void SGP30Component::write_iaq_baseline_(uint16_t baseline) { void SGP30Component::write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_baseline) {
uint8_t e_c_o2_baseline = baseline >> 8; uint8_t data[7];
uint8_t tvoc_baseline = baseline & 0xFF;
uint8_t data[4];
data[0] = SGP30_CMD_SET_IAQ_BASELINE & 0xFF; data[0] = SGP30_CMD_SET_IAQ_BASELINE & 0xFF;
data[1] = e_c_o2_baseline; data[1] = eco2_baseline >> 8;
data[2] = tvoc_baseline; data[2] = eco2_baseline & 0xFF;
data[3] = sht_crc_(e_c_o2_baseline, tvoc_baseline); data[3] = sht_crc_(data[1], data[2]);
if (!this->write_bytes(SGP30_CMD_SET_IAQ_BASELINE >> 8, data, 4)) { data[4] = tvoc_baseline >> 8;
ESP_LOGE(TAG, "Error applying baseline: 0x%04X", baseline); data[5] = tvoc_baseline & 0xFF;
data[6] = sht_crc_(data[4], data[5]);
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 } else
ESP_LOGI(TAG, "Initial baseline 0x%04X applied successfully!", baseline); ESP_LOGI(TAG, "Initial eCO2 and TVOC baselines applied successfully!");
} }
void SGP30Component::dump_config() { void SGP30Component::dump_config() {
@ -196,8 +197,13 @@ void SGP30Component::dump_config() {
} }
} else { } else {
ESP_LOGCONFIG(TAG, " Serial number: %llu", this->serial_number_); ESP_LOGCONFIG(TAG, " Serial number: %llu", this->serial_number_);
ESP_LOGCONFIG(TAG, " Baseline: 0x%04X%s", this->baseline_, if (this->eco2_baseline_ != 0x0000 && this->tvoc_baseline_ != 0x0000) {
((this->baseline_ != 0x0000) ? " (enabled)" : " (disabled)")); ESP_LOGCONFIG(TAG, " Baseline:");
ESP_LOGCONFIG(TAG, " eCO2 Baseline: 0x%04X", this->eco2_baseline_);
ESP_LOGCONFIG(TAG, " TVOC Baseline: 0x%04X", this->tvoc_baseline_);
} else {
ESP_LOGCONFIG(TAG, " Baseline: No baseline configured");
}
ESP_LOGCONFIG(TAG, " Warm up time: %lds", this->required_warm_up_time_); ESP_LOGCONFIG(TAG, " Warm up time: %lds", this->required_warm_up_time_);
} }
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);

View File

@ -13,7 +13,8 @@ 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_baseline(uint16_t baseline) { baseline_ = 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; } void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
@ -28,7 +29,7 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice {
void send_env_data_(); void send_env_data_();
void read_iaq_baseline_(); void read_iaq_baseline_();
bool is_sensor_baseline_reliable_(); bool is_sensor_baseline_reliable_();
void write_iaq_baseline_(uint16_t baseline); void write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_baseline);
uint8_t sht_crc_(uint8_t data1, uint8_t data2); uint8_t sht_crc_(uint8_t data1, uint8_t data2);
uint64_t serial_number_; uint64_t serial_number_;
uint16_t featureset_; uint16_t featureset_;
@ -44,7 +45,8 @@ 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};
uint16_t baseline_{0x0000}; uint16_t eco2_baseline_{0x0000};
uint16_t tvoc_baseline_{0x0000};
/// 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};