Skip to content

Commit

Permalink
Add final support for MH-Z19(B)
Browse files Browse the repository at this point in the history
* Add support for sensor MH-Z19(B) to be enabled with define USE_MHZ19
in user_config.h (#561, #1248)
  • Loading branch information
arendst committed Dec 19, 2017
1 parent 0129fc9 commit 41e315b
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 648 deletions.
4 changes: 1 addition & 3 deletions sonoff/_releasenotes.ino
Expand Up @@ -4,12 +4,10 @@
* Change Wemo SetBinaryState to distinguish from GetBinaryState (#1357)
* Change output of HTTP command to valid JSON only (#1363)
* Change output to valid JSON Array if needed (#1363)
* Add support for sensor MH-Z19(B) to be enabled with define USE_MHZ19 in user_config.h (#561, #1248)
*
* 5.10.0a
* Add (experimental) support for sensor SHT3x
* Add support for sensor MH-Z19(B) using serial interface to be enabled with define USE_MHZ19_HARD_SERIAL in user_config.h (#561, #1248)
* Add (experimental) support for sensor MH-Z19(B) using SoftwareSerial to be enabled with define USE_MHZ19_SOFT_SERIAL_OBSOLETE in user_config.h (#561, #1248)
* Add (experimental) support for sensor MH-Z19(B) using stripped SoftwareSerial to be enabled with define USE_MHZ19_SOFT_SERIAL 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)
* Add support for BME680 using adafruit libraries (#1212)
Expand Down
4 changes: 1 addition & 3 deletions sonoff/user_config.h
Expand Up @@ -192,9 +192,7 @@
#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_MHZ19_HARD_SERIAL // Add support for MH-Z19 CO2 sensor using hardware serial interface at 9600 bps on GPIO1/3 only (+1k1 code)
//#define USE_MHZ19_SOFT_SERIAL // Add support for MH-Z19 CO2 sensor using software serial interface at 9600 bps (+2k3 code, 215 iram)
//#define USE_MHZ19_SOFT_SERIAL_OBSOLETE // Add support for MH-Z19 CO2 sensor using software serial interface at 9600 bps (+2k3 code, 420 iram)
//#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+1k8 code)

#define USE_ARILUX_RF // Add support for Arilux RF remote controller (+0k8 code)

Expand Down
162 changes: 74 additions & 88 deletions sonoff/xsns_15_mhz_softserial.ino → sonoff/xsns_15_mhz19.ino
@@ -1,7 +1,7 @@
/*
xsns_15_mhz.ino - MH-Z19 CO2 sensor support for Sonoff-Tasmota
xsns_15_mhz.ino - MH-Z19(B) CO2 sensor support for Sonoff-Tasmota
Copyright (C) 2017 Theo Arends
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
Expand All @@ -17,11 +17,11 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef USE_MHZ19_SOFT_SERIAL
#ifdef USE_MHZ19
/*********************************************************************************************\
* MH-Z19 - CO2 sensor
*
* Based on EspEasy plugin P049 by Dmitry (rel22 ___ inbox.ru)
* Adapted from EspEasy plugin P049 by Dmitry (rel22 ___ inbox.ru)
**********************************************************************************************
* Filter usage
*
Expand Down Expand Up @@ -53,22 +53,25 @@ enum Mhz19FilterOptions {MHZ19_FILTER_OFF, MHZ19_FILTER_OFF_ALLSAMPLES, MHZ19_FI
/*********************************************************************************************/

#define MHZ19_BAUDRATE 9600
#define MHZ19_READ_TIMEOUT 600 // Must be way less than 1000
#define MHZ19_READ_TIMEOUT 500 // Must be way less than 1000

const char kMhz19Types[] PROGMEM = "MHZ19|MHZ19B";

const uint8_t mhz19_cmnd_read_ppm[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
const uint8_t mhz19_cmnd_abc_enable[9] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00, 0xE6};
const uint8_t mhz19_cmnd_abc_disable[9] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86};

uint8_t mhz19_type = 0;
uint8_t mhz19_type = 1;
uint16_t mhz19_last_ppm = 0;
uint8_t mhz19_filter = MHZ19_FILTER_OPTION;
uint8_t mhz19_response[9];
bool mhz19_abc_enable = MHZ19_ABC_ENABLE;
bool mhz19_abc_must_apply = false;
char mhz19_types[7];

float mhz19_temperature = 0;
uint8_t mhz19_timer = 0;
Ticker mhz19_ticker;

/*********************************************************************************************\
* Subset SoftwareSerial
\*********************************************************************************************/
Expand All @@ -85,7 +88,7 @@ unsigned long mhz19_serial_bit_time;
unsigned long mhz19_serial_bit_time_start;

bool Mhz19SerialValidGpioPin(uint8_t pin) {
return (pin >= 0 && pin <= 5) || (pin >= 12 && pin <= 15);
return (pin >= 0 && pin <= 5) || (pin >= 9 && pin <= 10) || (pin >= 12 && pin <= 15);
}

bool Mhz19Serial(uint8_t receive_pin, uint8_t transmit_pin)
Expand Down Expand Up @@ -158,7 +161,7 @@ size_t Mhz19SerialWrite(const uint8_t *buffer, size_t size = 1) {
return n;
}

void Mhz19SerialRxRead() ICACHE_RAM_ATTR; // Add 215 bytes to iram usage
//void Mhz19SerialRxRead() ICACHE_RAM_ATTR; // Add 215 bytes to iram usage
void Mhz19SerialRxRead() {
// Advance the starting point for the samples but compensate for the
// initial delay which occurs before the interrupt is delivered
Expand Down Expand Up @@ -222,31 +225,29 @@ bool Mhz19CheckAndApplyFilter(uint16_t ppm, uint8_t s)
return true;
}

bool Mhz19Read(uint16_t &p, float &t)
void Mhz19222ms()
{
bool status = false;
uint8_t mhz19_response[9];

p = 0;
t = NAN;
mhz19_timer++;
if (6 == mhz19_timer) { // MH-Z19 measuring cycle takes 1005 +5% ms
mhz19_timer = 0;

if (mhz19_type)
{
Mhz19SerialFlush();
if (Mhz19SerialWrite(mhz19_cmnd_read_ppm, 9) != 9) {
return false; // Unable to send 9 bytes
}
memset(mhz19_response, 0, sizeof(mhz19_response));
uint32_t start = millis();
Mhz19SerialWrite(mhz19_cmnd_read_ppm, 9);
}

if (1 == mhz19_timer) {
unsigned long start = millis();
uint8_t counter = 0;
while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) {
if (Mhz19SerialAvailable() > 0) {
mhz19_response[counter++] = Mhz19SerialRead();
} else {
delay(10);
}
}
if (counter < 9){
return false; // Timeout while trying to read
if (counter < 9) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 comms timeout"));
return;
}

byte crc = 0;
Expand All @@ -255,61 +256,55 @@ bool Mhz19Read(uint16_t &p, float &t)
}
crc = 255 - crc;
crc++;
if (mhz19_response[8] != crc) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 crc error"));
return;
}
if (0xFF != mhz19_response[0] || 0x86 != mhz19_response[1]) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 bad response"));
return;
}

/*
// 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) {
Mhz19SerialWrite(mhz19_cmnd_abc_enable, 9); // Sent sensor ABC Enable
} else {
Mhz19SerialWrite(mhz19_cmnd_abc_disable, 9); // Sent sensor ABC Disable
}
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];
mhz19_temperature = ConvertTemp((float)mhz19_response[4] - 40);
uint8_t s = mhz19_response[5];
mhz19_type = (s) ? 1 : 2;
if (Mhz19CheckAndApplyFilter(ppm, s)) {

if (0 == s || 64 == s) { // Reading is stable.
if (mhz19_abc_must_apply) {
mhz19_abc_must_apply = false;
if (mhz19_abc_enable) {
Mhz19SerialWrite(mhz19_cmnd_abc_enable, 9); // Sent sensor ABC Enable
} else {
Mhz19SerialWrite(mhz19_cmnd_abc_disable, 9); // Sent sensor ABC Disable
}
}

status = true;
}

}
}
}
return status;
}

/*********************************************************************************************/

void Mhz19Init()
{
if (Mhz19Serial(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD])) {
mhz19_type = 1;
mhz19_type = 0;
if ((pin[GPIO_MHZ_RXD] < 99) && (pin[GPIO_MHZ_TXD] < 99)) {
if (Mhz19Serial(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD])) {
mhz19_type = 1;
mhz19_ticker.attach_ms(222, Mhz19222ms);
}
}
}

Expand All @@ -320,25 +315,20 @@ const char HTTP_SNS_CO2[] PROGMEM =

void Mhz19Show(boolean json)
{
uint16_t co2;
float t;
char temperature[10];
dtostrfd(mhz19_temperature, Settings.flag2.temperature_resolution, temperature);
GetTextIndexed(mhz19_types, sizeof(mhz19_types), mhz19_type -1, kMhz19Types);

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);
if (json) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_CO2 "\":%d,\"" D_TEMPERATURE "\":%s}"), mqtt_data, mhz19_types, mhz19_last_ppm, temperature);
#ifdef USE_DOMOTICZ
DomoticzSensor(DZ_COUNT, co2);
DomoticzSensor(DZ_COUNT, mhz19_last_ppm);
#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());
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_CO2, mqtt_data, mhz19_types, mhz19_last_ppm);
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, mhz19_types, temperature, TempUnit());
#endif // USE_WEBSERVER
}
}
}

Expand All @@ -352,26 +342,22 @@ boolean Xsns15(byte function)
{
boolean result = false;

if ((pin[GPIO_MHZ_RXD] < 99) && (pin[GPIO_MHZ_TXD] < 99)) {
if (mhz19_type) {
switch (function) {
case FUNC_XSNS_INIT:
Mhz19Init();
break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
// Mhz19Prep();
break;
case FUNC_XSNS_JSON_APPEND:
Mhz19Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
Mhz19Show(0);
// Mhz19Prep();
break;
#endif // USE_WEBSERVER
}
}
return result;
}

#endif // USE_MHZ19_SOFT_SERIAL
#endif // USE_MHZ19

0 comments on commit 41e315b

Please sign in to comment.