1
1
/*
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
3
3
4
4
Copyright (C) 2023 Andrew Klaus
5
5
20
20
#ifdef USE_I2C
21
21
#ifdef USE_SGP4X
22
22
/* ********************************************************************************************\
23
- * SGP4x - Gas (TVOC - Total Volatile Organic Compounds) and NOx
23
+ * SGP4x - Gas (TVOC - Total Volatile Organic Compounds) and NOx (SGP41-only)
24
24
*
25
25
* Source: Sensirion Driver, with mods by Andrew Klaus
26
26
* Adaption for TASMOTA: Andrew Klaus
@@ -39,6 +39,11 @@ extern "C" {
39
39
#include " sensirion_gas_index_algorithm.h"
40
40
};
41
41
42
+ enum SGP4X_Type {
43
+ TYPE_SGP41,
44
+ TYPE_SGP40,
45
+ };
46
+
42
47
enum SGP4X_State {
43
48
STATE_SGP4X_START,
44
49
STATE_SGP4X_SELFTEST_SENT,
@@ -49,8 +54,10 @@ enum SGP4X_State {
49
54
STATE_SGP4X_NORMAL,
50
55
STATE_SGP4X_FAIL,
51
56
};
57
+
52
58
SensirionI2CSgp4x sgp4x;
53
59
SGP4X_State sgp4x_state = STATE_SGP4X_START;
60
+ SGP4X_Type sgp4x_type = TYPE_SGP41;
54
61
55
62
bool sgp4x_init = false ;
56
63
bool sgp4x_read_pend = false ;
@@ -133,14 +140,39 @@ void Sgp4xSendReadCmd(void)
133
140
}
134
141
}
135
142
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
+
136
149
// Initiate conditioning
137
150
if (sgp4x_state == STATE_SGP4X_SELFTEST_DONE) {
138
151
error = sgp4x.sendConditioningCmd (0x8000 , 0x6666 );
139
152
sgp4x_state = STATE_SGP4X_COND_SENT;
140
153
141
154
if (error) {
142
155
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" ));
143
174
}
175
+
144
176
return ;
145
177
}
146
178
@@ -150,7 +182,12 @@ void Sgp4xSendReadCmd(void)
150
182
151
183
// Normal operation
152
184
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
+
154
191
if (error) {
155
192
Sgp4xHandleError (error);
156
193
} else {
@@ -182,7 +219,7 @@ void Sgp4xUpdate(void)
182
219
{
183
220
uint16_t error;
184
221
185
- // Conditioning - NOx needs 10s to warmup
222
+ // Conditioning - SGP41 NOx needs 10s to warmup
186
223
if (sgp4x_state == STATE_SGP4X_COND_SENT) {
187
224
if (conditioning_s > 0 ) {
188
225
conditioning_s--;
@@ -193,38 +230,59 @@ void Sgp4xUpdate(void)
193
230
}
194
231
195
232
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
+ }
197
238
sgp4x_read_pend = false ;
198
239
199
240
if (!error) {
200
241
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
+ }
202
247
} else {
203
248
Sgp4xHandleError (error);
204
249
}
205
250
}
206
251
}
207
252
208
253
#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}" ;
214
263
#endif
215
264
216
265
void Sgp4xShow (bool json)
217
266
{
218
267
if (sgp4x_state == STATE_SGP4X_NORMAL) {
219
268
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
+ }
221
274
ResponseJsonEnd ();
222
275
#ifdef USE_DOMOTICZ
223
276
if (0 == TasmotaGlobal.tele_period ) DomoticzSensor (DZ_AIRQUALITY, srawVoc);
224
277
#endif // USE_DOMOTICZ
225
278
#ifdef USE_WEBSERVER
226
279
} 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
+
228
286
#endif
229
287
}
230
288
}
0 commit comments