diff --git a/lib/lib_i2c/arduino-i2c-sgp41/src/SensirionI2CSgp4x.cpp b/lib/lib_i2c/arduino-i2c-sgp41/src/SensirionI2CSgp4x.cpp index 7395d4a51055..a45bf7d3fb9f 100644 --- a/lib/lib_i2c/arduino-i2c-sgp41/src/SensirionI2CSgp4x.cpp +++ b/lib/lib_i2c/arduino-i2c-sgp41/src/SensirionI2CSgp4x.cpp @@ -178,6 +178,28 @@ uint16_t SensirionI2CSgp4x::readSelfTestValue(uint16_t& testResult) { return error; } +uint16_t SensirionI2CSgp4x::getFeaturesValue(uint16_t& featureResult) { + uint16_t error; + uint8_t buffer[3]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x202F, buffer, 3); + + error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame, + *_i2cBus); + + if (error) { + return error; + } + + delay(1); // 1ms delay for feature request + SensirionI2CRxFrame rxFrame(buffer, 3); + error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 3, + rxFrame, *_i2cBus); + + error |= rxFrame.getUInt16(featureResult); + return error; +} + uint16_t SensirionI2CSgp4x::turnHeaterOff() { uint16_t error; uint8_t buffer[2]; diff --git a/lib/lib_i2c/arduino-i2c-sgp41/src/SensirionI2CSgp4x.h b/lib/lib_i2c/arduino-i2c-sgp41/src/SensirionI2CSgp4x.h index b03bed3f4f1f..460ef7aa141c 100644 --- a/lib/lib_i2c/arduino-i2c-sgp41/src/SensirionI2CSgp4x.h +++ b/lib/lib_i2c/arduino-i2c-sgp41/src/SensirionI2CSgp4x.h @@ -120,6 +120,8 @@ class SensirionI2CSgp4x { uint16_t sendSelfTestCmd(void); uint16_t readSelfTestValue(uint16_t& testResult); + uint16_t getFeaturesValue(uint16_t& featureResult); + /** * turnHeaterOff() - This command turns the hotplate off and stops the * measurement. Subsequently, the sensor enters the idle mode. diff --git a/tasmota/tasmota_xsns_sensor/xsns_109_sgp4x.ino b/tasmota/tasmota_xsns_sensor/xsns_109_sgp4x.ino index 37d73e53852a..b58898b505ee 100644 --- a/tasmota/tasmota_xsns_sensor/xsns_109_sgp4x.ino +++ b/tasmota/tasmota_xsns_sensor/xsns_109_sgp4x.ino @@ -39,10 +39,14 @@ extern "C" { #include "sensirion_gas_index_algorithm.h" }; +enum SGP4X_Type { + TYPE_SGP41, + TYPE_SGP40, +}; + enum SGP4X_State { STATE_SGP4X_START, STATE_SGP4X_SELFTEST_SENT, - STATE_SGP4X_SELFTEST_WAIT, STATE_SGP4X_SELFTEST_DONE, STATE_SGP4X_COND_SENT, STATE_SGP4X_COND_DONE, @@ -51,6 +55,7 @@ enum SGP4X_State { }; SensirionI2CSgp4x sgp4x; SGP4X_State sgp4x_state = STATE_SGP4X_START; +SGP4X_Type sgp4x_type = TYPE_SGP41; bool sgp4x_init = false; bool sgp4x_read_pend = false; @@ -60,6 +65,7 @@ uint16_t srawNox; int32_t voc_index_sgp4x; int32_t nox_index_sgp4x; +uint16_t selftest_wait = 2; // 2x 250ms cycles (500ms) delay for self-testing uint16_t conditioning_s = 10; // 10 second delay for startup GasIndexAlgorithmParams voc_algorithm_params; @@ -76,22 +82,41 @@ void sgp4x_Init(void) uint16_t error; sgp4x.begin(Wire); - error = sgp4x.getSerialNumber(serialNumber, serialNumberSize); if (error) { + AddLog(LOG_LEVEL_INFO, PSTR("SGP4X error: error during getserialnumber")); Sgp4xHandleError(error); return; } else { AddLog(LOG_LEVEL_INFO, PSTR("SGP4X serial nr 0x%X 0x%X 0x%X") ,serialNumber[0], serialNumber[1], serialNumber[2]); } + // The command is still active for both SGP40 and SGP41 using 0x202f to distinguish them. + uint16_t features; + error = sgp4x.getFeaturesValue(features); + if (error) { + AddLog(LOG_LEVEL_INFO, PSTR("SGP4X error: error during getFeaturesValue")); + Sgp4xHandleError(error); + return; + } + AddLog(LOG_LEVEL_INFO, PSTR("SGP4X features: 0x%X") ,features); + + // Undocumented, but confirmed by Sensirion + // SGP40 is 0x3240, SGP41 is 0x0240 + if (features == 0x3240) { + sgp4x_type = TYPE_SGP40; + // SGP40 doesn't do conditioning, so skip + sgp4x_state = STATE_SGP4X_NORMAL; + } + I2cSetActiveFound(SGP4X_ADDRESS, "SGP4X"); sgp4x_init = true; error = sgp4x.sendSelfTestCmd(); if (error) { + AddLog(LOG_LEVEL_INFO, PSTR("SGP4X error: error during selftest")); Sgp4xHandleError(error); sgp4x_state = STATE_SGP4X_FAIL; return; @@ -120,11 +145,11 @@ void Sgp4xSendReadCmd(void) uint16_t tempticks = (uint16_t)(((TasmotaGlobal.temperature_celsius + 45) * 65535) / 175); // Handle self testing - // Wait 1 cycle (at least 320ms to read selftest value) - if (sgp4x_state == STATE_SGP4X_SELFTEST_SENT) { - sgp4x_state = STATE_SGP4X_SELFTEST_WAIT; + // Wait 2 cycles (at least 500ms to read selftest value) + if (sgp4x_state == STATE_SGP4X_SELFTEST_SENT and selftest_wait > 0) { + selftest_wait--; return; - } else if (sgp4x_state == STATE_SGP4X_SELFTEST_WAIT) { + } else if (sgp4x_state == STATE_SGP4X_SELFTEST_SENT) { if (Sgp4xReadSelfTest()){ sgp4x_state = STATE_SGP4X_FAIL; return; @@ -150,7 +175,11 @@ void Sgp4xSendReadCmd(void) // Normal operation if (sgp4x_state == STATE_SGP4X_NORMAL) { - error = sgp4x.sendRawSignalsCmd(rhticks, tempticks); + if (sgp4x_type == TYPE_SGP41) { + error = sgp4x.sendRawSignalsCmd(rhticks, tempticks); + } else { + error = sgp4x.sendRawSignalCmd(rhticks, tempticks); + } if (error) { Sgp4xHandleError(error); } else { @@ -182,7 +211,12 @@ void Sgp4xUpdate(void) { uint16_t error; - // Conditioning - NOx needs 10s to warmup + if (sgp4x_state == STATE_SGP4X_FAIL) { + AddLog(LOG_LEVEL_DEBUG, PSTR("SGP4X in FAIL state")); + return; + } + + // Conditioning - SGP41 NOx needs 10s to warmup if (sgp4x_state == STATE_SGP4X_COND_SENT) { if (conditioning_s > 0) { conditioning_s--; @@ -193,12 +227,21 @@ void Sgp4xUpdate(void) } if (sgp4x_state == STATE_SGP4X_NORMAL && sgp4x_read_pend) { - error = sgp4x.readRawSignalsValue(srawVoc, srawNox); + if (sgp4x_type == TYPE_SGP41) { + error = sgp4x.readRawSignalsValue(srawVoc, srawNox); + } else { + error = sgp4x.readRawSignalValue(srawVoc); + } sgp4x_read_pend = false; if (!error) { + // SGP40 and SGP41 support VOC GasIndexAlgorithm_process(&voc_algorithm_params, srawVoc, &voc_index_sgp4x); - GasIndexAlgorithm_process(&nox_algorithm_params, srawNox, &nox_index_sgp4x); + + // SGP41 supports NOx + if (sgp4x_type == TYPE_SGP41) { + GasIndexAlgorithm_process(&nox_algorithm_params, srawNox, &nox_index_sgp4x); + } } else { Sgp4xHandleError(error); } @@ -206,25 +249,37 @@ void Sgp4xUpdate(void) } #ifdef USE_WEBSERVER -const char HTTP_SNS_SGP4X[] PROGMEM = - "{s}SGP4X " D_TVOC "_" D_JSON_RAW "{m}%d " "{e}" // {s} =