diff --git a/tasmota/language/af_AF.h b/tasmota/language/af_AF.h
index 4b72c6837..60460a97a 100644
--- a/tasmota/language/af_AF.h
+++ b/tasmota/language/af_AF.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h
index 7e7cf52d0..602091030 100644
--- a/tasmota/language/bg_BG.h
+++ b/tasmota/language/bg_BG.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h
index cfa6c5625..1a731d7fa 100644
--- a/tasmota/language/cs_CZ.h
+++ b/tasmota/language/cs_CZ.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h
index b67a77851..bd4306de9 100644
--- a/tasmota/language/de_DE.h
+++ b/tasmota/language/de_DE.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h
index 1ce06a5bb..df9fd6fff 100644
--- a/tasmota/language/el_GR.h
+++ b/tasmota/language/el_GR.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h
index 45a0ca3ca..71bd64bac 100644
--- a/tasmota/language/en_GB.h
+++ b/tasmota/language/en_GB.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h
index 621f97679..a317601ad 100644
--- a/tasmota/language/es_ES.h
+++ b/tasmota/language/es_ES.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h
index 83b2063c9..8a28c747d 100644
--- a/tasmota/language/fr_FR.h
+++ b/tasmota/language/fr_FR.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/fy_NL.h b/tasmota/language/fy_NL.h
index 86be5540a..7addac432 100644
--- a/tasmota/language/fy_NL.h
+++ b/tasmota/language/fy_NL.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h
index 649df5e4b..9c0941429 100644
--- a/tasmota/language/he_HE.h
+++ b/tasmota/language/he_HE.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h
index d09d68b55..6fad9fbb5 100644
--- a/tasmota/language/hu_HU.h
+++ b/tasmota/language/hu_HU.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h
index 56b9f1944..f48d7f1e4 100644
--- a/tasmota/language/it_IT.h
+++ b/tasmota/language/it_IT.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 - RCLK"
#define D_GPIO_SHIFT595_OE "74x595 - OE"
#define D_GPIO_SHIFT595_SER "74x595 - SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h
index e503f55d4..85306451c 100644
--- a/tasmota/language/ko_KO.h
+++ b/tasmota/language/ko_KO.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h
index afe73ad75..17803a9b0 100644
--- a/tasmota/language/nl_NL.h
+++ b/tasmota/language/nl_NL.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h
index e69836af6..68ea8596c 100644
--- a/tasmota/language/pl_PL.h
+++ b/tasmota/language/pl_PL.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h
index 4ab70c0b6..b3314554e 100644
--- a/tasmota/language/pt_BR.h
+++ b/tasmota/language/pt_BR.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h
index c7d398fbb..357aec476 100644
--- a/tasmota/language/pt_PT.h
+++ b/tasmota/language/pt_PT.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h
index e2f01aff9..ab325f19a 100644
--- a/tasmota/language/ro_RO.h
+++ b/tasmota/language/ro_RO.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h
index ad70639de..927a5e38d 100644
--- a/tasmota/language/ru_RU.h
+++ b/tasmota/language/ru_RU.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "А"
diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h
index e61bc00ec..322b412cb 100644
--- a/tasmota/language/sk_SK.h
+++ b/tasmota/language/sk_SK.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h
index 8f47e8be7..ed1298eb2 100644
--- a/tasmota/language/sv_SE.h
+++ b/tasmota/language/sv_SE.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h
index 70c6c958a..b238b88b9 100644
--- a/tasmota/language/tr_TR.h
+++ b/tasmota/language/tr_TR.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h
index a2903fdc6..b75e1f04a 100644
--- a/tasmota/language/uk_UA.h
+++ b/tasmota/language/uk_UA.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "А"
diff --git a/tasmota/language/vi_VN.h b/tasmota/language/vi_VN.h
index fb91d7201..13242d134 100644
--- a/tasmota/language/vi_VN.h
+++ b/tasmota/language/vi_VN.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h
index c7cc7a136..bd9b96293 100644
--- a/tasmota/language/zh_CN.h
+++ b/tasmota/language/zh_CN.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "A"
diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h
index 563e2f666..8689fcddd 100644
--- a/tasmota/language/zh_TW.h
+++ b/tasmota/language/zh_TW.h
@@ -852,6 +852,8 @@
#define D_GPIO_SHIFT595_RCLK "74x595 RCLK"
#define D_GPIO_SHIFT595_OE "74x595 OE"
#define D_GPIO_SHIFT595_SER "74x595 SER"
+#define D_SENSOR_CM11_TX "CM110x TX"
+#define D_SENSOR_CM11_RX "CM110x RX"
// Units
#define D_UNIT_AMPERE "安培"
diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h
index 444aa1bad..9d43927e8 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -724,6 +724,7 @@
// -- Serial sensors ------------------------------
//#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
//#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)
+//#define USE_CM110x // Add support for CM110x CO2 sensors (+2k7code)
#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)
diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino
index d9bbd259b..a8be4bd25 100644
--- a/tasmota/support_features.ino
+++ b/tasmota/support_features.ino
@@ -788,7 +788,9 @@ void ResponseAppendFeatures(void)
#ifdef USE_SDM230
feature8 |= 0x00100000; // xnrg_21_sdm230.ino
#endif
-// feature8 |= 0x00200000;
+#ifdef USE_CM110x
+ feature8 |= 0x00200000; // xsns_95_cm110x.ino
+#endif
// feature8 |= 0x00400000;
// feature8 |= 0x00800000;
diff --git a/tasmota/tasmota_configurations_ESP32.h b/tasmota/tasmota_configurations_ESP32.h
index bdcfcee65..16c7fc488 100644
--- a/tasmota/tasmota_configurations_ESP32.h
+++ b/tasmota/tasmota_configurations_ESP32.h
@@ -340,6 +340,7 @@
//#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
//#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)
+//#define USE_CM110x // Add support for CM110x CO2 sensors (+2k7 code)
#ifndef CO2_LOW
#define CO2_LOW 800 // Below this CO2 value show green light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1)
#endif
@@ -476,6 +477,7 @@
#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)
+#define USE_CM110x // Add support for CM110x CO2 sensors (+2k7 code)
#ifndef CO2_LOW
#define CO2_LOW 800 // Below this CO2 value show green light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1)
#endif
diff --git a/tasmota/tasmota_template.h b/tasmota/tasmota_template.h
index 5ccb040d3..7501fc407 100644
--- a/tasmota/tasmota_template.h
+++ b/tasmota/tasmota_template.h
@@ -181,6 +181,7 @@ enum UserSelectablePins {
GPIO_OPTION_E, // Emulated module
GPIO_SDM230_TX, GPIO_SDM230_RX, // SDM230 Serial interface
GPIO_ADC_MQ, // Analog MQ Sensor
+ GPIO_CM11_TXD, GPIO_CM11_RXD, // CM11 Serial interface
GPIO_SENSOR_END };
enum ProgramSelectablePins {
@@ -400,7 +401,8 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_SOLAXX1_RTS "|"
D_SENSOR_OPTION " E|"
D_SENSOR_SDM230_TX "|" D_SENSOR_SDM230_RX "|"
- D_SENSOR_ADC_MQ
+ D_SENSOR_ADC_MQ "|"
+ D_SENSOR_CM11_TX "|" D_SENSOR_CM11_RX "|"
;
const char kSensorNamesFixed[] PROGMEM =
@@ -934,6 +936,10 @@ const uint16_t kGpioNiceList[] PROGMEM = {
AGPIO(GPIO_MAX7219CS),
#endif // USE_DISPLAY_MAX7219
+#ifdef USE_CM110x
+ AGPIO(GPIO_CM11_TXD), // CM110x Serial interface
+ AGPIO(GPIO_CM11_RXD), // CM110x Serial interface
+#endif
/*-------------------------------------------------------------------------------------------*\
* ESP32 specifics
\*-------------------------------------------------------------------------------------------*/
diff --git a/tasmota/xsns_95_cm110x.ino b/tasmota/xsns_95_cm110x.ino
new file mode 100644
index 000000000..17c090c10
--- /dev/null
+++ b/tasmota/xsns_95_cm110x.ino
@@ -0,0 +1,465 @@
+/*
+ XSNS_95_cm1107.ino - CM1107(B) CO2 sensor support for Tasmota
+
+ Copyright (C) 2021 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_CM110x
+/*********************************************************************************************\
+ * CM11xx - CO2 sensor
+ * https://en.gassensor.com.cn/CO2Sensor/list.html
+ * Adapted from Mhz19 plugin by Maksim (rekin.m ___ gmail.com)
+ *
+ * Hardware Serial will be selected if GPIO1 = [CM11 Rx] and GPIO3 = [CM11 Tx]
+ **********************************************************************************************
+ * Filter usage
+ *
+ * Select filter usage on low stability readings
+ *
+ * *******************************************************************************************
+ * Some CM11 models has manual or continuos modes - this logic not implemented.
+\*********************************************************************************************/
+
+#define XSNS_95 95
+
+enum CM11FilterOptions {CM1107_FILTER_OFF, CM1107_FILTER_FAST, CM1107_FILTER_MEDIUM, CM1107_FILTER_MEDIUM2, CM1107_FILTER_SLOW};
+
+#ifndef CM1107_FILTER_OPTION
+ #define CM1107_FILTER_OPTION CM1107_FILTER_FAST
+#endif
+/*********************************************************************************************\
+ * Source: https://en.gassensor.com.cn/CO2Sensor/list.html (pdf for 1106/1107/1109 sensors)
+ *
+ *
+ * Automatic Baseline Correction (ABC logic function) is enabled by default but may be disabled with command
+ * Sensor95 0
+ * and enabled again with command
+ * Sensor95 1
+ *
+ * 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 first 24 hours and 7 days cycle 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 command calibration.
+\*********************************************************************************************/
+
+#include
+
+#ifndef CO2_LOW
+#define CO2_LOW 800 // Below this CO2 value show green light
+#endif
+#ifndef CO2_HIGH
+#define CO2_HIGH 1200 // Above this CO2 value show red light
+#endif
+
+#define CM1107_READ_TIMEOUT 400 // Must be way less than 1000 but enough to read 16 bytes at 9600 bps
+#define CM1107_RETRY_COUNT 8
+
+TasmotaSerial *CM11Serial;
+
+
+const char CM11_ABC_ENABLED[] = "ABC is Enabled";
+const char CM11_ABC_DISABLED[] = "ABC is Disabled";
+
+//First [0] element - lenght of cmd and data
+const uint8_t cmd_read[] = {0x01,0x01}; // cm11_cmnd_read_ppm
+uint8_t cmd_abc_enable[] = {0x07,0x10,0x64,0x00,0x07,0x01,0x90,0x64}; // cm11_cmnd_abc_enable. Not const because can be modified
+const uint8_t cmd_abc_disable[] = {0x07,0x10,0x64,0x02,0x07,0x01,0x90,0x64}; // cm11_cmnd_abc_disable
+const uint8_t cmd_zeropoint[] = {0x03,0x03,0x01,0x90}; // cm11_cmnd_zeropoint_400
+const uint8_t cmd_serial[] = {0x01,0x1F}; // cm11_cmnd_read_serial
+const uint8_t cmd_sw_version[] = {0x01,0x1E}; // cm11_cmnd_read_sw_version
+
+
+enum CM11Commands { CM11_CMND_READPPM, CM11_CMND_ABCENABLE, CM11_CMND_ABCDISABLE, CM11_CMND_ZEROPOINT, CM11_CMND_SERIAL,CM11_CMND_SW_VERSION };
+const uint8_t* kCM11Commands[] PROGMEM = {
+ cmd_read,
+ cmd_abc_enable,
+ cmd_abc_disable,
+ cmd_zeropoint,
+ cmd_serial,
+ cmd_sw_version
+};
+
+
+uint8_t cm11_type = 1;
+uint16_t cm11_last_ppm = 0;
+uint8_t cm11_filter = CM1107_FILTER_OPTION;
+bool cm11_abc_must_apply = false;
+
+float cm11_temperature = 0;
+uint16_t cm11_humidity = 0;
+char cm11_sw_version[30] = {0};
+char cm11_serial_number[21] = {0};
+uint8_t cm11_retry = CM1107_RETRY_COUNT;
+uint8_t cm11_received = 0;
+uint8_t cm11_state = 0;
+uint16_t ppm_low_limit = 0;
+uint16_t ppm_high_limit = 5000;
+
+/*********************************************************************************************/
+//256-(HEAD+LEN+CMD+DATA)%256
+uint8_t CM11CalculateChecksum(uint8_t *array,uint8_t start, uint8_t len)
+{
+ uint8_t checksum = 0;
+ for (uint8_t i = start; i < len; i++) {
+ checksum += array[i];
+ }
+ checksum = checksum%256;
+ checksum = 255 - checksum;
+ return (checksum +1);
+}
+
+size_t CM11SendCmd(uint8_t command_id)
+{
+ uint8_t len =kCM11Commands[command_id][0];
+ uint8_t cm11_send[len+3];// = {0}; //Fix length
+ memset( cm11_send, 0, (len+3)*sizeof(uint8_t) );
+
+ cm11_send[0] = 0x11; // Start byte, fixed
+
+ memcpy_P(&cm11_send[1], kCM11Commands[command_id], (len+1) * sizeof(uint8_t));
+
+ cm11_send[len+2] = CM11CalculateChecksum(cm11_send,0, len+2);
+
+#ifdef DEBUG_TASMOTA_SENSOR
+ char cmdFull[len+30];// = {0};
+ memset( cmdFull, 0, (len+3)*sizeof(char) );
+ for(int i=0, j=0;iwrite(cm11_send, sizeof(cm11_send));
+}
+
+/*********************************************************************************************/
+
+bool CM11CheckAndApplyFilter(uint16_t ppm, uint8_t drift)
+{
+#ifdef DEBUG_TASMOTA_SENSOR
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "CM11 ppm: %u, last ppm: %u"),ppm, cm11_last_ppm);
+#endif //DEBUG_TASMOTA_SENSOR
+ if (cm11_last_ppm < ppm_low_limit || cm11_last_ppm > ppm_high_limit) {
+ // Prevent unrealistic values during start-up with filtering enabled.
+ // Just assume the entered value is correct.
+ cm11_last_ppm = ppm;
+ return true;
+ }
+ int32_t difference = ppm - cm11_last_ppm;
+ if (drift > 0 && cm11_filter != CM1107_FILTER_OFF) {
+ difference >>= CM1107_FILTER_SLOW; // If drifting values -> apply slow filter
+ }else if (CM1107_FILTER_OFF == cm11_filter) {
+ if (drift != 0 ) {
+ return false; //Do not alarm on such unstable values
+ }
+ }else {
+ difference >>=cm11_filter;
+ }
+
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "CM11 diff: %d"),difference);
+ cm11_last_ppm = static_cast(cm11_last_ppm + difference);
+ return true;
+}
+
+void CM11EverySecond(void)
+{
+ cm11_state++;
+ //If more than one command was send
+ //Reading preffered
+ if (CM11Serial->available() > 0){
+ cm11_received = 0;
+ }
+
+ if ((8 == cm11_state && cm11_received) || 16 == cm11_state) { // Every 8 sec start a CM11 measuring cycle (which takes 1005 +5% ms)
+ cm11_state = 0;
+
+ if (cm11_retry) {
+ cm11_retry--;
+ if (!cm11_retry) {
+ cm11_last_ppm = 0;
+ cm11_temperature = 0;
+ cm11_humidity = 0;
+ }
+ }
+
+ CM11Serial->flush(); // Sync reception
+ CM11SendCmd(CM11_CMND_READPPM);
+ cm11_received = 0;
+ }
+
+ if ((cm11_state > 2) && !cm11_received) { // Start reading response after 3 seconds every second until received
+ uint8_t cm11_response[50];
+ unsigned long start = millis();
+ uint8_t counter = 0;
+ uint8_t resp_len = 50;
+ while (((millis() - start) < CM1107_READ_TIMEOUT) && (counter < resp_len)) {
+ if (CM11Serial->available() > 0) {
+ cm11_response[counter++] = CM11Serial->read();
+ if (counter ==2 && cm11_response[0] == 0x16) { //0x16 - first byte in response
+ resp_len = cm11_response[1] +3 ; // Get expected response len (according protocol desc), +3 - first byte, len and checksum
+ }
+ } else {
+ delay(5);
+ }
+ }
+
+ if (counter < 5) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "CM1107 timeout (command sent, no responce"));
+ return;
+ }
+
+ uint8_t crc = CM11CalculateChecksum(cm11_response,0, cm11_response[1]+2);
+ if (cm11_response[cm11_response[1]+2] != crc) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "CM1107 crc error"));
+ return;
+ }
+ if (0x16 != cm11_response[0]) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "CM1107 bad response"));
+ return;
+ }
+
+ cm11_received = 1;
+
+ if (cm11_response[2]==cmd_read[1]){ //0x01 - read command
+ uint16_t ppm = (cm11_response[3] << 8) | cm11_response[4];
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "CM11 PPM: %u"),ppm);
+ if (ppm ==550) { // Preheating mode, fixed value.
+ //DOCs says that preheating is cm11_response[5] & (1 << 0)) ==1 (first bit ==1), but mine sensor (CM1107, sw V1.07.0.02 )
+ // set first bit 0 when preheating at switch to 1 then finished.
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "CM11 preheating"));
+ if (Settings->SensorBits1.mhz19b_abc_disable) {
+ // After bootup of the sensor the ABC will be enabled.
+ // Thus only actively disable after bootup.
+ cm11_abc_must_apply = true;
+ }
+ return;
+ }
+ if(cm11_response[1] ==13) { // CM1107T with temperature and humidity
+ cm11_temperature = (float)(((cm11_response[7] << 8) | cm11_response[8]) - 4685)/100.0f;
+ cm11_humidity = (((cm11_response[9] << 8) | cm11_response[10]) - 600)/100;
+ cm11_type = 2;
+ }
+ uint8_t cm11_drift = (cm11_response[5] & (1 << 7)) ? 1:0;
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "CM11 flags DF3: %02x"),cm11_response[5]);
+
+ if (CM11CheckAndApplyFilter(ppm,cm11_drift)) {
+ cm11_retry = CM1107_RETRY_COUNT;
+#ifdef USE_LIGHT
+ LightSetSignal(CO2_LOW, CO2_HIGH, cm11_last_ppm);
+#endif // USE_LIGHT
+
+ if (!cm11_drift) { // Measuring is stable.
+ if (cm11_abc_must_apply) {
+ cm11_abc_must_apply = false;
+ if (!Settings->SensorBits1.mhz19b_abc_disable) {
+ CM11SendCmd(CM11_CMND_ABCENABLE);
+ } else {
+ CM11SendCmd(CM11_CMND_ABCDISABLE);
+ }
+ }
+ }
+
+ }
+ }
+
+ if (cm11_response[2]==cmd_sw_version[1]){ //0x1E - read SW version
+ memcpy_P(cm11_sw_version, &cm11_response[3], cm11_response[1] * sizeof(uint8_t));
+ AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_DEBUG "SW version: %s"),cm11_sw_version);
+ }
+
+ if (cm11_response[2]==cmd_serial[1]){ //0x1F - read serial
+ // Serial num coded as 5 integers 0..9999. Each integer is uint16_t size
+ for (uint8_t i=0; iSensorBits1.mhz19b_abc_disable = true;
+ CM11SendCmd(CM11_CMND_ABCDISABLE);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, CM11_ABC_DISABLED);
+ break;
+ case 1:
+ Settings->SensorBits1.mhz19b_abc_disable = false;
+ CM11SendCmd(CM11_CMND_ABCENABLE);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, CM11_ABC_ENABLED);
+ break;
+ case 2:
+ CM11SendCmd(CM11_CMND_ZEROPOINT);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, D_JSON_ZERO_POINT_CALIBRATION);
+ break;
+ case 3:
+ CM11SendCmd(CM11_CMND_SW_VERSION);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, "CM11 sw version");
+ break;
+ case 4:
+ CM11SendCmd(CM11_CMND_SERIAL);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, "CM11 serial number");
+ break;
+
+ default:
+ // Set ppm limits: 5,,
+ // ABS period cmd(with enabling ABS): 1,[1..30]
+ uint32_t parm[3] = { 0 };
+ ParseParameters(3, parm);
+ switch (parm[0]) {
+ case 1:
+ if (parm[1]>=1 && parm[1]<=30){
+ cmd_abc_enable[4] = parm[1]; //set uint8 from uint32 *o*, but value limited by 30
+ Settings->SensorBits1.mhz19b_abc_disable = false;
+ CM11SendCmd(CM11_CMND_ABCENABLE);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, CM11_ABC_ENABLED);
+ } else {
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, "Valid period value: [1..30]");
+ }
+ break;
+ // Set sensor ppm limit. Default 0..5000, but some sensors has another range.
+ case 5:
+ if(parm[1]>=0 && parm[1] <=10000 && parm[2]>=0 && parm[2] <=10000 && parm[1]=0 && parm[1]<=4) {
+ cm11_filter = parm[1];
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, "CM11 set filter mode");
+ }
+ else {
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, "Invalid filter mode: [0..4]. 0 - Off, 1 (Fast) -> 4 (Slow)");
+ }
+ break;
+ default:
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_95, "Unknown command");
+ break;
+ }
+
+ }
+
+ return serviced;
+}
+
+/*********************************************************************************************/
+
+void CM11Init(void)
+{
+ cm11_type = 0;
+ if (PinUsed(GPIO_CM11_RXD) && PinUsed(GPIO_CM11_TXD)) {
+ CM11Serial = new TasmotaSerial(Pin(GPIO_CM11_RXD), Pin(GPIO_CM11_TXD), 1);
+ if (CM11Serial->begin(9600)) {
+ if (CM11Serial->hardwareSerial()) { ClaimSerial(); }
+ cm11_type = 1;
+ CM11SendCmd(CM11_CMND_SW_VERSION);
+ }
+ }
+}
+
+void CM11Show(bool json)
+{
+ if (json) {
+ ResponseAppend_P(PSTR(",\"CM11\":{\"" D_JSON_CO2 "\":%d,\"" D_JSON_VERSION "\":\"%s\",\"Serial number\":\"%s\""),
+ cm11_last_ppm, cm11_sw_version, cm11_serial_number);
+ if(cm11_type == 2) { // With temp and humidity
+ ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%*_f"),
+ Settings->flag2.temperature_resolution, &cm11_temperature);
+ }
+ ResponseAppend_P(PSTR("}"));
+#ifdef USE_DOMOTICZ
+ if (0 == TasmotaGlobal.tele_period) {
+ DomoticzSensor(DZ_AIRQUALITY, cm11_last_ppm);
+ if(cm11_type == 2) { // With temp and humidity
+ DomoticzFloatSensor(DZ_TEMP, cm11_temperature);
+ }
+ }
+#endif // USE_DOMOTICZ
+#ifdef USE_WEBSERVER
+ } else {
+ WSContentSend_PD(HTTP_SNS_CO2, "CM11", cm11_last_ppm);
+ if(cm11_type == 2) { // With temp and humidity
+ WSContentSend_Temp("CM11", cm11_temperature);
+ }
+#endif // USE_WEBSERVER
+ }
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xsns95(uint8_t function)
+{
+ bool result = false;
+
+ if (cm11_type) {
+ switch (function) {
+ case FUNC_INIT:
+ CM11Init();
+ break;
+ case FUNC_EVERY_SECOND:
+ CM11EverySecond();
+ break;
+ case FUNC_COMMAND_SENSOR:
+ if (XSNS_95 == XdrvMailbox.index) {
+ result = CM11CommandSensor();
+ }
+ break;
+ case FUNC_JSON_APPEND:
+ CM11Show(1);
+ break;
+#ifdef USE_WEBSERVER
+ case FUNC_WEB_SENSOR:
+ CM11Show(0);
+ break;
+#endif // USE_WEBSERVER
+ }
+ }
+ return result;
+}
+
+#endif // USE_CM110x
diff --git a/tools/lv_gpio/lv_gpio_enum.h b/tools/lv_gpio/lv_gpio_enum.h
index 74f6c64f3..51e83ae7e 100644
--- a/tools/lv_gpio/lv_gpio_enum.h
+++ b/tools/lv_gpio/lv_gpio_enum.h
@@ -63,6 +63,8 @@ DSB_OUT = GPIO_DSB_OUT
WS2812 = GPIO_WS2812
MHZ_TXD = GPIO_MHZ_TXD
MHZ_RXD = GPIO_MHZ_RXD
+CM11_TXD = GPIO_CM11_TXD
+CM11_RXD = GPIO_CM11_RXD
PZEM0XX_TX = GPIO_PZEM0XX_TX
PZEM004_RX = GPIO_PZEM004_RX
PZEM016_RX = GPIO_PZEM016_RX