Add experimental SoftwareSerial MH-Z19(B) support

Add experimental support for sensor MH-Z19(B) using SoftwareSerial to be
enabled with define USE_MHZ19 in user_config.h (#561, #1248)
This commit is contained in:
arendst 2017-12-05 17:46:26 +01:00
parent a5afc3d227
commit 53c5728349
3 changed files with 287 additions and 9 deletions

View File

@ -1,6 +1,7 @@
/* 5.10.0a /* 5.10.0a
* Add (experimental) support for sensor SHT3x * Add (experimental) support for sensor SHT3x
* Add (experimental) support for sensor MH-Z19(B) to be enabled with define USE_MHZ19 in user_config.h (#561, #1248) * Add (experimental) support for sensor MH-Z19(B) to be enabled with define USE_MHZ19 in user_config.h (#561, #1248)
* Add (experimental) support for sensor MH-Z19(B) using SoftwareSerial 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) * 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) * Fix BME280 calculation (#1051)
* Change ADS1115 default voltage range from +/-2V to +/-6V (#1289) * Change ADS1115 default voltage range from +/-2V to +/-6V (#1289)

View File

@ -164,31 +164,32 @@
#define USE_ADC_VCC // Display Vcc in Power status. Disable for use as Analog input on selected devices #define USE_ADC_VCC // Display Vcc in Power status. Disable for use as Analog input on selected devices
// WARNING: Select none for default one DS18B20 sensor or enable one of the following two options for multiple sensors // WARNING: Select none for default one DS18B20 sensor or enable one of the following two options for multiple sensors
//#define USE_DS18x20 // Optional for more than one DS18x20 sensors with id sort, single scan and read retry (+1.3k code) //#define USE_DS18x20 // Optional for more than one DS18x20 sensors with id sort, single scan and read retry (+1k3 code)
//#define USE_DS18x20_LEGACY // Optional for more than one DS18x20 sensors with dynamic scan using library OneWire (+1.5k code) //#define USE_DS18x20_LEGACY // Optional for more than one DS18x20 sensors with dynamic scan using library OneWire (+1k5 code)
#define USE_I2C // I2C using library wire (+10k code, 0.2k mem) - Disable by // #define USE_I2C // I2C using library wire (+10k code, 0k2 mem) - Disable by //
#define USE_SHT // Add I2C emulating code for SHT1X sensor #define USE_SHT // Add I2C emulating code for SHT1X sensor
// #define USE_SHT3X // Add I2C code for SHT3x sensor // #define USE_SHT3X // Add I2C code for SHT3x sensor
#define USE_HTU // Add I2C code for HTU21/SI7013/SI7020/SI7021 sensor #define USE_HTU // Add I2C code for HTU21/SI7013/SI7020/SI7021 sensor
#define USE_BMP // Add I2C code for BMP/BME280 sensor #define USE_BMP // Add I2C code for BMP/BME280 sensor
#define USE_BH1750 // Add I2C code for BH1750 sensor #define USE_BH1750 // Add I2C code for BH1750 sensor
// #define USE_VEML6070 // Add I2C code for VEML6070 sensor (+0.5k code) // #define USE_VEML6070 // Add I2C code for VEML6070 sensor (+0k5 code)
// #define USE_ADS1115 // Add I2C code for ADS1115 16 bit A/D converter based on Adafruit ADS1x15 library (library not needed) (+0.7k code) // #define USE_ADS1115 // Add I2C code for ADS1115 16 bit A/D converter based on Adafruit ADS1x15 library (library not needed) (+0k7 code)
// #define USE_ADS1115_I2CDEV // Add I2C code for ADS1115 16 bit A/D converter using library i2cdevlib-Core and i2cdevlib-ADS1115 (+2k code) // #define USE_ADS1115_I2CDEV // Add I2C code for ADS1115 16 bit A/D converter using library i2cdevlib-Core and i2cdevlib-ADS1115 (+2k code)
// #define USE_INA219 // Add I2C code for INA219 Low voltage and current sensor (+1k code) // #define USE_INA219 // Add I2C code for INA219 Low voltage and current sensor (+1k code)
#define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0.3k mem) #define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0k3 mem)
// #define USE_IR_HVAC // Support for HVAC system using IR (+2k code) // #define USE_IR_HVAC // Support for HVAC system using IR (+2k code)
#define USE_IR_RECEIVE // Support for IR receiver (+5.5k code) #define USE_IR_RECEIVE // Support for IR receiver (+5k5 code)
#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem) - Disable by // #define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem) - Disable by //
#define USE_WS2812_CTYPE 1 // WS2812 Color type (0 - RGB, 1 - GRB, 2 - RGBW, 3 - GRBW) #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_WS2812_DMA // DMA supports only GPIO03 (= Serial RXD) (+1k mem). When USE_WS2812_DMA is enabled expect Exceptions on Pow
//#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor using hardware serial interface at 9600 bps //#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor using hardware serial interface at 9600 bps (+1k1 code)
//#define USE_MHZ19_SOFT_SERIAL // Add support for MH-Z19 CO2 sensor using iram free software serial interface at 9600 bps (+2k3 code)
#define USE_ARILUX_RF // Add support for Arilux RF remote controller (+0.8k code) #define USE_ARILUX_RF // Add support for Arilux RF remote controller (+0k8 code)
/*********************************************************************************************\ /*********************************************************************************************\
* Compile a minimal version if upgrade memory gets tight ONLY TO BE USED FOR UPGRADE STEP 1! * Compile a minimal version if upgrade memory gets tight ONLY TO BE USED FOR UPGRADE STEP 1!

View File

@ -0,0 +1,276 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
#ifdef USE_MHZ19_SOFT_SERIAL
/*********************************************************************************************\
* MH-Z19 - CO2 sensor
*
* Based on EspEasy plugin P049 by Dmitry (rel22 ___ inbox.ru)
**********************************************************************************************
* Filter usage
*
* Select filter usage on low stability readings
\*********************************************************************************************/
#include <SoftwareSerialNoIram.h>
SoftwareSerialNoIram *SoftSerial;
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<uint16_t>(mhz19_last_ppm + difference);
return true;
}
bool Mhz19Read(uint16_t &p, float &t)
{
bool status = false;
p = 0;
t = NAN;
if (mhz19_type)
{
SoftSerial->flush();
if (SoftSerial->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 (SoftSerial->available() > 0) {
mhz19_response[counter++] = SoftSerial->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) {
SoftSerial->write(mhz19_cmnd_abc_enable, 9); // Sent sensor ABC Enable
} else {
SoftSerial->write(mhz19_cmnd_abc_disable, 9); // Sent sensor ABC Disable
}
}
}
status = true;
}
}
}
}
return status;
}
void Mhz19Init()
{
SoftSerial = new SoftwareSerialNoIram(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD]);
SoftSerial->begin(9600);
mhz19_type = 1;
}
#ifdef USE_WEBSERVER
const char HTTP_SNS_CO2[] PROGMEM =
"%s{s}%s " D_CO2 "{m}%d " D_UNIT_PPM "{e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
#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_SOFT_SERIAL