Updated MH-Z19 drivers

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)
 * Change
ADS1115 default voltage range from +/-2V to +/-6V (#1289)
 * Add
multipress support and more user configurable options to Sonoff Dual R2
(#1291)
 * Fix Sonoff Bridge missed learned key if learned data contains
0x55 (End of Transmission) flag (#1095, #1294)
 * Add support for
TSL2561 using adafruit library (#661, #1311)
 * Add alternative support
for SHT3x (#1314)
This commit is contained in:
arendst 2017-12-08 14:14:10 +01:00
parent 4cf64af51e
commit 0747e0fc68
19 changed files with 701 additions and 892 deletions

View File

@ -1,11 +0,0 @@
# EspSoftwareSerial
Implementation of the Arduino software serial library for the ESP8266
Same functionality as the corresponding AVR library but several instances can be active at the same time.
Speed up to 115200 baud is supported. The constructor also has an optional input buffer size.
Please note that due to the fact that the ESP always have other activities ongoing, there will be some inexactness in interrupt
timings. This may lead to bit errors when having heavy data traffic in high baud rates.

View File

@ -1,228 +0,0 @@
/*
SoftwareSerial.cpp - Implementation of the Arduino software serial for ESP8266.
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
// The Arduino standard GPIO routines are not enough,
// must use some from the Espressif SDK as well
extern "C" {
#include "gpio.h"
}
#include <SoftwareSerial.h>
#define MAX_PIN 15
// As the Arduino attachInterrupt has no parameter, lists of objects
// and callbacks corresponding to each possible GPIO pins have to be defined
SoftwareSerial *ObjList[MAX_PIN+1];
void ICACHE_RAM_ATTR sws_isr_0() { ObjList[0]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_1() { ObjList[1]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_2() { ObjList[2]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_3() { ObjList[3]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_4() { ObjList[4]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_5() { ObjList[5]->rxRead(); };
// Pin 6 to 11 can not be used
void ICACHE_RAM_ATTR sws_isr_12() { ObjList[12]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_13() { ObjList[13]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_14() { ObjList[14]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_15() { ObjList[15]->rxRead(); };
static void (*ISRList[MAX_PIN+1])() = {
sws_isr_0,
sws_isr_1,
sws_isr_2,
sws_isr_3,
sws_isr_4,
sws_isr_5,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
sws_isr_12,
sws_isr_13,
sws_isr_14,
sws_isr_15
};
SoftwareSerial::SoftwareSerial(int receivePin, int transmitPin, bool inverse_logic, unsigned int buffSize) {
m_rxValid = m_txValid = m_txEnableValid = false;
m_buffer = NULL;
m_invert = inverse_logic;
m_overflow = false;
m_rxEnabled = false;
if (isValidGPIOpin(receivePin)) {
m_rxPin = receivePin;
m_buffSize = buffSize;
m_buffer = (uint8_t*)malloc(m_buffSize);
if (m_buffer != NULL) {
m_rxValid = true;
m_inPos = m_outPos = 0;
pinMode(m_rxPin, INPUT);
ObjList[m_rxPin] = this;
enableRx(true);
}
}
if (isValidGPIOpin(transmitPin) || transmitPin == 16) {
m_txValid = true;
m_txPin = transmitPin;
pinMode(m_txPin, OUTPUT);
digitalWrite(m_txPin, !m_invert);
}
// Default speed
begin(9600);
}
SoftwareSerial::~SoftwareSerial() {
enableRx(false);
if (m_rxValid)
ObjList[m_rxPin] = NULL;
if (m_buffer)
free(m_buffer);
}
bool SoftwareSerial::isValidGPIOpin(int pin) {
return (pin >= 0 && pin <= 5) || (pin >= 12 && pin <= MAX_PIN);
}
void SoftwareSerial::begin(long speed) {
// Use getCycleCount() loop to get as exact timing as possible
m_bitTime = ESP.getCpuFreqMHz()*1000000/speed;
m_highSpeed = speed > 9600;
if (!m_rxEnabled)
enableRx(true);
}
long SoftwareSerial::baudRate() {
return ESP.getCpuFreqMHz()*1000000/m_bitTime;
}
void SoftwareSerial::setTransmitEnablePin(int transmitEnablePin) {
if (isValidGPIOpin(transmitEnablePin)) {
m_txEnableValid = true;
m_txEnablePin = transmitEnablePin;
pinMode(m_txEnablePin, OUTPUT);
digitalWrite(m_txEnablePin, LOW);
} else {
m_txEnableValid = false;
}
}
void SoftwareSerial::enableRx(bool on) {
if (m_rxValid) {
if (on)
attachInterrupt(m_rxPin, ISRList[m_rxPin], m_invert ? RISING : FALLING);
else
detachInterrupt(m_rxPin);
m_rxEnabled = on;
}
}
int SoftwareSerial::read() {
if (!m_rxValid || (m_inPos == m_outPos)) return -1;
uint8_t ch = m_buffer[m_outPos];
m_outPos = (m_outPos+1) % m_buffSize;
return ch;
}
int SoftwareSerial::available() {
if (!m_rxValid) return 0;
int avail = m_inPos - m_outPos;
if (avail < 0) avail += m_buffSize;
return avail;
}
#define WAIT { while (ESP.getCycleCount()-start < wait) if (!m_highSpeed) optimistic_yield(1); wait += m_bitTime; }
size_t SoftwareSerial::write(uint8_t b) {
if (!m_txValid) return 0;
if (m_invert) b = ~b;
if (m_highSpeed)
// Disable interrupts in order to get a clean transmit
cli();
if (m_txEnableValid) digitalWrite(m_txEnablePin, HIGH);
unsigned long wait = m_bitTime;
digitalWrite(m_txPin, HIGH);
unsigned long start = ESP.getCycleCount();
// Start bit;
digitalWrite(m_txPin, LOW);
WAIT;
for (int i = 0; i < 8; i++) {
digitalWrite(m_txPin, (b & 1) ? HIGH : LOW);
WAIT;
b >>= 1;
}
// Stop bit
digitalWrite(m_txPin, HIGH);
WAIT;
if (m_txEnableValid) digitalWrite(m_txEnablePin, LOW);
if (m_highSpeed)
sei();
return 1;
}
void SoftwareSerial::flush() {
m_inPos = m_outPos = 0;
}
bool SoftwareSerial::overflow() {
bool res = m_overflow;
m_overflow = false;
return res;
}
int SoftwareSerial::peek() {
if (!m_rxValid || (m_inPos == m_outPos)) return -1;
return m_buffer[m_outPos];
}
void ICACHE_RAM_ATTR SoftwareSerial::rxRead() {
// Advance the starting point for the samples but compensate for the
// initial delay which occurs before the interrupt is delivered
unsigned long wait = m_bitTime + m_bitTime/3 - 500;
unsigned long start = ESP.getCycleCount();
uint8_t rec = 0;
for (int i = 0; i < 8; i++) {
WAIT;
rec >>= 1;
if (digitalRead(m_rxPin))
rec |= 0x80;
}
if (m_invert) rec = ~rec;
// Stop bit
WAIT;
// Store the received value in the buffer unless we have an overflow
int next = (m_inPos+1) % m_buffSize;
if (next != m_outPos) {
m_buffer[m_inPos] = rec;
m_inPos = next;
} else {
m_overflow = true;
}
// Must clear this bit in the interrupt register,
// it gets set even when interrupts are disabled
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << m_rxPin);
}

View File

@ -1,88 +0,0 @@
/*
SoftwareSerial.h
SoftwareSerial.cpp - Implementation of the Arduino software serial for ESP8266.
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef SoftwareSerial_h
#define SoftwareSerial_h
#include <inttypes.h>
#include <Stream.h>
// This class is compatible with the corresponding AVR one,
// the constructor however has an optional rx buffer size.
// Speed up to 115200 can be used.
class SoftwareSerial : public Stream
{
public:
SoftwareSerial(int receivePin, int transmitPin, bool inverse_logic = false, unsigned int buffSize = 64);
~SoftwareSerial();
void begin(long speed);
long baudRate();
void setTransmitEnablePin(int transmitEnablePin);
bool overflow();
int peek();
virtual size_t write(uint8_t byte);
virtual int read();
virtual int available();
virtual void flush();
operator bool() {return m_rxValid || m_txValid;}
// Disable or enable interrupts on the rx pin
void enableRx(bool on);
void rxRead();
// AVR compatibility methods
bool listen() { enableRx(true); return true; }
void end() { stopListening(); }
bool isListening() { return m_rxEnabled; }
bool stopListening() { enableRx(false); return true; }
using Print::write;
private:
bool isValidGPIOpin(int pin);
// Member variables
int m_rxPin, m_txPin, m_txEnablePin;
bool m_rxValid, m_rxEnabled;
bool m_txValid, m_txEnableValid;
bool m_invert;
bool m_overflow;
unsigned long m_bitTime;
bool m_highSpeed;
unsigned int m_inPos, m_outPos;
int m_buffSize;
uint8_t *m_buffer;
};
// If only one tx or rx wanted then use this as parameter for the unused pin
#define SW_SERIAL_UNUSED_PIN -1
#endif

View File

@ -1,27 +0,0 @@
#include <SoftwareSerial.h>
SoftwareSerial swSer(14, 12, false, 256);
void setup() {
Serial.begin(115200);
swSer.begin(115200);
Serial.println("\nSoftware serial test started");
for (char ch = ' '; ch <= 'z'; ch++) {
swSer.write(ch);
}
swSer.println("");
}
void loop() {
while (swSer.available() > 0) {
Serial.write(swSer.read());
}
while (Serial.available() > 0) {
swSer.write(Serial.read());
}
}

View File

@ -1,31 +0,0 @@
#######################################
# Syntax Coloring Map for SoftwareSerial
# (esp8266)
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SoftwareSerial KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
read KEYWORD2
write KEYWORD2
available KEYWORD2
flush KEYWORD2
overflow KEYWORD2
peek KEYWORD2
listen KEYWORD2
end KEYWORD2
isListening KEYWORD2
stopListening KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -1,15 +0,0 @@
{
"name": "EspSoftwareSerial",
"version": "3.3.1",
"keywords": [
"serial", "io", "softwareserial"
],
"description": "Implementation of the Arduino software serial for ESP8266.",
"repository":
{
"type": "git",
"url": "https://github.com/plerup/espsoftwareserial"
},
"frameworks": "arduino",
"platforms": "espressif8266"
}

View File

@ -1,9 +0,0 @@
name=SoftwareSerial
version=1.0
author=Peter Lerup
maintainer=Peter Lerup <peter@lerup.com>
sentence=Implementation of the Arduino software serial for ESP8266.
paragraph=
category=Signal Input/Output
url=
architectures=esp8266

View File

@ -1,11 +0,0 @@
# EspSoftwareSerial
Implementation of the Arduino software serial library for the ESP8266
Same functionality as the corresponding AVR library but several instances can be active at the same time.
Speed up to 115200 baud is supported. The constructor also has an optional input buffer size.
Please note that due to the fact that the ESP always have other activities ongoing, there will be some inexactness in interrupt
timings. This may lead to bit errors when having heavy data traffic in high baud rates.

View File

@ -1,248 +0,0 @@
/*
SoftwareSerialNoIram.cpp - Implementation of the Arduino software serial for ESP8266.
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define TASMOTA_NO_ICACHE_RAM // To solve compile errors due to lack off iram
#include <Arduino.h>
// The Arduino standard GPIO routines are not enough,
// must use some from the Espressif SDK as well
extern "C" {
#include "gpio.h"
}
#include <SoftwareSerialNoIram.h>
#define MAX_PIN 15
// As the Arduino attachInterrupt has no parameter, lists of objects
// and callbacks corresponding to each possible GPIO pins have to be defined
SoftwareSerialNoIram *ObjList[MAX_PIN+1];
#ifdef TASMOTA_NO_ICACHE_RAM
void sws_isr_0() { ObjList[0]->rxRead(); };
void sws_isr_1() { ObjList[1]->rxRead(); };
void sws_isr_2() { ObjList[2]->rxRead(); };
void sws_isr_3() { ObjList[3]->rxRead(); };
void sws_isr_4() { ObjList[4]->rxRead(); };
void sws_isr_5() { ObjList[5]->rxRead(); };
// Pin 6 to 11 can not be used
void sws_isr_12() { ObjList[12]->rxRead(); };
void sws_isr_13() { ObjList[13]->rxRead(); };
void sws_isr_14() { ObjList[14]->rxRead(); };
void sws_isr_15() { ObjList[15]->rxRead(); };
#else
void ICACHE_RAM_ATTR sws_isr_0() { ObjList[0]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_1() { ObjList[1]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_2() { ObjList[2]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_3() { ObjList[3]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_4() { ObjList[4]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_5() { ObjList[5]->rxRead(); };
// Pin 6 to 11 can not be used
void ICACHE_RAM_ATTR sws_isr_12() { ObjList[12]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_13() { ObjList[13]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_14() { ObjList[14]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_15() { ObjList[15]->rxRead(); };
#endif
static void (*ISRList[MAX_PIN+1])() = {
sws_isr_0,
sws_isr_1,
sws_isr_2,
sws_isr_3,
sws_isr_4,
sws_isr_5,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
sws_isr_12,
sws_isr_13,
sws_isr_14,
sws_isr_15
};
SoftwareSerialNoIram::SoftwareSerialNoIram(int receivePin, int transmitPin, bool inverse_logic, unsigned int buffSize) {
m_rxValid = m_txValid = m_txEnableValid = false;
m_buffer = NULL;
m_invert = inverse_logic;
m_overflow = false;
m_rxEnabled = false;
if (isValidGPIOpin(receivePin)) {
m_rxPin = receivePin;
m_buffSize = buffSize;
m_buffer = (uint8_t*)malloc(m_buffSize);
if (m_buffer != NULL) {
m_rxValid = true;
m_inPos = m_outPos = 0;
pinMode(m_rxPin, INPUT);
ObjList[m_rxPin] = this;
enableRx(true);
}
}
if (isValidGPIOpin(transmitPin) || transmitPin == 16) {
m_txValid = true;
m_txPin = transmitPin;
pinMode(m_txPin, OUTPUT);
digitalWrite(m_txPin, !m_invert);
}
// Default speed
begin(9600);
}
SoftwareSerialNoIram::~SoftwareSerialNoIram() {
enableRx(false);
if (m_rxValid)
ObjList[m_rxPin] = NULL;
if (m_buffer)
free(m_buffer);
}
bool SoftwareSerialNoIram::isValidGPIOpin(int pin) {
return (pin >= 0 && pin <= 5) || (pin >= 12 && pin <= MAX_PIN);
}
void SoftwareSerialNoIram::begin(long speed) {
// Use getCycleCount() loop to get as exact timing as possible
m_bitTime = ESP.getCpuFreqMHz()*1000000/speed;
m_highSpeed = speed > 9600;
if (!m_rxEnabled)
enableRx(true);
}
long SoftwareSerialNoIram::baudRate() {
return ESP.getCpuFreqMHz()*1000000/m_bitTime;
}
void SoftwareSerialNoIram::setTransmitEnablePin(int transmitEnablePin) {
if (isValidGPIOpin(transmitEnablePin)) {
m_txEnableValid = true;
m_txEnablePin = transmitEnablePin;
pinMode(m_txEnablePin, OUTPUT);
digitalWrite(m_txEnablePin, LOW);
} else {
m_txEnableValid = false;
}
}
void SoftwareSerialNoIram::enableRx(bool on) {
if (m_rxValid) {
if (on)
attachInterrupt(m_rxPin, ISRList[m_rxPin], m_invert ? RISING : FALLING);
else
detachInterrupt(m_rxPin);
m_rxEnabled = on;
}
}
int SoftwareSerialNoIram::read() {
if (!m_rxValid || (m_inPos == m_outPos)) return -1;
uint8_t ch = m_buffer[m_outPos];
m_outPos = (m_outPos+1) % m_buffSize;
return ch;
}
int SoftwareSerialNoIram::available() {
if (!m_rxValid) return 0;
int avail = m_inPos - m_outPos;
if (avail < 0) avail += m_buffSize;
return avail;
}
#define WAIT { while (ESP.getCycleCount()-start < wait) if (!m_highSpeed) optimistic_yield(1); wait += m_bitTime; }
size_t SoftwareSerialNoIram::write(uint8_t b) {
if (!m_txValid) return 0;
if (m_invert) b = ~b;
if (m_highSpeed)
// Disable interrupts in order to get a clean transmit
cli();
if (m_txEnableValid) digitalWrite(m_txEnablePin, HIGH);
unsigned long wait = m_bitTime;
digitalWrite(m_txPin, HIGH);
unsigned long start = ESP.getCycleCount();
// Start bit;
digitalWrite(m_txPin, LOW);
WAIT;
for (int i = 0; i < 8; i++) {
digitalWrite(m_txPin, (b & 1) ? HIGH : LOW);
WAIT;
b >>= 1;
}
// Stop bit
digitalWrite(m_txPin, HIGH);
WAIT;
if (m_txEnableValid) digitalWrite(m_txEnablePin, LOW);
if (m_highSpeed)
sei();
return 1;
}
void SoftwareSerialNoIram::flush() {
m_inPos = m_outPos = 0;
}
bool SoftwareSerialNoIram::overflow() {
bool res = m_overflow;
m_overflow = false;
return res;
}
int SoftwareSerialNoIram::peek() {
if (!m_rxValid || (m_inPos == m_outPos)) return -1;
return m_buffer[m_outPos];
}
#ifdef TASMOTA_NO_ICACHE_RAM
void SoftwareSerialNoIram::rxRead() {
#else
void ICACHE_RAM_ATTR SoftwareSerialNoIram::rxRead() {
#endif
// Advance the starting point for the samples but compensate for the
// initial delay which occurs before the interrupt is delivered
unsigned long wait = m_bitTime + m_bitTime/3 - 500;
unsigned long start = ESP.getCycleCount();
uint8_t rec = 0;
for (int i = 0; i < 8; i++) {
WAIT;
rec >>= 1;
if (digitalRead(m_rxPin))
rec |= 0x80;
}
if (m_invert) rec = ~rec;
// Stop bit
WAIT;
// Store the received value in the buffer unless we have an overflow
int next = (m_inPos+1) % m_buffSize;
if (next != m_outPos) {
m_buffer[m_inPos] = rec;
m_inPos = next;
} else {
m_overflow = true;
}
// Must clear this bit in the interrupt register,
// it gets set even when interrupts are disabled
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << m_rxPin);
}

View File

@ -1,88 +0,0 @@
/*
SoftwareSerialNoIram.h
SoftwareSerialNoIram.cpp - Implementation of the Arduino software serial for ESP8266 without iram usage.
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef SoftwareSerialNoIram_h
#define SoftwareSerialNoIram_h
#include <inttypes.h>
#include <Stream.h>
// This class is compatible with the corresponding AVR one,
// the constructor however has an optional rx buffer size.
// Speed up to 115200 can be used.
class SoftwareSerialNoIram : public Stream
{
public:
SoftwareSerialNoIram(int receivePin, int transmitPin, bool inverse_logic = false, unsigned int buffSize = 64);
~SoftwareSerialNoIram();
void begin(long speed);
long baudRate();
void setTransmitEnablePin(int transmitEnablePin);
bool overflow();
int peek();
virtual size_t write(uint8_t byte);
virtual int read();
virtual int available();
virtual void flush();
operator bool() {return m_rxValid || m_txValid;}
// Disable or enable interrupts on the rx pin
void enableRx(bool on);
void rxRead();
// AVR compatibility methods
bool listen() { enableRx(true); return true; }
void end() { stopListening(); }
bool isListening() { return m_rxEnabled; }
bool stopListening() { enableRx(false); return true; }
using Print::write;
private:
bool isValidGPIOpin(int pin);
// Member variables
int m_rxPin, m_txPin, m_txEnablePin;
bool m_rxValid, m_rxEnabled;
bool m_txValid, m_txEnableValid;
bool m_invert;
bool m_overflow;
unsigned long m_bitTime;
bool m_highSpeed;
unsigned int m_inPos, m_outPos;
int m_buffSize;
uint8_t *m_buffer;
};
// If only one tx or rx wanted then use this as parameter for the unused pin
#define SW_SERIAL_UNUSED_PIN -1
#endif

View File

@ -1,27 +0,0 @@
#include <SoftwareSerial.h>
SoftwareSerial swSer(14, 12, false, 256);
void setup() {
Serial.begin(115200);
swSer.begin(115200);
Serial.println("\nSoftware serial test started");
for (char ch = ' '; ch <= 'z'; ch++) {
swSer.write(ch);
}
swSer.println("");
}
void loop() {
while (swSer.available() > 0) {
Serial.write(swSer.read());
}
while (Serial.available() > 0) {
swSer.write(Serial.read());
}
}

View File

@ -1,31 +0,0 @@
#######################################
# Syntax Coloring Map for SoftwareSerialNoIram
# (esp8266)
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SoftwareSerialNoIram KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
read KEYWORD2
write KEYWORD2
available KEYWORD2
flush KEYWORD2
overflow KEYWORD2
peek KEYWORD2
listen KEYWORD2
end KEYWORD2
isListening KEYWORD2
stopListening KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -1,15 +0,0 @@
{
"name": "EspSoftwareSerialNoIram",
"version": "3.3.1",
"keywords": [
"serial", "io", "softwareserialnoiram"
],
"description": "Implementation of the Arduino software serial for ESP8266 without iram usage.",
"repository":
{
"type": "git",
"url": "https://github.com/plerup/espsoftwareserial"
},
"frameworks": "arduino",
"platforms": "espressif8266"
}

View File

@ -1,9 +0,0 @@
name=SoftwareSerialNoIram
version=1.0
author=Peter Lerup
maintainer=Peter Lerup <peter@lerup.com>
sentence=Implementation of the Arduino software serial for ESP8266 without iram usage.
paragraph=
category=Signal Input/Output
url=
architectures=esp8266

View File

@ -1,7 +1,8 @@
/* 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 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 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)

View File

@ -167,7 +167,7 @@
//#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 (+1k5 code)
#define USE_I2C // I2C using library wire (+10k code, 0k2 mem) - Disable by //
#define USE_I2C // I2C using library wire (+10k code, 0k2 mem, 124 iram)
#define USE_SHT // Add I2C emulating code for SHT1X sensor (+1k4 code)
// #define USE_SHT3X // Add I2C code for SHT3x sensor based on Adafruit (+0k7 code)
// #define USE_SHT3X_V2 // Add I2C code for SHT3x sensor based on EspEasy (+0k7 code)
@ -181,17 +181,17 @@
// #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_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0k3 mem)
#define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0k3 mem, 48 iram)
// #define USE_IR_HVAC // Support for HVAC system using IR (+2k code)
#define USE_IR_RECEIVE // Support for IR receiver (+5k5 code)
#define USE_IR_RECEIVE // Support for IR receiver (+5k5 code, 264 iram)
#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, 232 iram) - Disable by //
#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 // 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_SERIAL_NO_ICACHE // Use no iram with SoftwareSerial (may loose characters)
//#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_ARILUX_RF // Add support for Arilux RF remote controller (+0k8 code)

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef USE_MHZ19_HARD_SERIAL
/*********************************************************************************************\
* 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<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)
{
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} = <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_HARD_SERIAL

View File

@ -28,14 +28,6 @@
* Select filter usage on low stability readings
\*********************************************************************************************/
#ifdef USE_SERIAL_NO_ICACHE
#include <SoftwareSerialNoIram.h>
SoftwareSerialNoIram *SoftSerial;
#else
#include <SoftwareSerial.h>
SoftwareSerial *SoftSerial;
#endif // USE_SERIAL_NO_ICACHE
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
@ -65,18 +57,136 @@ enum Mhz19FilterOptions {MHZ19_FILTER_OFF, MHZ19_FILTER_OFF_ALLSAMPLES, MHZ19_FI
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};
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;
uint16_t mhz19_last_ppm = 0;
uint8_t mhz19_filter = MHZ19_FILTER_OPTION;
byte mhz19_response[9];
uint8_t mhz19_response[9];
bool mhz19_abc_enable = MHZ19_ABC_ENABLE;
bool mhz19_abc_must_apply = false;
char mhz19_types[7];
/*********************************************************************************************\
* Subset SoftwareSerial
\*********************************************************************************************/
#define MHZ19_SERIAL_BUFFER_SIZE 20
#define MHZ19_SERIAL_WAIT { while (ESP.getCycleCount() -start < wait) optimistic_yield(1); wait += mhz19_serial_bit_time; }
uint8_t mhz19_serial_rx_pin;
uint8_t mhz19_serial_tx_pin;
uint8_t mhz19_serial_in_pos = 0;
uint8_t mhz19_serial_out_pos = 0;
uint8_t mhz19_serial_buffer[MHZ19_SERIAL_BUFFER_SIZE];
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);
}
bool Mhz19Serial(uint8_t receive_pin, uint8_t transmit_pin)
{
if (!((Mhz19SerialValidGpioPin(receive_pin)) && (Mhz19SerialValidGpioPin(transmit_pin) || transmit_pin == 16))) {
return false;
}
mhz19_serial_rx_pin = receive_pin;
pinMode(mhz19_serial_rx_pin, INPUT);
attachInterrupt(mhz19_serial_rx_pin, Mhz19SerialRxRead, FALLING);
mhz19_serial_tx_pin = transmit_pin;
pinMode(mhz19_serial_tx_pin, OUTPUT);
digitalWrite(mhz19_serial_tx_pin, 1);
mhz19_serial_bit_time = ESP.getCpuFreqMHz() *1000000 /MHZ19_BAUDRATE; // 8333
mhz19_serial_bit_time_start = mhz19_serial_bit_time + mhz19_serial_bit_time /3 -500; // 10610 ICACHE_RAM_ATTR start delay
// mhz19_serial_bit_time_start = mhz19_serial_bit_time; // Non ICACHE_RAM_ATTR start delay (experimental)
return true;
}
int Mhz19SerialRead() {
if (mhz19_serial_in_pos == mhz19_serial_out_pos) {
return -1;
}
int ch = mhz19_serial_buffer[mhz19_serial_out_pos];
mhz19_serial_out_pos = (mhz19_serial_out_pos +1) % MHZ19_SERIAL_BUFFER_SIZE;
return ch;
}
int Mhz19SerialAvailable() {
int avail = mhz19_serial_in_pos - mhz19_serial_out_pos;
if (avail < 0) {
avail += MHZ19_SERIAL_BUFFER_SIZE;
}
return avail;
}
void Mhz19SerialFlush()
{
mhz19_serial_in_pos = 0;
mhz19_serial_out_pos = 0;
}
size_t Mhz19SerialTxWrite(uint8_t b)
{
unsigned long wait = mhz19_serial_bit_time;
digitalWrite(mhz19_serial_tx_pin, HIGH);
unsigned long start = ESP.getCycleCount();
// Start bit;
digitalWrite(mhz19_serial_tx_pin, LOW);
MHZ19_SERIAL_WAIT;
for (int i = 0; i < 8; i++) {
digitalWrite(mhz19_serial_tx_pin, (b & 1) ? HIGH : LOW);
MHZ19_SERIAL_WAIT;
b >>= 1;
}
// Stop bit
digitalWrite(mhz19_serial_tx_pin, HIGH);
MHZ19_SERIAL_WAIT;
return 1;
}
size_t Mhz19SerialWrite(const uint8_t *buffer, size_t size = 1) {
size_t n = 0;
while(size--) {
n += Mhz19SerialTxWrite(*buffer++);
}
return n;
}
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
unsigned long wait = mhz19_serial_bit_time_start;
unsigned long start = ESP.getCycleCount();
uint8_t rec = 0;
for (int i = 0; i < 8; i++) {
MHZ19_SERIAL_WAIT;
rec >>= 1;
if (digitalRead(mhz19_serial_rx_pin)) {
rec |= 0x80;
}
}
// Stop bit
MHZ19_SERIAL_WAIT;
// Store the received value in the buffer unless we have an overflow
int next = (mhz19_serial_in_pos +1) % MHZ19_SERIAL_BUFFER_SIZE;
if (next != mhz19_serial_out_pos) {
mhz19_serial_buffer[mhz19_serial_in_pos] = rec;
mhz19_serial_in_pos = next;
}
// Must clear this bit in the interrupt register,
// it gets set even when interrupts are disabled
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << mhz19_serial_rx_pin);
}
/*********************************************************************************************/
bool Mhz19CheckAndApplyFilter(uint16_t ppm, uint8_t s)
{
if (1 == s) {
@ -98,28 +208,15 @@ bool Mhz19CheckAndApplyFilter(uint16_t ppm, uint8_t s)
// 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 *= s;
difference /= 64;
}
switch (mhz19_filter) {
case MHZ19_FILTER_OFF: {
if (MHZ19_FILTER_OFF == mhz19_filter) {
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
} else {
difference >>= (mhz19_filter -1);
}
mhz19_last_ppm = static_cast<uint16_t>(mhz19_last_ppm + difference);
return true;
@ -134,16 +231,16 @@ bool Mhz19Read(uint16_t &p, float &t)
if (mhz19_type)
{
SoftSerial->flush();
if (SoftSerial->write(mhz19_cmnd_read_ppm, 9) != 9) {
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();
uint8_t counter = 0;
while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) {
if (SoftSerial->available() > 0) {
mhz19_response[counter++] = SoftSerial->read();
if (Mhz19SerialAvailable() > 0) {
mhz19_response[counter++] = Mhz19SerialRead();
} else {
delay(10);
}
@ -194,9 +291,9 @@ bool Mhz19Read(uint16_t &p, float &t)
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
Mhz19SerialWrite(mhz19_cmnd_abc_enable, 9); // Sent sensor ABC Enable
} else {
SoftSerial->write(mhz19_cmnd_abc_disable, 9); // Sent sensor ABC Disable
Mhz19SerialWrite(mhz19_cmnd_abc_disable, 9); // Sent sensor ABC Disable
}
}
}
@ -211,14 +308,9 @@ bool Mhz19Read(uint16_t &p, float &t)
void Mhz19Init()
{
#ifdef USE_SERIAL_NO_ICACHE
SoftSerial = new SoftwareSerialNoIram(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD]);
#else
SoftSerial = new SoftwareSerial(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD]);
#endif // USE_SERIAL_NO_ICACHE
SoftSerial->begin(9600);
if (Mhz19Serial(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD])) {
mhz19_type = 1;
}
}
#ifdef USE_WEBSERVER

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef USE_MHZ19_SOFT_SERIAL_OBSOLETE
/*********************************************************************************************\
* MH-Z19 - CO2 sensor
*
* Based on EspEasy plugin P049 by Dmitry (rel22 ___ inbox.ru)
**********************************************************************************************
* Filter usage
*
* Select filter usage on low stability readings
\*********************************************************************************************/
#include <SoftwareSerial.h>
SoftwareSerial *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 SoftwareSerial(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_OBSOLETE