Skip to content

Commit f877b20

Browse files
committed
Adding SGP40 support to universal driver
1 parent cd1523f commit f877b20

File tree

1 file changed

+71
-13
lines changed

1 file changed

+71
-13
lines changed

tasmota/tasmota_xsns_sensor/xsns_109_sgp4x.ino

+71-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
xsns_109_sgp4x.ino - SGP4X VOC and NOx sensor support for Tasmota
2+
xsns_109_sgp4x.ino - SGP4X Universal VOC and NOx sensor support for Tasmota
33
44
Copyright (C) 2023 Andrew Klaus
55
@@ -20,7 +20,7 @@
2020
#ifdef USE_I2C
2121
#ifdef USE_SGP4X
2222
/*********************************************************************************************\
23-
* SGP4x - Gas (TVOC - Total Volatile Organic Compounds) and NOx
23+
* SGP4x - Gas (TVOC - Total Volatile Organic Compounds) and NOx (SGP41-only)
2424
*
2525
* Source: Sensirion Driver, with mods by Andrew Klaus
2626
* Adaption for TASMOTA: Andrew Klaus
@@ -39,6 +39,11 @@ extern "C" {
3939
#include "sensirion_gas_index_algorithm.h"
4040
};
4141

42+
enum SGP4X_Type {
43+
TYPE_SGP41,
44+
TYPE_SGP40,
45+
};
46+
4247
enum SGP4X_State {
4348
STATE_SGP4X_START,
4449
STATE_SGP4X_SELFTEST_SENT,
@@ -49,8 +54,10 @@ enum SGP4X_State {
4954
STATE_SGP4X_NORMAL,
5055
STATE_SGP4X_FAIL,
5156
};
57+
5258
SensirionI2CSgp4x sgp4x;
5359
SGP4X_State sgp4x_state = STATE_SGP4X_START;
60+
SGP4X_Type sgp4x_type = TYPE_SGP41;
5461

5562
bool sgp4x_init = false;
5663
bool sgp4x_read_pend = false;
@@ -133,14 +140,39 @@ void Sgp4xSendReadCmd(void)
133140
}
134141
}
135142

143+
// SGP40 device detection happens here
144+
// Sensirion provides no way to detect if device is an SGP40 or SGP41
145+
// The workaround is to first send sendConditioningCmd()
146+
// SGP40 doesn't support this, which results in a NACK
147+
// If a self-test is already successful, we can reliably assume this is an SGP40
148+
136149
// Initiate conditioning
137150
if (sgp4x_state == STATE_SGP4X_SELFTEST_DONE) {
138151
error = sgp4x.sendConditioningCmd(0x8000, 0x6666);
139152
sgp4x_state = STATE_SGP4X_COND_SENT;
140153

141154
if (error) {
142155
Sgp4xHandleError(error);
156+
157+
// If we received a write error NACK, we can assume this is an SGP40
158+
uint16_t highLevelError = error & 0xFF00;
159+
uint16_t lowLevelError = error & 0x00FF;
160+
161+
if (highLevelError == HighLevelError::WriteError and lowLevelError == LowLevelError::I2cDataNack) {
162+
sgp4x_type = TYPE_SGP40;
163+
AddLog(LOG_LEVEL_INFO, PSTR("SGP4X detected SGP40 device type"));
164+
165+
// SGP40 doesn't do conditioning, so skip
166+
sgp4x_state = STATE_SGP4X_NORMAL;
167+
} else {
168+
// Unexpected error after sending conditioning command
169+
sgp4x_state = STATE_SGP4X_FAIL;
170+
}
171+
} else {
172+
sgp4x_type = TYPE_SGP41;
173+
AddLog(LOG_LEVEL_INFO, PSTR("SGP4X detected SGP41 device type"));
143174
}
175+
144176
return;
145177
}
146178

@@ -150,7 +182,12 @@ void Sgp4xSendReadCmd(void)
150182

151183
// Normal operation
152184
if (sgp4x_state == STATE_SGP4X_NORMAL) {
153-
error = sgp4x.sendRawSignalsCmd(rhticks, tempticks);
185+
if (sgp4x_type == TYPE_SGP41) {
186+
error = sgp4x.sendRawSignalsCmd(rhticks, tempticks);
187+
} else {
188+
error = sgp4x.sendRawSignalCmd(rhticks, tempticks);
189+
}
190+
154191
if (error) {
155192
Sgp4xHandleError(error);
156193
} else {
@@ -182,7 +219,7 @@ void Sgp4xUpdate(void)
182219
{
183220
uint16_t error;
184221

185-
// Conditioning - NOx needs 10s to warmup
222+
// Conditioning - SGP41 NOx needs 10s to warmup
186223
if (sgp4x_state == STATE_SGP4X_COND_SENT) {
187224
if (conditioning_s > 0) {
188225
conditioning_s--;
@@ -193,38 +230,59 @@ void Sgp4xUpdate(void)
193230
}
194231

195232
if (sgp4x_state == STATE_SGP4X_NORMAL && sgp4x_read_pend) {
196-
error = sgp4x.readRawSignalsValue(srawVoc, srawNox);
233+
if (sgp4x_type == TYPE_SGP41) {
234+
error = sgp4x.readRawSignalsValue(srawVoc, srawNox);
235+
} else {
236+
error = sgp4x.readRawSignalValue(srawVoc);
237+
}
197238
sgp4x_read_pend = false;
198239

199240
if (!error) {
200241
GasIndexAlgorithm_process(&voc_algorithm_params, srawVoc, &voc_index_sgp4x);
201-
GasIndexAlgorithm_process(&nox_algorithm_params, srawNox, &nox_index_sgp4x);
242+
243+
// SGP41 supports NOx
244+
if (sgp4x_type == TYPE_SGP41) {
245+
GasIndexAlgorithm_process(&nox_algorithm_params, srawNox, &nox_index_sgp4x);
246+
}
202247
} else {
203248
Sgp4xHandleError(error);
204249
}
205250
}
206251
}
207252

208253
#ifdef USE_WEBSERVER
209-
const char HTTP_SNS_SGP4X[] PROGMEM =
210-
"{s}SGP4X TVOC " D_JSON_RAW "{m}%d " "{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
211-
"{s}SGP4X NOX " D_JSON_RAW "{m}%d " "{e}"
212-
"{s}SGP4X " D_TVOC "{m}%d " "{e}"
213-
"{s}SGP4X " D_NOX "{m}%d " "{e}";
254+
const char HTTP_SNS_SGP4X_SGP41[] PROGMEM =
255+
"{s}SGP41 TVOC " D_JSON_RAW "{m}%d " "{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
256+
"{s}SGP41 NOX " D_JSON_RAW "{m}%d " "{e}"
257+
"{s}SGP41 " D_TVOC "{m}%d " "{e}"
258+
"{s}SGP41 " D_NOX "{m}%d " "{e}";
259+
260+
const char HTTP_SNS_SGP4X_SGP40[] PROGMEM =
261+
"{s}SGP40 TVOC " D_JSON_RAW "{m}%d " "{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
262+
"{s}SGP40 " D_TVOC "{m}%d " "{e}";
214263
#endif
215264

216265
void Sgp4xShow(bool json)
217266
{
218267
if (sgp4x_state == STATE_SGP4X_NORMAL) {
219268
if (json) {
220-
ResponseAppend_P(PSTR(",\"SGP4X\":{\"VOC_" D_JSON_RAW "\":%d,\"NOX_" D_JSON_RAW "\":%d,\"" D_TVOC "\":%d,\"" D_NOX "\":%d"), srawVoc, srawNox, voc_index_sgp4x, nox_index_sgp4x);
269+
if (sgp4x_type == TYPE_SGP41) {
270+
ResponseAppend_P(PSTR(",\"SGP40\":{\"VOC_" D_JSON_RAW "\":%d,\"NOX_" D_JSON_RAW "\":%d,\"" D_TVOC "\":%d,\"" D_NOX "\":%d"), srawVoc, srawNox, voc_index_sgp4x, nox_index_sgp4x);
271+
} else {
272+
ResponseAppend_P(PSTR(",\"SGP41\":{\"VOC_" D_JSON_RAW "\":%d,,\"" D_TVOC "\":%d,"), srawVoc, voc_index_sgp4x);
273+
}
221274
ResponseJsonEnd();
222275
#ifdef USE_DOMOTICZ
223276
if (0 == TasmotaGlobal.tele_period) DomoticzSensor(DZ_AIRQUALITY, srawVoc);
224277
#endif // USE_DOMOTICZ
225278
#ifdef USE_WEBSERVER
226279
} else {
227-
WSContentSend_PD(HTTP_SNS_SGP4X, srawVoc, srawNox, voc_index_sgp4x, nox_index_sgp4x);
280+
if (sgp4x_type == TYPE_SGP41) {
281+
WSContentSend_PD(HTTP_SNS_SGP4X_SGP41, srawVoc, srawNox, voc_index_sgp4x, nox_index_sgp4x);
282+
} else {
283+
WSContentSend_PD(HTTP_SNS_SGP4X_SGP40, srawVoc, voc_index_sgp4x);
284+
}
285+
228286
#endif
229287
}
230288
}

0 commit comments

Comments
 (0)