diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino
index 85ba74766..76899d43a 100644
--- a/sonoff/_releasenotes.ino
+++ b/sonoff/_releasenotes.ino
@@ -1,6 +1,7 @@
/* 5.10.0a
* Add (experimental) support for sensor SHT3x
- * Add (experimental) support for iTead SI7021 temperature and humidity sensor (#735)
+ * Add (experimental) support for sensor MH-Z19(B) to be enabled with define USE_MHZ19 in user_config.h (#561, #1248)
+ * Add support for iTead SI7021 temperature and humidity sensor by consolidating DHT22 into AM2301 and using former DHT22 as SI7021 (#735)
* Fix BME280 calculation (#1051)
* Change ADS1115 default voltage range from +/-2V to +/-6V (#1289)
* Add multipress support and more user configurable options to Sonoff Dual R2 (#1291)
diff --git a/sonoff/language/de-DE.h b/sonoff/language/de-DE.h
index 5ad6606fb..09d6da112 100644
--- a/sonoff/language/de-DE.h
+++ b/sonoff/language/de-DE.h
@@ -66,6 +66,7 @@
#define D_BUTTON "Knopf"
#define D_BY "von" // Written by me
#define D_CELSIUS "Celsius"
+#define D_CO2 "CO2"
#define D_CODE "code" // Button code
#define D_COLDLIGHT "kalt"
#define D_COMMAND "Befehl"
@@ -473,6 +474,8 @@
#define D_SENSOR_PWM "PWM " // Suffix "1"
#define D_SENSOR_COUNTER "Counter" // Suffix "1"
#define D_SENSOR_IRRECV "IRRecv"
+#define D_SENSOR_MHZ_RX "MHZ Rx"
+#define D_SENSOR_MHZ_TX "MHZ Tx"
#define D_SENSOR_SPI_CS "SPI CS"
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BLight"
@@ -486,6 +489,7 @@
#define D_UNIT_MILLIAMPERE "mA"
#define D_UNIT_MILLISECOND "ms"
#define D_UNIT_MINUTE "min"
+#define D_UNIT_PPM "ppm"
#define D_UNIT_PRESSURE "hPa"
#define D_UNIT_SECOND "sek"
#define D_UNIT_SECTORS "Sektoren"
diff --git a/sonoff/language/en-GB.h b/sonoff/language/en-GB.h
index 517ab8ee3..c1a4378aa 100644
--- a/sonoff/language/en-GB.h
+++ b/sonoff/language/en-GB.h
@@ -66,6 +66,7 @@
#define D_BUTTON "Button"
#define D_BY "by" // Written by me
#define D_CELSIUS "Celsius"
+#define D_CO2 "CO2"
#define D_CODE "code" // Button code
#define D_COLDLIGHT "Cold"
#define D_COMMAND "Command"
@@ -473,6 +474,8 @@
#define D_SENSOR_PWM "PWM" // Suffix "1"
#define D_SENSOR_COUNTER "Counter" // Suffix "1"
#define D_SENSOR_IRRECV "IRrecv"
+#define D_SENSOR_MHZ_RX "MHZ Rx"
+#define D_SENSOR_MHZ_TX "MHZ Tx"
#define D_SENSOR_SPI_CS "SPI CS"
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BLight"
@@ -486,6 +489,7 @@
#define D_UNIT_MILLIAMPERE "mA"
#define D_UNIT_MILLISECOND "ms"
#define D_UNIT_MINUTE "Min"
+#define D_UNIT_PPM "ppm"
#define D_UNIT_PRESSURE "hPa"
#define D_UNIT_SECOND "sec"
#define D_UNIT_SECTORS "sectors"
diff --git a/sonoff/language/nl-NL.h b/sonoff/language/nl-NL.h
index 4053e5210..6a6ea368e 100644
--- a/sonoff/language/nl-NL.h
+++ b/sonoff/language/nl-NL.h
@@ -66,6 +66,7 @@
#define D_BUTTON "DrukKnop"
#define D_BY "door" // Written by me
#define D_CELSIUS "Celsius"
+#define D_CO2 "CO2"
#define D_CODE "code" // Button code
#define D_COLDLIGHT "Koud"
#define D_COMMAND "Opdracht"
@@ -472,6 +473,8 @@
#define D_SENSOR_LED "Led" // Suffix "1i"
#define D_SENSOR_PWM "PWM" // Suffix "1"
#define D_SENSOR_COUNTER "Teller" // Suffix "1"
+#define D_SENSOR_MHZ_RX "MHZ Rx"
+#define D_SENSOR_MHZ_TX "MHZ Tx"
#define D_SENSOR_IRRECV "IRrecv"
#define D_SENSOR_SPI_CS "SPI CS"
#define D_SENSOR_SPI_DC "SPI DC"
@@ -486,6 +489,7 @@
#define D_UNIT_MILLIAMPERE "mA"
#define D_UNIT_MILLISECOND "ms"
#define D_UNIT_MINUTE "Min"
+#define D_UNIT_PPM "ppm"
#define D_UNIT_PRESSURE "hPa"
#define D_UNIT_SECOND "sec"
#define D_UNIT_SECTORS "sectoren"
diff --git a/sonoff/language/pl-PL.h b/sonoff/language/pl-PL.h
index 71ccb7bcc..e53cc4962 100644
--- a/sonoff/language/pl-PL.h
+++ b/sonoff/language/pl-PL.h
@@ -66,6 +66,7 @@
#define D_BUTTON "Przycisk"
#define D_BY "by" // Written by me
#define D_CELSIUS "Celsiusza"
+#define D_CO2 "CO2"
#define D_CODE "kod" // Button code
#define D_COLDLIGHT "Zimny"
#define D_COMMAND "Komenda"
@@ -473,6 +474,8 @@
#define D_SENSOR_PWM "PWM" // Suffix "1"
#define D_SENSOR_COUNTER "Liczni" // Suffix "1"
#define D_SENSOR_IRRECV "IRrecv"
+#define D_SENSOR_MHZ_RX "MHZ Rx"
+#define D_SENSOR_MHZ_TX "MHZ Tx"
#define D_SENSOR_SPI_CS "SPI CS"
#define D_SENSOR_SPI_DC "SPI DC"
#define D_SENSOR_BACKLIGHT "BLight"
@@ -486,6 +489,7 @@
#define D_UNIT_MILLIAMPERE "mA"
#define D_UNIT_MILLISECOND "ms"
#define D_UNIT_MINUTE "Min"
+#define D_UNIT_PPM "ppm"
#define D_UNIT_PRESSURE "hPa"
#define D_UNIT_SECOND "sec"
#define D_UNIT_SECTORS "sektory"
diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino
index 0d1a7e377..78f662ea7 100644
--- a/sonoff/sonoff.ino
+++ b/sonoff/sonoff.ino
@@ -2665,17 +2665,7 @@ void setup()
GpioInit();
- if (Serial.baudRate() != baudrate) {
- if (seriallog_level) {
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_SET_BAUDRATE_TO " %d"), baudrate);
- AddLog(LOG_LEVEL_INFO);
- }
- delay(100);
- Serial.flush();
- Serial.begin(baudrate);
- delay(10);
- Serial.println();
- }
+ SetSerialBaudrate(baudrate);
if (strstr(Settings.hostname, "%")) {
strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h
index a250795b2..53ae6bcdb 100644
--- a/sonoff/sonoff_template.h
+++ b/sonoff/sonoff_template.h
@@ -78,6 +78,8 @@ enum UserSelectablePins {
GPIO_LED2_INV,
GPIO_LED3_INV,
GPIO_LED4_INV,
+ GPIO_MHZ_TXD,
+ GPIO_MHZ_RXD,
GPIO_SENSOR_END };
// Text in webpage Module Parameters and commands GPIOS and GPIO
@@ -137,7 +139,9 @@ const char kSensors[GPIO_SENSOR_END][9] PROGMEM = {
D_SENSOR_LED "1i",
D_SENSOR_LED "2i",
D_SENSOR_LED "3i",
- D_SENSOR_LED "4i"
+ D_SENSOR_LED "4i",
+ D_SENSOR_MHZ_TX,
+ D_SENSOR_MHZ_RX
};
// Programmer selectable GPIO functionality offset by user selectable GPIOs
diff --git a/sonoff/support.ino b/sonoff/support.ino
index 30ba4748c..1c1300129 100644
--- a/sonoff/support.ino
+++ b/sonoff/support.ino
@@ -1287,6 +1287,21 @@ int GetCommandCode(char* destination, size_t destination_size, const char* needl
return result;
}
+void SetSerialBaudrate(int baudrate)
+{
+ if (Serial.baudRate() != baudrate) {
+ if (seriallog_level) {
+ snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_SET_BAUDRATE_TO " %d"), baudrate);
+ AddLog(LOG_LEVEL_INFO);
+ }
+ delay(100);
+ Serial.flush();
+ Serial.begin(baudrate);
+ delay(10);
+ Serial.println();
+ }
+}
+
#ifndef USE_ADC_VCC
/*********************************************************************************************\
* ADC support
diff --git a/sonoff/user_config.h b/sonoff/user_config.h
index d00c74f61..33f4345f7 100644
--- a/sonoff/user_config.h
+++ b/sonoff/user_config.h
@@ -186,7 +186,9 @@
#define USE_WS2812_CTYPE 1 // WS2812 Color type (0 - RGB, 1 - GRB, 2 - RGBW, 3 - GRBW)
// #define USE_WS2812_DMA // DMA supports only GPIO03 (= Serial RXD) (+1k mem). When USE_WS2812_DMA is enabled expect Exceptions on Pow
-#define USE_ARILUX_RF // Add code for Arilux RF remote controller (+0.8k code)
+//#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor using hardware serial interface at 9600 bps
+
+#define USE_ARILUX_RF // Add support for Arilux RF remote controller (+0.8k code)
/*********************************************************************************************\
* Compile a minimal version if upgrade memory gets tight ONLY TO BE USED FOR UPGRADE STEP 1!
diff --git a/sonoff/xsns_15_mhz.ino b/sonoff/xsns_15_mhz.ino
new file mode 100644
index 000000000..594540b5a
--- /dev/null
+++ b/sonoff/xsns_15_mhz.ino
@@ -0,0 +1,277 @@
+/*
+ xsns_15_mhz.ino - MH-Z19 CO2 sensor support for Sonoff-Tasmota
+
+ Copyright (C) 2017 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_MHZ19
+/*********************************************************************************************\
+ * MH-Z19 - CO2 sensor
+ *
+ * Supported on hardware serial interface only due to lack of iram needed by SoftwareSerial
+ *
+ * Based on EspEasy plugin P049 by Dmitry (rel22 ___ inbox.ru)
+ *
+ **********************************************************************************************
+ * Filter usage
+ *
+ * Select filter usage on low stability readings
+\*********************************************************************************************/
+
+enum Mhz19FilterOptions {MHZ19_FILTER_OFF, MHZ19_FILTER_OFF_ALLSAMPLES, MHZ19_FILTER_FAST, MHZ19_FILTER_MEDIUM, MHZ19_FILTER_SLOW};
+
+#define MHZ19_FILTER_OPTION MHZ19_FILTER_FAST
+
+/*********************************************************************************************\
+ * Source: http://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
+ *
+ * Automatic Baseline Correction (ABC logic function)
+ *
+ * ABC logic function refers to that sensor itself do zero point judgment and automatic calibration procedure
+ * intelligently after a continuous operation period. The automatic calibration cycle is every 24 hours after powered on.
+ *
+ * The zero point of automatic calibration is 400ppm.
+ *
+ * This function is usually suitable for indoor air quality monitor such as offices, schools and homes,
+ * not suitable for greenhouse, farm and refrigeratory where this function should be off.
+ *
+ * Please do zero calibration timely, such as manual or commend calibration.
+\*********************************************************************************************/
+
+#define MHZ19_ABC_ENABLE 1 // Automatic Baseline Correction (0 = off, 1 = on (default))
+
+/*********************************************************************************************/
+
+#define MHZ19_BAUDRATE 9600
+#define MHZ19_READ_TIMEOUT 600 // Must be way less than 1000
+
+const char kMhz19Types[] PROGMEM = "MHZ19|MHZ19B";
+
+const byte mhz19_cmnd_read_ppm[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
+const byte mhz19_cmnd_abc_enable[9] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00, 0xE6};
+const byte mhz19_cmnd_abc_disable[9] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86};
+
+uint8_t mhz19_type = 0;
+uint16_t mhz19_last_ppm = 0;
+uint8_t mhz19_filter = MHZ19_FILTER_OPTION;
+byte mhz19_response[9];
+bool mhz19_abc_enable = MHZ19_ABC_ENABLE;
+bool mhz19_abc_must_apply = false;
+char mhz19_types[7];
+
+bool Mhz19CheckAndApplyFilter(uint16_t ppm, uint8_t s)
+{
+ if (1 == s) {
+ return false; // S==1 => "A" version sensor bootup, do not use values.
+ }
+ if (mhz19_last_ppm < 400 || mhz19_last_ppm > 5000) {
+ // Prevent unrealistic values during start-up with filtering enabled.
+ // Just assume the entered value is correct.
+ mhz19_last_ppm = ppm;
+ return true;
+ }
+ int32_t difference = ppm - mhz19_last_ppm;
+ if (s > 0 && s < 64 && mhz19_filter != MHZ19_FILTER_OFF) {
+ // Not the "B" version of the sensor, S value is used.
+ // S==0 => "B" version, else "A" version
+ // The S value is an indication of the stability of the reading.
+ // S == 64 represents a stable reading and any lower value indicates (unusual) fast change.
+ // Now we increase the delay filter for low values of S and increase response time when the
+ // value is more stable.
+ // This will make the reading useful in more turbulent environments,
+ // where the sensor would report more rapid change of measured values.
+ difference = difference * s;
+ difference /= 64;
+ }
+ switch (mhz19_filter) {
+ case MHZ19_FILTER_OFF: {
+ if (s != 0 && s != 64) {
+ return false;
+ }
+ break;
+ }
+ // #Samples to reach >= 75% of step response
+ case MHZ19_FILTER_OFF_ALLSAMPLES:
+ break; // No Delay
+ case MHZ19_FILTER_FAST:
+ difference /= 2;
+ break; // Delay: 2 samples
+ case MHZ19_FILTER_MEDIUM:
+ difference /= 4;
+ break; // Delay: 5 samples
+ case MHZ19_FILTER_SLOW:
+ difference /= 8;
+ break; // Delay: 11 samples
+ }
+ mhz19_last_ppm = static_cast(mhz19_last_ppm + difference);
+ return true;
+}
+
+bool Mhz19Read(uint16_t &p, float &t)
+{
+ bool status = false;
+
+ p = 0;
+ t = NAN;
+
+ if (mhz19_type)
+ {
+ Serial.flush();
+ if (Serial.write(mhz19_cmnd_read_ppm, 9) != 9) {
+ return false; // Unable to send 9 bytes
+ }
+ memset(mhz19_response, 0, sizeof(mhz19_response));
+ uint32_t start = millis();
+ uint8_t counter = 0;
+ while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) {
+ if (Serial.available() > 0) {
+ mhz19_response[counter++] = Serial.read();
+ } else {
+ delay(10);
+ }
+ }
+ if (counter < 9){
+ return false; // Timeout while trying to read
+ }
+
+ byte crc = 0;
+ for (uint8_t i = 1; i < 8; i++) {
+ crc += mhz19_response[i];
+ }
+ crc = 255 - crc;
+ crc++;
+
+/*
+ // Test data
+ mhz19_response[0] = 0xFF;
+ mhz19_response[1] = 0x86;
+ mhz19_response[2] = 0x12;
+ mhz19_response[3] = 0x86;
+ mhz19_response[4] = 64;
+// mhz19_response[5] = 32;
+ mhz19_response[8] = crc;
+*/
+
+ if (0xFF == mhz19_response[0] && 0x86 == mhz19_response[1] && mhz19_response[8] == crc) {
+ uint16_t u = (mhz19_response[6] << 8) | mhz19_response[7];
+ if (15000 == u) { // During (and only ever at) sensor boot, 'u' is reported as 15000
+ if (!mhz19_abc_enable) {
+ // After bootup of the sensor the ABC will be enabled.
+ // Thus only actively disable after bootup.
+ mhz19_abc_must_apply = true;
+ }
+ } else {
+ uint16_t ppm = (mhz19_response[2] << 8) | mhz19_response[3];
+ t = ConvertTemp((float)mhz19_response[4] - 40);
+ uint8_t s = mhz19_response[5];
+ if (s) {
+ mhz19_type = 1;
+ } else {
+ mhz19_type = 2;
+ }
+ if (Mhz19CheckAndApplyFilter(ppm, s)) {
+ p = mhz19_last_ppm;
+
+ if (0 == s || 64 == s) { // Reading is stable.
+ if (mhz19_abc_must_apply) {
+ mhz19_abc_must_apply = false;
+ if (mhz19_abc_enable) {
+ Serial.write(mhz19_cmnd_abc_enable, 9); // Sent sensor ABC Enable
+ } else {
+ Serial.write(mhz19_cmnd_abc_disable, 9); // Sent sensor ABC Disable
+ }
+ }
+ }
+
+ status = true;
+ }
+ }
+ }
+ }
+ return status;
+}
+
+void Mhz19Init()
+{
+ SetSerialBaudrate(MHZ19_BAUDRATE);
+ Serial.flush();
+
+ seriallog_level = 0;
+ mhz19_type = 1;
+}
+
+#ifdef USE_WEBSERVER
+const char HTTP_SNS_CO2[] PROGMEM =
+ "%s{s}%s " D_CO2 "{m}%d " D_UNIT_PPM "{e}"; // {s} = , {m} = | , {e} = |
+#endif // USE_WEBSERVER
+
+void Mhz19Show(boolean json)
+{
+ uint16_t co2;
+ float t;
+
+ if (Mhz19Read(co2, t)) {
+ char temperature[10];
+ dtostrfd(t, Settings.flag2.temperature_resolution, temperature);
+ GetTextIndexed(mhz19_types, sizeof(mhz19_types), mhz19_type -1, kMhz19Types);
+
+ if (json) {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_CO2 "\":%d,\"" D_TEMPERATURE "\":%s}"), mqtt_data, mhz19_types, co2, temperature);
+#ifdef USE_DOMOTICZ
+ DomoticzSensor(DZ_COUNT, co2);
+#endif // USE_DOMOTICZ
+#ifdef USE_WEBSERVER
+ } else {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_CO2, mqtt_data, mhz19_types, co2);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, mhz19_types, temperature, TempUnit());
+#endif // USE_WEBSERVER
+ }
+ }
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+#define XSNS_15
+
+boolean Xsns15(byte function)
+{
+ boolean result = false;
+
+ if ((pin[GPIO_MHZ_RXD] < 99) && (pin[GPIO_MHZ_TXD] < 99)) {
+ switch (function) {
+ case FUNC_XSNS_INIT:
+ Mhz19Init();
+ break;
+ case FUNC_XSNS_PREP:
+// Mhz19Prep();
+ break;
+ case FUNC_XSNS_JSON_APPEND:
+ Mhz19Show(1);
+ break;
+#ifdef USE_WEBSERVER
+ case FUNC_XSNS_WEB:
+ Mhz19Show(0);
+// Mhz19Prep();
+ break;
+#endif // USE_WEBSERVER
+ }
+ }
+ return result;
+}
+
+#endif // USE_MHZ19