diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino
index f18747ba0..51ebe785b 100644
--- a/sonoff/_releasenotes.ino
+++ b/sonoff/_releasenotes.ino
@@ -4,6 +4,7 @@
* Add compiler check for stable lwIP version v1.4 (#1940)
* Add diacritics to Polish language file (#2005)
* Add Hungarian language file (#2024)
+ * Add support for Nova Fitness SDS011 and possibly SDS021 particle concentration sensor (#2070)
* Fix MQTT TLS fingerprint validation (#2033)
*
* 5.12.0c
diff --git a/sonoff/language/de-DE.h b/sonoff/language/de-DE.h
index 09877893c..f1eed0dac 100644
--- a/sonoff/language/de-DE.h
+++ b/sonoff/language/de-DE.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/sonoff/language/en-GB.h b/sonoff/language/en-GB.h
index 77db37009..987d0ccda 100644
--- a/sonoff/language/en-GB.h
+++ b/sonoff/language/en-GB.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/sonoff/language/es-AR.h b/sonoff/language/es-AR.h
index e54098edf..8386dd605 100644
--- a/sonoff/language/es-AR.h
+++ b/sonoff/language/es-AR.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/sonoff/language/fr-FR.h b/sonoff/language/fr-FR.h
index 78afc0677..9364bc102 100644
--- a/sonoff/language/fr-FR.h
+++ b/sonoff/language/fr-FR.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/sonoff/language/hu-HU.h b/sonoff/language/hu-HU.h
index 3f283f7e5..f1fb99c61 100644
--- a/sonoff/language/hu-HU.h
+++ b/sonoff/language/hu-HU.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "Háttérvil"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/sonoff/language/it-IT.h b/sonoff/language/it-IT.h
index 6ab442ca1..ea6ce9804 100644
--- a/sonoff/language/it-IT.h
+++ b/sonoff/language/it-IT.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/sonoff/language/nl-NL.h b/sonoff/language/nl-NL.h
index 6f9d39f72..c260553a0 100644
--- a/sonoff/language/nl-NL.h
+++ b/sonoff/language/nl-NL.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/sonoff/language/pl-PL.h b/sonoff/language/pl-PL.h
index 12496e9a6..334934a12 100644
--- a/sonoff/language/pl-PL.h
+++ b/sonoff/language/pl-PL.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/sonoff/language/pt-PT.h b/sonoff/language/pt-PT.h
index cf4b7ee09..7fd9bac23 100644
--- a/sonoff/language/pt-PT.h
+++ b/sonoff/language/pt-PT.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "Luz negra"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/sonoff/language/ru-RU.h b/sonoff/language/ru-RU.h
index f6bab7d90..b0f5cd764 100644
--- a/sonoff/language/ru-RU.h
+++ b/sonoff/language/ru-RU.h
@@ -408,6 +408,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "А"
diff --git a/sonoff/language/zh-CN.h b/sonoff/language/zh-CN.h
index 76c271ee9..feff61a23 100644
--- a/sonoff/language/zh-CN.h
+++ b/sonoff/language/zh-CN.h
@@ -407,6 +407,7 @@
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BkLight"
#define D_SENSOR_PMS5003 "PMS5003"
+#define D_SENSOR_SDS0X1 "SDS0X1"
// Units
#define D_UNIT_AMPERE "安"
diff --git a/sonoff/sonoff_post.h b/sonoff/sonoff_post.h
index d7b55030a..0a023ea6f 100644
--- a/sonoff/sonoff_post.h
+++ b/sonoff/sonoff_post.h
@@ -61,6 +61,7 @@ void WifiWpsStatusCallback(wps_cb_status status);
#define CO2_HIGH 1200 // Above this CO2 value show red light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1)
#endif
#define USE_PMS5003 // Add support for PMS5003 and PMS7003 particle concentration sensor (+1k3 code)
+#define USE_NOVA_SDS // Add support for SDS011 and SDS021 particle concentration sensor (+0k7 code)
#define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code)
#define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0k3 mem, 48 iram)
#define USE_IR_HVAC // Support for HVAC system using IR (+2k code)
@@ -170,4 +171,4 @@ void WifiWpsStatusCallback(wps_cb_status status);
#define ARDUINO_ESP8266_RELEASE "STAGE"
#endif
-#endif // _SONOFF_POST_H_
\ No newline at end of file
+#endif // _SONOFF_POST_H_
diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h
index 1a8206c08..6a0e2cd97 100644
--- a/sonoff/sonoff_template.h
+++ b/sonoff/sonoff_template.h
@@ -88,6 +88,7 @@ enum UserSelectablePins {
GPIO_SPI_DC, // SPI Data Direction
GPIO_BACKLIGHT, // Display backlight control
GPIO_PMS5003, // Plantower PMS5003 Serial interface
+ GPIO_SDS0X1, // Nova Fitness SDS011 Serial interface
GPIO_SENSOR_END };
// Programmer selectable GPIO functionality offset by user selectable GPIOs
@@ -129,7 +130,7 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_PZEM_TX "|" D_SENSOR_PZEM_RX "|"
D_SENSOR_SAIR_TX "|" D_SENSOR_SAIR_RX "|"
D_SENSOR_SPI_CS "|" D_SENSOR_SPI_DC "|" D_SENSOR_BACKLIGHT "|"
- D_SENSOR_PMS5003;
+ D_SENSOR_PMS5003 "|" D_SENSOR_SDS0X1;
/********************************************************************************************/
diff --git a/sonoff/user_config.h b/sonoff/user_config.h
index d2e8aa9e7..f5f7acdc7 100644
--- a/sonoff/user_config.h
+++ b/sonoff/user_config.h
@@ -215,6 +215,7 @@
#define CO2_LOW 800 // Below this CO2 value show green light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1)
#define CO2_HIGH 1200 // Above this CO2 value show red light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1)
#define USE_PMS5003 // Add support for PMS5003 and PMS7003 particle concentration sensor (+1k3 code)
+#define USE_NOVA_SDS // Add support for SDS011 and SDS021 particle concentration sensor (+0k7 code)
#define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code)
// -- Low level interface devices -----------------
diff --git a/sonoff/xsns_20_novasds.ino b/sonoff/xsns_20_novasds.ino
new file mode 100644
index 000000000..ed877fd2f
--- /dev/null
+++ b/sonoff/xsns_20_novasds.ino
@@ -0,0 +1,143 @@
+/*
+ xsns_20_novasds.ino - Nova SDS011/SDS021 particle concentration sensor support for Sonoff-Tasmota
+
+ Copyright (C) 2018 Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_NOVA_SDS
+/*********************************************************************************************\
+ * Nova Fitness SDS011 (and possibly SDS021) particle concentration sensor
+ * For background information see http://aqicn.org/sensor/sds011/
+\*********************************************************************************************/
+
+#include
+
+TasmotaSerial *NovaSdsSerial;
+
+uint8_t novasds_type = 1;
+uint8_t novasds_valid = 0;
+
+struct sds011data {
+ uint16_t pm100;
+ uint16_t pm25;
+} novasds_data;
+
+bool NovaSdsReadData()
+{
+ if (! NovaSdsSerial->available()) return false;
+
+ while ((NovaSdsSerial->peek() != 0xAA) && NovaSdsSerial->available()) {
+ NovaSdsSerial->read();
+ }
+
+ byte d[8] = { 0 };
+ NovaSdsSerial->read(); // skip 0xAA
+ NovaSdsSerial->readBytes(d, 8);
+ NovaSdsSerial->flush();
+
+ AddLogSerial(LOG_LEVEL_DEBUG_MORE, d, 8);
+
+ if (d[7] == ((d[1] + d[2] + d[3] + d[4] + d[5] + d[6]) & 0xFF)) {
+ novasds_data.pm25 = (d[1] + 256 * d[2]);
+ novasds_data.pm100 = (d[3] + 256 * d[4]);
+ } else {
+ AddLog_P(LOG_LEVEL_DEBUG, PSTR("SDS: " D_CHECKSUM_FAILURE));
+ return false;
+ }
+
+ novasds_valid = 10;
+
+ return true;
+}
+
+/*********************************************************************************************/
+
+void NovaSdsSecond() // Every second
+{
+ if (NovaSdsReadData()) {
+ novasds_valid = 10;
+ } else {
+ if (novasds_valid) {
+ novasds_valid--;
+ }
+ }
+}
+
+/*********************************************************************************************/
+
+void NovaSdsInit()
+{
+ novasds_type = 0;
+
+ if (pin[GPIO_SDS0X1] < 99) {
+ NovaSdsSerial = new TasmotaSerial(pin[GPIO_SDS0X1], -1);
+ if (NovaSdsSerial->begin()) {
+ novasds_type = 1;
+ }
+ }
+}
+
+#ifdef USE_WEBSERVER
+const char HTTP_SDS0X1_SNS[] PROGMEM = "%s"
+ "{s}SDS0X1 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
+ "{s}SDS0X1 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"; // {s} = , {m} = | , {e} = |
+#endif // USE_WEBSERVER
+
+void NovaSdsShow(boolean json)
+{
+ if (novasds_valid) {
+ if (json) {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"SDS0X1\":{\"PM2.5\":%d,\"PM10\":%d}"), mqtt_data, novasds_data.pm25, novasds_data.pm100);
+#ifdef USE_WEBSERVER
+ } else {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SDS0X1_SNS, mqtt_data, novasds_data.pm25/10, novasds_data.pm100/10);
+#endif // USE_WEBSERVER
+ }
+ }
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+#define XSNS_20
+
+boolean Xsns20(byte function)
+{
+ boolean result = false;
+
+ if (novasds_type) {
+ switch (function) {
+ case FUNC_INIT:
+ NovaSdsInit();
+ break;
+ case FUNC_EVERY_SECOND:
+ NovaSdsSecond();
+ break;
+ case FUNC_JSON_APPEND:
+ NovaSdsShow(1);
+ break;
+#ifdef USE_WEBSERVER
+ case FUNC_WEB_APPEND:
+ NovaSdsShow(0);
+ break;
+#endif // USE_WEBSERVER
+ }
+ }
+ return result;
+}
+
+#endif // USE_NOVA_SDS
\ No newline at end of file