mirror of
https://github.com/esphome/esphome.git
synced 2024-12-02 19:54:14 +01:00
ac0d921413
* Socket refactor and SSL * esp-idf temp * Fixes * Echo component and noise * Add noise API transport support * Updates * ESP-IDF * Complete * Fixes * Fixes * Versions update * New i2c APIs * Complete i2c refactor * SPI migration * Revert ESP Preferences migration, too complex for now * OTA support * Remove echo again * Remove ssl again * GPIOFlags updates * Rename esphal and ICACHE_RAM_ATTR * Make ESP32 arduino compilable again * Fix GPIO flags * Complete pin registry refactor and fixes * Fixes to make test1 compile * Remove sdkconfig file * Ignore sdkconfig file * Fixes in reviewing * Make test2 compile * Make test4 compile * Make test5 compile * Run clang-format * Fix lint errors * Use esp-idf APIs instead of btStart * Another round of fixes * Start implementing ESP8266 * Make test3 compile * Guard esp8266 code * Lint * Reformat * Fixes * Fixes v2 * more fixes * ESP-IDF tidy target * Convert ARDUINO_ARCH_ESPxx * Update WiFiSignalSensor * Update time ifdefs * OTA needs millis from hal * RestartSwitch needs delay from hal * ESP-IDF Uart * Fix OTA blank password * Allow setting sdkconfig * Fix idf partitions and allow setting sdkconfig from yaml * Re-add read/write compat APIs and fix esp8266 uart * Fix esp8266 store log strings in flash * Fix ESP32 arduino preferences not initialized * Update ifdefs * Change how sdkconfig change is detected * Add checks to ci-custom and fix them * Run clang-format * Add esp-idf clang-tidy target and fix errors * Fixes from clang-tidy idf round 2 * Fixes from compiling tests with esp-idf * Run clang-format * Switch test5.yaml to esp-idf * Implement ESP8266 Preferences * Lint * Re-do PIO package version selection a bit * Fix arduinoespressif32 package version * Fix unit tests * Lint * Lint fixes * Fix readv/writev not defined * Fix graphing component * Re-add all old options from core/config.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
324 lines
9.3 KiB
C++
324 lines
9.3 KiB
C++
#include "sgp40.h"
|
|
#include "esphome/core/log.h"
|
|
#include "esphome/core/hal.h"
|
|
#include <cinttypes>
|
|
|
|
namespace esphome {
|
|
namespace sgp40 {
|
|
|
|
static const char *const TAG = "sgp40";
|
|
|
|
void SGP40Component::setup() {
|
|
ESP_LOGCONFIG(TAG, "Setting up SGP40...");
|
|
|
|
// Serial Number identification
|
|
if (!this->write_command_(SGP40_CMD_GET_SERIAL_ID)) {
|
|
this->error_code_ = COMMUNICATION_FAILED;
|
|
this->mark_failed();
|
|
return;
|
|
}
|
|
uint16_t raw_serial_number[3];
|
|
|
|
if (!this->read_data_(raw_serial_number, 3)) {
|
|
this->mark_failed();
|
|
return;
|
|
}
|
|
this->serial_number_ = (uint64_t(raw_serial_number[0]) << 24) | (uint64_t(raw_serial_number[1]) << 16) |
|
|
(uint64_t(raw_serial_number[2]));
|
|
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
|
|
|
|
// Featureset identification for future use
|
|
if (!this->write_command_(SGP40_CMD_GET_FEATURESET)) {
|
|
ESP_LOGD(TAG, "raw_featureset write_command_ failed");
|
|
this->mark_failed();
|
|
return;
|
|
}
|
|
uint16_t raw_featureset[1];
|
|
if (!this->read_data_(raw_featureset, 1)) {
|
|
ESP_LOGD(TAG, "raw_featureset read_data_ failed");
|
|
this->mark_failed();
|
|
return;
|
|
}
|
|
|
|
this->featureset_ = raw_featureset[0];
|
|
if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) {
|
|
ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF),
|
|
SGP40_FEATURESET);
|
|
this->mark_failed();
|
|
return;
|
|
}
|
|
|
|
ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF));
|
|
|
|
voc_algorithm_init(&this->voc_algorithm_params_);
|
|
|
|
if (this->store_baseline_) {
|
|
// 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<SGP40Baselines>(hash, true);
|
|
|
|
if (this->pref_.load(&this->baselines_storage_)) {
|
|
this->state0_ = this->baselines_storage_.state0;
|
|
this->state1_ = this->baselines_storage_.state1;
|
|
ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04X, state1: 0x%04X", this->baselines_storage_.state0,
|
|
baselines_storage_.state1);
|
|
}
|
|
|
|
// Initialize storage timestamp
|
|
this->seconds_since_last_store_ = 0;
|
|
|
|
if (this->baselines_storage_.state0 > 0 && this->baselines_storage_.state1 > 0) {
|
|
ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04X, state1: 0x%04X", this->baselines_storage_.state0,
|
|
baselines_storage_.state1);
|
|
voc_algorithm_set_states(&this->voc_algorithm_params_, this->baselines_storage_.state0,
|
|
this->baselines_storage_.state1);
|
|
}
|
|
}
|
|
|
|
this->self_test_();
|
|
}
|
|
|
|
void SGP40Component::self_test_() {
|
|
ESP_LOGD(TAG, "Self-test started");
|
|
if (!this->write_command_(SGP40_CMD_SELF_TEST)) {
|
|
this->error_code_ = COMMUNICATION_FAILED;
|
|
ESP_LOGD(TAG, "Self-test communication failed");
|
|
this->mark_failed();
|
|
}
|
|
|
|
this->set_timeout(250, [this]() {
|
|
uint16_t reply[1];
|
|
if (!this->read_data_(reply, 1)) {
|
|
ESP_LOGD(TAG, "Self-test read_data_ failed");
|
|
this->mark_failed();
|
|
return;
|
|
}
|
|
|
|
if (reply[0] == 0xD400) {
|
|
this->self_test_complete_ = true;
|
|
ESP_LOGD(TAG, "Self-test completed");
|
|
return;
|
|
}
|
|
|
|
ESP_LOGD(TAG, "Self-test failed");
|
|
this->mark_failed();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @brief Combined the measured gasses, temperature, and humidity
|
|
* to calculate the VOC Index
|
|
*
|
|
* @param temperature The measured temperature in degrees C
|
|
* @param humidity The measured relative humidity in % rH
|
|
* @return int32_t The VOC Index
|
|
*/
|
|
int32_t SGP40Component::measure_voc_index_() {
|
|
int32_t voc_index;
|
|
|
|
uint16_t sraw = measure_raw_();
|
|
|
|
if (sraw == UINT16_MAX)
|
|
return UINT16_MAX;
|
|
|
|
this->status_clear_warning();
|
|
|
|
voc_algorithm_process(&voc_algorithm_params_, sraw, &voc_index);
|
|
|
|
// 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) {
|
|
voc_algorithm_get_states(&voc_algorithm_params_, &this->state0_, &this->state1_);
|
|
if (abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF ||
|
|
abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) {
|
|
this->seconds_since_last_store_ = 0;
|
|
this->baselines_storage_.state0 = this->state0_;
|
|
this->baselines_storage_.state1 = this->state1_;
|
|
|
|
if (this->pref_.save(&this->baselines_storage_)) {
|
|
ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04X ,state1: 0x%04X", this->baselines_storage_.state0,
|
|
baselines_storage_.state1);
|
|
} else {
|
|
ESP_LOGW(TAG, "Could not store VOC baselines");
|
|
}
|
|
}
|
|
}
|
|
|
|
return voc_index;
|
|
}
|
|
|
|
/**
|
|
* @brief Return the raw gas measurement
|
|
*
|
|
* @param temperature The measured temperature in degrees C
|
|
* @param humidity The measured relative humidity in % rH
|
|
* @return uint16_t The current raw gas measurement
|
|
*/
|
|
uint16_t SGP40Component::measure_raw_() {
|
|
float humidity = NAN;
|
|
|
|
if (!this->self_test_complete_) {
|
|
ESP_LOGD(TAG, "Self-test not yet complete");
|
|
return UINT16_MAX;
|
|
}
|
|
|
|
if (this->humidity_sensor_ != nullptr) {
|
|
humidity = this->humidity_sensor_->state;
|
|
}
|
|
if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) {
|
|
humidity = 50;
|
|
}
|
|
|
|
float temperature = NAN;
|
|
if (this->temperature_sensor_ != nullptr) {
|
|
temperature = float(this->temperature_sensor_->state);
|
|
}
|
|
if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) {
|
|
temperature = 25;
|
|
}
|
|
|
|
uint8_t command[8];
|
|
|
|
command[0] = 0x26;
|
|
command[1] = 0x0F;
|
|
|
|
uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100));
|
|
command[2] = rhticks >> 8;
|
|
command[3] = rhticks & 0xFF;
|
|
command[4] = generate_crc_(command + 2, 2);
|
|
uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175);
|
|
command[5] = tempticks >> 8;
|
|
command[6] = tempticks & 0xFF;
|
|
command[7] = generate_crc_(command + 5, 2);
|
|
|
|
if (this->write(command, 8) != i2c::ERROR_OK) {
|
|
this->status_set_warning();
|
|
ESP_LOGD(TAG, "write error");
|
|
return UINT16_MAX;
|
|
}
|
|
delay(250); // NOLINT
|
|
uint16_t raw_data[1];
|
|
|
|
if (!this->read_data_(raw_data, 1)) {
|
|
this->status_set_warning();
|
|
ESP_LOGD(TAG, "read_data_ error");
|
|
return UINT16_MAX;
|
|
}
|
|
return raw_data[0];
|
|
}
|
|
|
|
uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) {
|
|
// calculates 8-Bit checksum with given polynomial
|
|
uint8_t crc = SGP40_CRC8_INIT;
|
|
|
|
for (uint8_t i = 0; i < datalen; i++) {
|
|
crc ^= data[i];
|
|
for (uint8_t b = 0; b < 8; b++) {
|
|
if (crc & 0x80)
|
|
crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL;
|
|
else
|
|
crc <<= 1;
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
void SGP40Component::update() {
|
|
this->seconds_since_last_store_ += this->update_interval_ / 1000;
|
|
|
|
uint32_t voc_index = this->measure_voc_index_();
|
|
|
|
if (this->samples_read_ < this->samples_to_stabalize_) {
|
|
this->samples_read_++;
|
|
ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %u", this->samples_read_,
|
|
this->samples_to_stabalize_, voc_index);
|
|
return;
|
|
}
|
|
|
|
if (voc_index != UINT16_MAX) {
|
|
this->status_clear_warning();
|
|
this->publish_state(voc_index);
|
|
} else {
|
|
this->status_set_warning();
|
|
}
|
|
}
|
|
|
|
void SGP40Component::dump_config() {
|
|
ESP_LOGCONFIG(TAG, "SGP40:");
|
|
LOG_I2C_DEVICE(this);
|
|
if (this->is_failed()) {
|
|
switch (this->error_code_) {
|
|
case COMMUNICATION_FAILED:
|
|
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
|
|
break;
|
|
default:
|
|
ESP_LOGW(TAG, "Unknown setup error!");
|
|
break;
|
|
}
|
|
} else {
|
|
ESP_LOGCONFIG(TAG, " Serial number: %" PRIu64, this->serial_number_);
|
|
ESP_LOGCONFIG(TAG, " Minimum Samples: %f", VOC_ALGORITHM_INITIAL_BLACKOUT);
|
|
}
|
|
LOG_UPDATE_INTERVAL(this);
|
|
|
|
if (this->humidity_sensor_ != nullptr && this->temperature_sensor_ != nullptr) {
|
|
ESP_LOGCONFIG(TAG, " Compensation:");
|
|
LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_);
|
|
LOG_SENSOR(" ", "Humidity Source:", this->humidity_sensor_);
|
|
} else {
|
|
ESP_LOGCONFIG(TAG, " Compensation: No source configured");
|
|
}
|
|
}
|
|
|
|
bool SGP40Component::write_command_(uint16_t command) {
|
|
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
|
return this->write_byte(command >> 8, command & 0xFF);
|
|
}
|
|
|
|
uint8_t SGP40Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
|
uint8_t bit;
|
|
uint8_t crc = 0xFF;
|
|
|
|
crc ^= data1;
|
|
for (bit = 8; bit > 0; --bit) {
|
|
if (crc & 0x80)
|
|
crc = (crc << 1) ^ 0x131;
|
|
else
|
|
crc = (crc << 1);
|
|
}
|
|
|
|
crc ^= data2;
|
|
for (bit = 8; bit > 0; --bit) {
|
|
if (crc & 0x80)
|
|
crc = (crc << 1) ^ 0x131;
|
|
else
|
|
crc = (crc << 1);
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
bool SGP40Component::read_data_(uint16_t *data, uint8_t len) {
|
|
const uint8_t num_bytes = len * 3;
|
|
std::vector<uint8_t> buf(num_bytes);
|
|
|
|
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
|
return false;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < len; i++) {
|
|
const uint8_t j = 3 * i;
|
|
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
|
if (crc != buf[j + 2]) {
|
|
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
|
return false;
|
|
}
|
|
data[i] = (buf[j] << 8) | buf[j + 1];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace sgp40
|
|
} // namespace esphome
|