From 30a5355526c1a35fb41f2f9ad3fb25913b6cfd0f Mon Sep 17 00:00:00 2001
From: jaapgvk <40623760+jaapgvk@users.noreply.github.com>
Date: Fri, 1 Jan 2021 20:47:03 +0100
Subject: [PATCH 1/2] Delete xsns_15_mhz19.ino
---
tasmota/xsns_15_mhz19.ino | 396 --------------------------------------
1 file changed, 396 deletions(-)
delete mode 100644 tasmota/xsns_15_mhz19.ino
diff --git a/tasmota/xsns_15_mhz19.ino b/tasmota/xsns_15_mhz19.ino
deleted file mode 100644
index 0444bce64..000000000
--- a/tasmota/xsns_15_mhz19.ino
+++ /dev/null
@@ -1,396 +0,0 @@
-/*
- xsns_15_mhz19.ino - MH-Z19(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_MHZ19
-/*********************************************************************************************\
- * MH-Z19 - CO2 sensor
- *
- * Adapted from EspEasy plugin P049 by Dmitry (rel22 ___ inbox.ru)
- *
- * Hardware Serial will be selected if GPIO1 = [MHZ Rx] and GPIO3 = [MHZ Tx]
- **********************************************************************************************
- * Filter usage
- *
- * Select filter usage on low stability readings
-\*********************************************************************************************/
-
-#define XSNS_15 15
-
-enum MhzFilterOptions {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) is enabled by default but may be disabled with command
- * Sensor15 0
- * and enabled again with command
- * Sensor15 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 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.
-\*********************************************************************************************/
-
-#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 MHZ19_READ_TIMEOUT 400 // Must be way less than 1000 but enough to read 9 bytes at 9600 bps
-#define MHZ19_RETRY_COUNT 8
-
-TasmotaSerial *MhzSerial;
-
-const char kMhzModels[] PROGMEM = "|B";
-
-const char ABC_ENABLED[] = "ABC is Enabled";
-const char ABC_DISABLED[] = "ABC is Disabled";
-
-enum MhzCommands { MHZ_CMND_READPPM, MHZ_CMND_ABCENABLE, MHZ_CMND_ABCDISABLE, MHZ_CMND_ZEROPOINT, MHZ_CMND_RESET, MHZ_CMND_RANGE_1000, MHZ_CMND_RANGE_2000, MHZ_CMND_RANGE_3000, MHZ_CMND_RANGE_5000 };
-const uint8_t kMhzCommands[][4] PROGMEM = {
-// 2 3 6 7
- {0x86,0x00,0x00,0x00}, // mhz_cmnd_read_ppm
- {0x79,0xA0,0x00,0x00}, // mhz_cmnd_abc_enable
- {0x79,0x00,0x00,0x00}, // mhz_cmnd_abc_disable
- {0x87,0x00,0x00,0x00}, // mhz_cmnd_zeropoint
- {0x8D,0x00,0x00,0x00}, // mhz_cmnd_reset
- {0x99,0x00,0x03,0xE8}, // mhz_cmnd_set_range_1000
- {0x99,0x00,0x07,0xD0}, // mhz_cmnd_set_range_2000
- {0x99,0x00,0x0B,0xB8}, // mhz_cmnd_set_range_3000
- {0x99,0x00,0x13,0x88}}; // mhz_cmnd_set_range_5000
-
-uint8_t mhz_type = 1;
-uint16_t mhz_last_ppm = 0;
-uint8_t mhz_filter = MHZ19_FILTER_OPTION;
-bool mhz_abc_must_apply = false;
-
-float mhz_temperature = 0;
-uint8_t mhz_retry = MHZ19_RETRY_COUNT;
-uint8_t mhz_received = 0;
-uint8_t mhz_state = 0;
-
-/*********************************************************************************************/
-
-uint8_t MhzCalculateChecksum(uint8_t *array)
-{
- uint8_t checksum = 0;
- for (uint32_t i = 1; i < 8; i++) {
- checksum += array[i];
- }
- checksum = 255 - checksum;
- return (checksum +1);
-}
-
-size_t MhzSendCmd(uint8_t command_id)
-{
- uint8_t mhz_send[9] = { 0 };
-
- mhz_send[0] = 0xFF; // Start byte, fixed
- mhz_send[1] = 0x01; // Sensor number, 0x01 by default
- memcpy_P(&mhz_send[2], kMhzCommands[command_id], sizeof(uint16_t));
-/*
- mhz_send[4] = 0x00;
- mhz_send[5] = 0x00;
-*/
- memcpy_P(&mhz_send[6], kMhzCommands[command_id] + sizeof(uint16_t), sizeof(uint16_t));
- mhz_send[8] = MhzCalculateChecksum(mhz_send);
-
-// AddLog_P(LOG_LEVEL_DEBUG, PSTR("Final MhzCommand: %x %x %x %x %x %x %x %x %x"),mhz_send[0],mhz_send[1],mhz_send[2],mhz_send[3],mhz_send[4],mhz_send[5],mhz_send[6],mhz_send[7],mhz_send[8]);
-
- return MhzSerial->write(mhz_send, sizeof(mhz_send));
-}
-
-/*********************************************************************************************/
-
-bool MhzCheckAndApplyFilter(uint16_t ppm, uint8_t s)
-{
- if (1 == s) {
- return false; // S==1 => "A" version sensor bootup, do not use values.
- }
- if (mhz_last_ppm < 400 || mhz_last_ppm > 5000) {
- // Prevent unrealistic values during start-up with filtering enabled.
- // Just assume the entered value is correct.
- mhz_last_ppm = ppm;
- return true;
- }
- int32_t difference = ppm - mhz_last_ppm;
- if (s > 0 && s < 64 && mhz_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 *= s;
- difference /= 64;
- }
- if (MHZ19_FILTER_OFF == mhz_filter) {
- if (s != 0 && s != 64) {
- return false;
- }
- } else {
- difference >>= (mhz_filter -1);
- }
- mhz_last_ppm = static_cast(mhz_last_ppm + difference);
- return true;
-}
-
-void MhzEverySecond(void)
-{
- mhz_state++;
- if (8 == mhz_state) { // Every 8 sec start a MH-Z19 measuring cycle (which takes 1005 +5% ms)
- mhz_state = 0;
-
- if (mhz_retry) {
- mhz_retry--;
- if (!mhz_retry) {
- mhz_last_ppm = 0;
- mhz_temperature = 0;
- }
- }
-
- MhzSerial->flush(); // Sync reception
- MhzSendCmd(MHZ_CMND_READPPM);
- mhz_received = 0;
- }
-
- if ((mhz_state > 2) && !mhz_received) { // Start reading response after 3 seconds every second until received
- uint8_t mhz_response[9];
-
- unsigned long start = millis();
- uint8_t counter = 0;
- while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) {
- if (MhzSerial->available() > 0) {
- mhz_response[counter++] = MhzSerial->read();
- } else {
- delay(5);
- }
- }
-
- AddLogBuffer(LOG_LEVEL_DEBUG_MORE, mhz_response, counter);
-
- if (counter < 9) {
-// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 comms timeout"));
- return;
- }
-
- uint8_t crc = MhzCalculateChecksum(mhz_response);
- if (mhz_response[8] != crc) {
-// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 crc error"));
- return;
- }
- if (0xFF != mhz_response[0] || 0x86 != mhz_response[1]) {
-// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 bad response"));
- return;
- }
-
- mhz_received = 1;
-
- uint16_t u = (mhz_response[6] << 8) | mhz_response[7];
- if (15000 == u) { // During (and only ever at) sensor boot, 'u' is reported as 15000
- if (Settings.SensorBits1.mhz19b_abc_disable) {
- // After bootup of the sensor the ABC will be enabled.
- // Thus only actively disable after bootup.
- mhz_abc_must_apply = true;
- }
- } else {
- uint16_t ppm = (mhz_response[2] << 8) | mhz_response[3];
- mhz_temperature = ConvertTemp((float)mhz_response[4] - 40);
- uint8_t s = mhz_response[5];
- mhz_type = (s) ? 1 : 2;
- if (MhzCheckAndApplyFilter(ppm, s)) {
- mhz_retry = MHZ19_RETRY_COUNT;
-#ifdef USE_LIGHT
- LightSetSignal(CO2_LOW, CO2_HIGH, mhz_last_ppm);
-#endif // USE_LIGHT
-
- if (0 == s || 64 == s) { // Reading is stable.
- if (mhz_abc_must_apply) {
- mhz_abc_must_apply = false;
- if (!Settings.SensorBits1.mhz19b_abc_disable) {
- MhzSendCmd(MHZ_CMND_ABCENABLE);
- } else {
- MhzSendCmd(MHZ_CMND_ABCDISABLE);
- }
- }
- }
-
- }
- }
-
- }
-}
-
-/*********************************************************************************************\
- * Command Sensor15
- *
- * 0 - ABC Off
- * 1 - ABC On (Default)
- * 2 - Manual start = ABC Off
- * 3 - (Not implemented) Optional filter settings
- * 9 - Reset
- * 1000 - Range
- * 2000 - Range
- * 3000 - Range
- * 5000 - Range
-\*********************************************************************************************/
-
-#define D_JSON_RANGE_1000 "1000 ppm range"
-#define D_JSON_RANGE_2000 "2000 ppm range"
-#define D_JSON_RANGE_3000 "3000 ppm range"
-#define D_JSON_RANGE_5000 "5000 ppm range"
-
-bool MhzCommandSensor(void)
-{
- bool serviced = true;
-
- switch (XdrvMailbox.payload) {
- case 0:
- Settings.SensorBits1.mhz19b_abc_disable = true;
- MhzSendCmd(MHZ_CMND_ABCDISABLE);
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED);
- break;
- case 1:
- Settings.SensorBits1.mhz19b_abc_disable = false;
- MhzSendCmd(MHZ_CMND_ABCENABLE);
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED);
- break;
- case 2:
- MhzSendCmd(MHZ_CMND_ZEROPOINT);
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_ZERO_POINT_CALIBRATION);
- break;
- case 9:
- MhzSendCmd(MHZ_CMND_RESET);
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RESET);
- break;
- case 1000:
- MhzSendCmd(MHZ_CMND_RANGE_1000);
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_1000);
- break;
- case 2000:
- MhzSendCmd(MHZ_CMND_RANGE_2000);
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_2000);
- break;
- case 3000:
- MhzSendCmd(MHZ_CMND_RANGE_3000);
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_3000);
- break;
- case 5000:
- MhzSendCmd(MHZ_CMND_RANGE_5000);
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_5000);
- break;
- default:
- if (!Settings.SensorBits1.mhz19b_abc_disable) {
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED);
- } else {
- Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED);
- }
- }
-
- return serviced;
-}
-
-/*********************************************************************************************/
-
-void MhzInit(void)
-{
- mhz_type = 0;
- if (PinUsed(GPIO_MHZ_RXD) && PinUsed(GPIO_MHZ_TXD)) {
- MhzSerial = new TasmotaSerial(Pin(GPIO_MHZ_RXD), Pin(GPIO_MHZ_TXD), 1);
- if (MhzSerial->begin(9600)) {
- if (MhzSerial->hardwareSerial()) { ClaimSerial(); }
- mhz_type = 1;
- }
-
- }
-}
-
-void MhzShow(bool json)
-{
- char types[7] = "MHZ19B"; // MHZ19B for legacy reasons. Prefered is MHZ19
- char temperature[33];
- dtostrfd(mhz_temperature, Settings.flag2.temperature_resolution, temperature);
- char model[3];
- GetTextIndexed(model, sizeof(model), mhz_type -1, kMhzModels);
-
- if (json) {
- ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_MODEL "\":\"%s\",\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s}"), types, model, mhz_last_ppm, temperature);
-#ifdef USE_DOMOTICZ
- if (0 == TasmotaGlobal.tele_period) {
- DomoticzSensor(DZ_AIRQUALITY, mhz_last_ppm);
- DomoticzSensor(DZ_TEMP, temperature);
- }
-#endif // USE_DOMOTICZ
-#ifdef USE_WEBSERVER
- } else {
- WSContentSend_PD(HTTP_SNS_CO2, types, mhz_last_ppm);
- WSContentSend_PD(HTTP_SNS_TEMP, types, temperature, TempUnit());
-#endif // USE_WEBSERVER
- }
-}
-
-/*********************************************************************************************\
- * Interface
-\*********************************************************************************************/
-
-bool Xsns15(uint8_t function)
-{
- bool result = false;
-
- if (mhz_type) {
- switch (function) {
- case FUNC_INIT:
- MhzInit();
- break;
- case FUNC_EVERY_SECOND:
- MhzEverySecond();
- break;
- case FUNC_COMMAND_SENSOR:
- if (XSNS_15 == XdrvMailbox.index) {
- result = MhzCommandSensor();
- }
- break;
- case FUNC_JSON_APPEND:
- MhzShow(1);
- break;
-#ifdef USE_WEBSERVER
- case FUNC_WEB_SENSOR:
- MhzShow(0);
- break;
-#endif // USE_WEBSERVER
- }
- }
- return result;
-}
-
-#endif // USE_MHZ19
From 2b12e995d38befcf05eada03f69b15ffdf301a6b Mon Sep 17 00:00:00 2001
From: jaapgvk <40623760+jaapgvk@users.noreply.github.com>
Date: Fri, 1 Jan 2021 20:47:29 +0100
Subject: [PATCH 2/2] Add files via upload
---
tasmota/xsns_15_mhz19.ino | 402 ++++++++++++++++++++++++++++++++++++++
1 file changed, 402 insertions(+)
create mode 100644 tasmota/xsns_15_mhz19.ino
diff --git a/tasmota/xsns_15_mhz19.ino b/tasmota/xsns_15_mhz19.ino
new file mode 100644
index 000000000..a2afb267f
--- /dev/null
+++ b/tasmota/xsns_15_mhz19.ino
@@ -0,0 +1,402 @@
+/*
+ xsns_15_mhz19.ino - MH-Z19(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_MHZ19
+/*********************************************************************************************\
+ * MH-Z19 - CO2 sensor
+ *
+ * Adapted from EspEasy plugin P049 by Dmitry (rel22 ___ inbox.ru)
+ *
+ * Hardware Serial will be selected if GPIO1 = [MHZ Rx] and GPIO3 = [MHZ Tx]
+ **********************************************************************************************
+ * Filter usage
+ *
+ * Select filter usage on low stability readings
+\*********************************************************************************************/
+
+#define XSNS_15 15
+
+enum MhzFilterOptions {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) is enabled by default but may be disabled with command
+ * Sensor15 0
+ * and enabled again with command
+ * Sensor15 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 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.
+\*********************************************************************************************/
+
+#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 MHZ19_READ_TIMEOUT 400 // Must be way less than 1000 but enough to read 9 bytes at 9600 bps
+#define MHZ19_RETRY_COUNT 8
+
+TasmotaSerial *MhzSerial;
+
+const char kMhzModels[] PROGMEM = "|B";
+
+const char ABC_ENABLED[] = "ABC is Enabled";
+const char ABC_DISABLED[] = "ABC is Disabled";
+
+enum MhzCommands { MHZ_CMND_READPPM, MHZ_CMND_ABCENABLE, MHZ_CMND_ABCDISABLE, MHZ_CMND_ZEROPOINT, MHZ_CMND_RESET, MHZ_CMND_RANGE_1000, MHZ_CMND_RANGE_2000, MHZ_CMND_RANGE_3000, MHZ_CMND_RANGE_5000, MHZ_CMND_RANGE_10000 };
+const uint8_t kMhzCommands[][4] PROGMEM = {
+// 2 3 6 7
+ {0x86,0x00,0x00,0x00}, // mhz_cmnd_read_ppm
+ {0x79,0xA0,0x00,0x00}, // mhz_cmnd_abc_enable
+ {0x79,0x00,0x00,0x00}, // mhz_cmnd_abc_disable
+ {0x87,0x00,0x00,0x00}, // mhz_cmnd_zeropoint
+ {0x8D,0x00,0x00,0x00}, // mhz_cmnd_reset
+ {0x99,0x00,0x03,0xE8}, // mhz_cmnd_set_range_1000
+ {0x99,0x00,0x07,0xD0}, // mhz_cmnd_set_range_2000
+ {0x99,0x00,0x0B,0xB8}, // mhz_cmnd_set_range_3000
+ {0x99,0x00,0x13,0x88}, // mhz_cmnd_set_range_5000
+ {0x99,0x00,0x27,0x10}}; // mhz_cmnd_set_range_10000
+
+uint8_t mhz_type = 1;
+uint16_t mhz_last_ppm = 0;
+uint8_t mhz_filter = MHZ19_FILTER_OPTION;
+bool mhz_abc_must_apply = false;
+
+float mhz_temperature = 0;
+uint8_t mhz_retry = MHZ19_RETRY_COUNT;
+uint8_t mhz_received = 0;
+uint8_t mhz_state = 0;
+
+/*********************************************************************************************/
+
+uint8_t MhzCalculateChecksum(uint8_t *array)
+{
+ uint8_t checksum = 0;
+ for (uint32_t i = 1; i < 8; i++) {
+ checksum += array[i];
+ }
+ checksum = 255 - checksum;
+ return (checksum +1);
+}
+
+size_t MhzSendCmd(uint8_t command_id)
+{
+ uint8_t mhz_send[9] = { 0 };
+
+ mhz_send[0] = 0xFF; // Start byte, fixed
+ mhz_send[1] = 0x01; // Sensor number, 0x01 by default
+ memcpy_P(&mhz_send[2], kMhzCommands[command_id], sizeof(uint16_t));
+/*
+ mhz_send[4] = 0x00;
+ mhz_send[5] = 0x00;
+*/
+ memcpy_P(&mhz_send[6], kMhzCommands[command_id] + sizeof(uint16_t), sizeof(uint16_t));
+ mhz_send[8] = MhzCalculateChecksum(mhz_send);
+
+// AddLog_P(LOG_LEVEL_DEBUG, PSTR("Final MhzCommand: %x %x %x %x %x %x %x %x %x"),mhz_send[0],mhz_send[1],mhz_send[2],mhz_send[3],mhz_send[4],mhz_send[5],mhz_send[6],mhz_send[7],mhz_send[8]);
+
+ return MhzSerial->write(mhz_send, sizeof(mhz_send));
+}
+
+/*********************************************************************************************/
+
+bool MhzCheckAndApplyFilter(uint16_t ppm, uint8_t s)
+{
+ if (1 == s) {
+ return false; // S==1 => "A" version sensor bootup, do not use values.
+ }
+ if (mhz_last_ppm < 400 || mhz_last_ppm > 10000) {
+ // Prevent unrealistic values during start-up with filtering enabled.
+ // Just assume the entered value is correct.
+ mhz_last_ppm = ppm;
+ return true;
+ }
+ int32_t difference = ppm - mhz_last_ppm;
+ if (s > 0 && s < 64 && mhz_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 *= s;
+ difference /= 64;
+ }
+ if (MHZ19_FILTER_OFF == mhz_filter) {
+ if (s != 0 && s != 64) {
+ return false;
+ }
+ } else {
+ difference >>= (mhz_filter -1);
+ }
+ mhz_last_ppm = static_cast(mhz_last_ppm + difference);
+ return true;
+}
+
+void MhzEverySecond(void)
+{
+ mhz_state++;
+ if (8 == mhz_state) { // Every 8 sec start a MH-Z19 measuring cycle (which takes 1005 +5% ms)
+ mhz_state = 0;
+
+ if (mhz_retry) {
+ mhz_retry--;
+ if (!mhz_retry) {
+ mhz_last_ppm = 0;
+ mhz_temperature = 0;
+ }
+ }
+
+ MhzSerial->flush(); // Sync reception
+ MhzSendCmd(MHZ_CMND_READPPM);
+ mhz_received = 0;
+ }
+
+ if ((mhz_state > 2) && !mhz_received) { // Start reading response after 3 seconds every second until received
+ uint8_t mhz_response[9];
+
+ unsigned long start = millis();
+ uint8_t counter = 0;
+ while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) {
+ if (MhzSerial->available() > 0) {
+ mhz_response[counter++] = MhzSerial->read();
+ } else {
+ delay(5);
+ }
+ }
+
+ AddLogBuffer(LOG_LEVEL_DEBUG_MORE, mhz_response, counter);
+
+ if (counter < 9) {
+// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 comms timeout"));
+ return;
+ }
+
+ uint8_t crc = MhzCalculateChecksum(mhz_response);
+ if (mhz_response[8] != crc) {
+// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 crc error"));
+ return;
+ }
+ if (0xFF != mhz_response[0] || 0x86 != mhz_response[1]) {
+// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 bad response"));
+ return;
+ }
+
+ mhz_received = 1;
+
+ uint16_t u = (mhz_response[6] << 8) | mhz_response[7];
+ if (15000 == u) { // During (and only ever at) sensor boot, 'u' is reported as 15000
+ if (Settings.SensorBits1.mhz19b_abc_disable) {
+ // After bootup of the sensor the ABC will be enabled.
+ // Thus only actively disable after bootup.
+ mhz_abc_must_apply = true;
+ }
+ } else {
+ uint16_t ppm = (mhz_response[2] << 8) | mhz_response[3];
+ mhz_temperature = ConvertTemp((float)mhz_response[4] - 40);
+ uint8_t s = mhz_response[5];
+ mhz_type = (s) ? 1 : 2;
+ if (MhzCheckAndApplyFilter(ppm, s)) {
+ mhz_retry = MHZ19_RETRY_COUNT;
+#ifdef USE_LIGHT
+ LightSetSignal(CO2_LOW, CO2_HIGH, mhz_last_ppm);
+#endif // USE_LIGHT
+
+ if (0 == s || 64 == s) { // Reading is stable.
+ if (mhz_abc_must_apply) {
+ mhz_abc_must_apply = false;
+ if (!Settings.SensorBits1.mhz19b_abc_disable) {
+ MhzSendCmd(MHZ_CMND_ABCENABLE);
+ } else {
+ MhzSendCmd(MHZ_CMND_ABCDISABLE);
+ }
+ }
+ }
+
+ }
+ }
+
+ }
+}
+
+/*********************************************************************************************\
+ * Command Sensor15
+ *
+ * 0 - ABC Off
+ * 1 - ABC On (Default)
+ * 2 - Manual start = ABC Off
+ * 3 - (Not implemented) Optional filter settings
+ * 9 - Reset
+ * 1000 - Range
+ * 2000 - Range
+ * 3000 - Range
+ * 5000 - Range
+\*********************************************************************************************/
+
+#define D_JSON_RANGE_1000 "1000 ppm range"
+#define D_JSON_RANGE_2000 "2000 ppm range"
+#define D_JSON_RANGE_3000 "3000 ppm range"
+#define D_JSON_RANGE_5000 "5000 ppm range"
+#define D_JSON_RANGE_10000 "10000 ppm range"
+
+bool MhzCommandSensor(void)
+{
+ bool serviced = true;
+
+ switch (XdrvMailbox.payload) {
+ case 0:
+ Settings.SensorBits1.mhz19b_abc_disable = true;
+ MhzSendCmd(MHZ_CMND_ABCDISABLE);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED);
+ break;
+ case 1:
+ Settings.SensorBits1.mhz19b_abc_disable = false;
+ MhzSendCmd(MHZ_CMND_ABCENABLE);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED);
+ break;
+ case 2:
+ MhzSendCmd(MHZ_CMND_ZEROPOINT);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_ZERO_POINT_CALIBRATION);
+ break;
+ case 9:
+ MhzSendCmd(MHZ_CMND_RESET);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RESET);
+ break;
+ case 1000:
+ MhzSendCmd(MHZ_CMND_RANGE_1000);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_1000);
+ break;
+ case 2000:
+ MhzSendCmd(MHZ_CMND_RANGE_2000);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_2000);
+ break;
+ case 3000:
+ MhzSendCmd(MHZ_CMND_RANGE_3000);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_3000);
+ break;
+ case 5000:
+ MhzSendCmd(MHZ_CMND_RANGE_5000);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_5000);
+ break;
+ case 10000:
+ MhzSendCmd(MHZ_CMND_RANGE_10000);
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_10000);
+ break;
+ default:
+ if (!Settings.SensorBits1.mhz19b_abc_disable) {
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED);
+ } else {
+ Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED);
+ }
+ }
+
+ return serviced;
+}
+
+/*********************************************************************************************/
+
+void MhzInit(void)
+{
+ mhz_type = 0;
+ if (PinUsed(GPIO_MHZ_RXD) && PinUsed(GPIO_MHZ_TXD)) {
+ MhzSerial = new TasmotaSerial(Pin(GPIO_MHZ_RXD), Pin(GPIO_MHZ_TXD), 1);
+ if (MhzSerial->begin(9600)) {
+ if (MhzSerial->hardwareSerial()) { ClaimSerial(); }
+ mhz_type = 1;
+ }
+
+ }
+}
+
+void MhzShow(bool json)
+{
+ char types[7] = "MHZ19B"; // MHZ19B for legacy reasons. Prefered is MHZ19
+ char temperature[33];
+ dtostrfd(mhz_temperature, Settings.flag2.temperature_resolution, temperature);
+ char model[3];
+ GetTextIndexed(model, sizeof(model), mhz_type -1, kMhzModels);
+
+ if (json) {
+ ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_MODEL "\":\"%s\",\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s}"), types, model, mhz_last_ppm, temperature);
+#ifdef USE_DOMOTICZ
+ if (0 == TasmotaGlobal.tele_period) {
+ DomoticzSensor(DZ_AIRQUALITY, mhz_last_ppm);
+ DomoticzSensor(DZ_TEMP, temperature);
+ }
+#endif // USE_DOMOTICZ
+#ifdef USE_WEBSERVER
+ } else {
+ WSContentSend_PD(HTTP_SNS_CO2, types, mhz_last_ppm);
+ WSContentSend_PD(HTTP_SNS_TEMP, types, temperature, TempUnit());
+#endif // USE_WEBSERVER
+ }
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xsns15(uint8_t function)
+{
+ bool result = false;
+
+ if (mhz_type) {
+ switch (function) {
+ case FUNC_INIT:
+ MhzInit();
+ break;
+ case FUNC_EVERY_SECOND:
+ MhzEverySecond();
+ break;
+ case FUNC_COMMAND_SENSOR:
+ if (XSNS_15 == XdrvMailbox.index) {
+ result = MhzCommandSensor();
+ }
+ break;
+ case FUNC_JSON_APPEND:
+ MhzShow(1);
+ break;
+#ifdef USE_WEBSERVER
+ case FUNC_WEB_SENSOR:
+ MhzShow(0);
+ break;
+#endif // USE_WEBSERVER
+ }
+ }
+ return result;
+}
+
+#endif // USE_MHZ19