Add support for multiple CCS811 sensors

Add support for multiple CCS811 sensors with baseline control (USE_CCS811_V2) by clanganke (#10858)
This commit is contained in:
Theo Arends 2021-03-31 15:04:24 +02:00
parent c096a032b7
commit 0331e47e4c
7 changed files with 347 additions and 1 deletions

View File

@ -111,6 +111,7 @@
| USE_PCA9685 | - | - | - | - | - | - | - | | USE_PCA9685 | - | - | - | - | - | - | - |
| USE_MPR121 | - | - | - | - | - | - | - | | USE_MPR121 | - | - | - | - | - | - | - |
| USE_CCS811 | - | - | - | - | x | - | - | | USE_CCS811 | - | - | - | - | x | - | - |
| USE_CCS811_V2 | - | - | - | - | - | - | - |
| USE_MPU6050 | - | - | - | - | - | - | - | | USE_MPU6050 | - | - | - | - | - | - | - |
| USE_DS3231 | - | - | - | - | - | - | - | | USE_DS3231 | - | - | - | - | - | - | - |
| USE_MGC3130 | - | - | - | - | - | - | - | | USE_MGC3130 | - | - | - | - | - | - | - |

View File

@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
- Support for MAX7219 seven segment display by Ajith Vasudevan (#11387) - Support for MAX7219 seven segment display by Ajith Vasudevan (#11387)
- Support for Frequency monitoring and zero-cross detection on CSE7761 (Sonoff Dual R3) - Support for Frequency monitoring and zero-cross detection on CSE7761 (Sonoff Dual R3)
- ESP32 support for internal Hall Effect sensor connected to both GPIO36 and GPIO39 only - ESP32 support for internal Hall Effect sensor connected to both GPIO36 and GPIO39 only
- Support for multiple CCS811 sensors with baseline control (USE_CCS811_V2) by clanganke (#10858)
### Changed ### Changed
- PubSubClient library from EspEasy v2.7.12 to Tasmota v2.8.12 - PubSubClient library from EspEasy v2.7.12 to Tasmota v2.8.12

View File

@ -92,6 +92,7 @@ The attached binaries can also be downloaded from http://ota.tasmota.com/tasmota
- Support for TM1638 seven segment display by Ajith Vasudevan [#11031](https://github.com/arendst/Tasmota/issues/11031) - Support for TM1638 seven segment display by Ajith Vasudevan [#11031](https://github.com/arendst/Tasmota/issues/11031)
- Support for MAX7219 seven segment display by Ajith Vasudevan [#11387](https://github.com/arendst/Tasmota/issues/11387) - Support for MAX7219 seven segment display by Ajith Vasudevan [#11387](https://github.com/arendst/Tasmota/issues/11387)
- Support for MPU6886 on primary or secondary I2C bus - Support for MPU6886 on primary or secondary I2C bus
- Support for multiple CCS811 sensors with baseline control (USE_CCS811_V2) by clanganke [#10858](https://github.com/arendst/Tasmota/issues/10858)
- Allow MCP230xx pinmode from output to input [#11104](https://github.com/arendst/Tasmota/issues/11104) - Allow MCP230xx pinmode from output to input [#11104](https://github.com/arendst/Tasmota/issues/11104)
- Berry improvements [#11163](https://github.com/arendst/Tasmota/issues/11163) - Berry improvements [#11163](https://github.com/arendst/Tasmota/issues/11163)
- Extent compile time SetOptions support [#11204](https://github.com/arendst/Tasmota/issues/11204) - Extent compile time SetOptions support [#11204](https://github.com/arendst/Tasmota/issues/11204)

View File

@ -568,6 +568,7 @@
// #define USE_PCA9685_FREQ 50 // Define default PWM frequency in Hz to be used (must be within 24 to 1526) - If other value is used, it will rever to 50Hz // #define USE_PCA9685_FREQ 50 // Define default PWM frequency in Hz to be used (must be within 24 to 1526) - If other value is used, it will rever to 50Hz
// #define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code) // #define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code)
// #define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code) // #define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code)
// #define USE_CCS811_V2 // [I2cDriver24] Enable CCS811 sensor (I2C addresses 0x5A and 0x5B) (+2k8 code)
// #define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM) // #define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM)
// #define USE_MPU6050_DMP // Enable in MPU6050 to use the DMP on the chip, should create better results (+8k6 of code) // #define USE_MPU6050_DMP // Enable in MPU6050 to use the DMP on the chip, should create better results (+8k6 of code)
// #define USE_DS3231 // [I2cDriver26] Enable DS3231 external RTC in case no Wifi is avaliable. See docs in the source file (+1k2 code) // #define USE_DS3231 // [I2cDriver26] Enable DS3231 external RTC in case no Wifi is avaliable. See docs in the source file (+1k2 code)

View File

@ -101,6 +101,7 @@
//#define USE_PCA9685 // [I2cDriver1] Enable PCA9685 I2C HW PWM Driver - Must define I2C Address in #define USE_PCA9685_ADDR below - range 0x40 - 0x47 (+1k4 code) //#define USE_PCA9685 // [I2cDriver1] Enable PCA9685 I2C HW PWM Driver - Must define I2C Address in #define USE_PCA9685_ADDR below - range 0x40 - 0x47 (+1k4 code)
//#define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code) //#define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code)
#define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code) #define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code)
//#define USE_CCS811_V2 // [I2cDriver24] Enable CCS811 sensor (I2C addresses 0x5A and 0x5B) (+2k8 code)
//#define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM) //#define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM)
//#define USE_DS3231 // [I2cDriver26] Enable DS3231 external RTC in case no Wifi is avaliable. See docs in the source file (+1k2 code) //#define USE_DS3231 // [I2cDriver26] Enable DS3231 external RTC in case no Wifi is avaliable. See docs in the source file (+1k2 code)
//#define USE_MGC3130 // [I2cDriver27] Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem) //#define USE_MGC3130 // [I2cDriver27] Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem)

View File

@ -213,7 +213,8 @@
//#define USE_MCP230xx // [I2cDriver22] Enable MCP23008/MCP23017 - Must define I2C Address in #define USE_MCP230xx_ADDR below - range 0x20 - 0x27 (+4k7 code) //#define USE_MCP230xx // [I2cDriver22] Enable MCP23008/MCP23017 - Must define I2C Address in #define USE_MCP230xx_ADDR below - range 0x20 - 0x27 (+4k7 code)
//#define USE_PCA9685 // [I2cDriver1] Enable PCA9685 I2C HW PWM Driver - Must define I2C Address in #define USE_PCA9685_ADDR below - range 0x40 - 0x47 (+1k4 code) //#define USE_PCA9685 // [I2cDriver1] Enable PCA9685 I2C HW PWM Driver - Must define I2C Address in #define USE_PCA9685_ADDR below - range 0x40 - 0x47 (+1k4 code)
//#define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code) //#define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code)
#define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code) //#define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code)
#define USE_CCS811_V2 // [I2cDriver24] Enable CCS811 sensor (I2C addresses 0x5A and 0x5B) (+2k8 code)
#define USE_MPU6886 // [I2cDriver??] Enable MPU6886 6-axis MotionTracking sensor (I2C address 0x68) #define USE_MPU6886 // [I2cDriver??] Enable MPU6886 6-axis MotionTracking sensor (I2C address 0x68)
//#define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM) //#define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM)
//#define USE_DS3231 // [I2cDriver26] Enable DS3231 external RTC in case no Wifi is avaliable. See docs in the source file (+1k2 code) //#define USE_DS3231 // [I2cDriver26] Enable DS3231 external RTC in case no Wifi is avaliable. See docs in the source file (+1k2 code)

View File

@ -0,0 +1,340 @@
/*
xsns_31_ccs811.ino - CCS811 gas and air quality sensor support for Tasmota
Copyright (C) 2021 Gerhard Mutz and 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_I2C
#ifdef USE_CCS811_V2
/*********************************************************************************************\
* CCS811 - Gas (TVOC - Total Volatile Organic Compounds) and Air Quality (CO2)
*
* Source: Adafruit
*
* This driver supports one to two devices at a time at
* addressses 0x5A or/and 0x5B
* - for I2C address 0x5A, connect ADDR to GND
* - for I2C address 0x5B, connect ADDR to VCC
* NOTE:
* - Wake must be connected to GND (no sleep mode supported!)
* - depending on the breakout board, SDA & SCL may require
* pull-ups to VCC, e.g. 4k7R
*
\*********************************************************************************************/
#define XSNS_31 31
#define XI2C_24 24 // See I2CDEVICES.md
#define EVERYNSECONDS 5
#define RESETCOUNT 6
#include "Adafruit_CCS811.h"
uint8_t CCS811_addresses[] = { CCS811_ADDRESS, (CCS811_ADDRESS + 1) };
#define MAXDEVICECOUNT (sizeof( CCS811_addresses) / sizeof(uint8_t))
typedef struct {
uint8_t address;
uint8_t device_found;
uint8_t device_index;
uint8_t device_ready;
Adafruit_CCS811 ccsinstance;
uint16_t eCO2;
uint16_t TVOC;
uint8_t refresh_count;
uint8_t reset_count;
} CCS811DATA;
uint8_t CCS811_devices_found = 0;
CCS811DATA ccsd[ MAXDEVICECOUNT];
CCS811DATA * pccsd;
uint32_t i;
#define D_PRFX_CCS811 "CCS811"
#define D_CMND_HWVERSION "HW"
#define D_CMND_FWAPPVERSION "FWApp"
#define D_CMND_BASELINE "Baseline"
const char kCCS811Commands[] PROGMEM = D_PRFX_CCS811 "|" // Prefix
D_CMND_HWVERSION "|"
D_CMND_FWAPPVERSION "|"
D_CMND_BASELINE;
void (* const CCS811Command[])(void) PROGMEM = {
&CmndCCS811HwVersion,
&CmndCCS811FwAppVersion,
&CmndCCS811Baseline
};
/********************************************************************************************/
void CCS811Detect(void)
{
if (!CCS811_devices_found) {
memset( ccsd, 0, sizeof( ccsd));
}
int active_index = 1;
for (i = 0, pccsd = ccsd; i < MAXDEVICECOUNT; i++, pccsd++) {
pccsd->address = CCS811_addresses[ i];
if (I2cActive( pccsd->address)) { continue; }
if (!pccsd->ccsinstance.begin(pccsd->address)) {
pccsd->device_found = 1;
CCS811_devices_found += 1;
I2cSetActiveFound( pccsd->address, "CCS811");
pccsd->device_index = active_index;
active_index++;
}
}
}
void CCS811Update(void) // Perform every n second
{
for (i = 0, pccsd = ccsd; i < MAXDEVICECOUNT; i++, pccsd++) {
if (!pccsd->device_found)
continue;
pccsd->refresh_count++;
if (pccsd->refresh_count >= EVERYNSECONDS) {
pccsd->refresh_count = 0;
pccsd->device_ready = 0;
if (pccsd->ccsinstance.available()) {
if (!pccsd->ccsinstance.readData()){
pccsd->TVOC = pccsd->ccsinstance.getTVOC();
pccsd->eCO2 = pccsd->ccsinstance.geteCO2();
pccsd->device_ready = 1;
if ((TasmotaGlobal.global_update) &&
(TasmotaGlobal.humidity > 0) &&
(!isnan(TasmotaGlobal.temperature_celsius))) {
pccsd->ccsinstance.setEnvironmentalData((uint8_t)TasmotaGlobal.humidity,
TasmotaGlobal.temperature_celsius);
}
pccsd->reset_count = 0;
}
} else {
// failed, count up
pccsd->reset_count++;
if (pccsd->reset_count > RESETCOUNT) {
// after 30 seconds, restart
pccsd->ccsinstance.begin( pccsd->address);
pccsd->reset_count = 0;
}
}
}
}
}
// no methods available in Adafruit library to read version data or
// read/set the baseline value, so we need to emulate the private methods
void CCS811ReadMailboxValue( uint8_t address, uint8_t mailbox, byte * pbuf, uint8_t buflen)
{
Wire.beginTransmission(address);
Wire.write(mailbox);
Wire.endTransmission();
Wire.requestFrom(address, buflen);
for (uint8_t i = 0; i < buflen; i++) {
*(pbuf + i) = Wire.read();
#ifdef CCS811_DEBUG
AddLog_P(LOG_LEVEL_DEBUG, PSTR( D_LOG_DEBUG D_PRFX_CCS811 " reading byte %u: 0%02x / %u"), i, *(pbuf + i), *(pbuf + i));
#endif
}
}
void CCS811WriteMailboxValue(uint8_t address, uint8_t mailbox, byte * pbuf, uint8_t buflen)
{
#ifdef CCS811_DEBUG
for (uint8_t i = 0; i < buflen; i++) {
AddLog_P(LOG_LEVEL_DEBUG, PSTR( D_LOG_DEBUG D_PRFX_CCS811 " writing byte %u: 0%02x / %u"), i, *(pbuf + i), *(pbuf + i));
}
#endif
Wire.beginTransmission(address);
Wire.write((uint8_t)mailbox);
Wire.write(pbuf, buflen);
Wire.endTransmission();
}
/*********************************************************************************************\
* Command Sensor31
\*********************************************************************************************/
CCS811DATA * CmndCCS811SelectDeviceFromIndex(void) {
CCS811DATA * pccsd_command = NULL;
if (XdrvMailbox.index <= CCS811_devices_found) {
// select device data matching the index
for (i = 0, pccsd = ccsd; i < MAXDEVICECOUNT; i++, pccsd++) {
if (pccsd->device_index == XdrvMailbox.index) {
pccsd_command = pccsd;
#ifdef CCS811_DEBUG
AddLog_P(LOG_LEVEL_DEBUG, PSTR( D_LOG_DEBUG D_PRFX_CCS811 " I2C Address: 0%02x"), pccsd_command->address);
#endif
break;
}
}
}
return pccsd_command;
}
void CmndCCS811HwVersion(void) {
CCS811DATA * pccsd = CmndCCS811SelectDeviceFromIndex();
if (pccsd) {
byte CCS811_hw_version;
CCS811ReadMailboxValue( pccsd->address,
CCS811_HW_VERSION,
&CCS811_hw_version,
sizeof(CCS811_hw_version));
ResponseCmndIdxNumber(CCS811_hw_version);
}
}
void CmndCCS811FwAppVersion(void) {
pccsd = CmndCCS811SelectDeviceFromIndex();
if (pccsd) {
byte bCCS811_fw_app_version[2];
char CCS811_fw_app_version[16];
CCS811ReadMailboxValue( pccsd->address,
CCS811_FW_APP_VERSION,
bCCS811_fw_app_version,
(sizeof(bCCS811_fw_app_version) / sizeof(byte)));
sprintf( CCS811_fw_app_version,
PSTR( "%x.%x.%x"),
(bCCS811_fw_app_version[0] >> 4), // major
(bCCS811_fw_app_version[0] & 0xF), // minor
bCCS811_fw_app_version[1]); // build
ResponseCmndIdxChar(CCS811_fw_app_version);
}
}
void CmndCCS811Baseline(void) {
pccsd = CmndCCS811SelectDeviceFromIndex();
if (pccsd) {
byte CCS811_baseline[2];
if (XdrvMailbox.data_len > 0) {
CCS811_baseline[0] = (XdrvMailbox.payload & 0xFF00) >> 8;
CCS811_baseline[1] = XdrvMailbox.payload & 0xFF;
CCS811WriteMailboxValue( pccsd->address,
CCS811_BASELINE,
CCS811_baseline,
(sizeof(CCS811_baseline) / sizeof(byte)));
} else {
CCS811ReadMailboxValue( pccsd->address,
CCS811_BASELINE,
CCS811_baseline,
(sizeof(CCS811_baseline) / sizeof(byte)));
}
ResponseCmndIdxNumber(((CCS811_baseline[0] << 8) + CCS811_baseline[1]));
}
}
// -----------------------------------------------------------------------------
const char HTTP_SNS_CCS811[] PROGMEM =
"{s}%s " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
"{s}%s " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}";
const char * devicenamelist[] PROGMEM = { "CCS811", "CCS811_1", "CCS811_2" };
void CCS811Show(bool json)
{
uint8_t ready_count = 0;
for (i = 0, pccsd = ccsd; i < MAXDEVICECOUNT; i++, pccsd++) {
if ((pccsd->device_found) && (pccsd->device_ready)) {
ready_count += 1;
}
}
if (!ready_count) {
return;
}
// in upcoming loops use either one device name
// with no index or or two names with index
const char ** pdevicename;
const char ** pdevicename_first = devicenamelist;
if (ready_count > 1) {
pdevicename_first++;
}
if (json) {
for (i = 0, pccsd = ccsd, pdevicename = pdevicename_first; i < MAXDEVICECOUNT; i++, pccsd++) {
if (pccsd->device_ready) {
ResponseAppend_P( PSTR(",\"%s\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d}"),
*pdevicename,
pccsd->eCO2,
pccsd->TVOC);
pdevicename++;
}
}
#ifdef USE_DOMOTICZ
if (0 == TasmotaGlobal.tele_period) {
if (pccsd->device_ready) {
pccsd = ccsd;
DomoticzSensor(DZ_AIRQUALITY, pccsd->eCO2);
}
}
#endif // USE_DOMOTICZ
#ifdef USE_WEBSERVER
} else {
for (i = 0, pccsd = ccsd, pdevicename = pdevicename_first; i < MAXDEVICECOUNT; i++, pccsd++) {
if (pccsd->device_ready) {
WSContentSend_PD( HTTP_SNS_CCS811,
*pdevicename, pccsd->eCO2,
*pdevicename, pccsd->TVOC);
pdevicename++;
}
}
#endif
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns31(uint8_t function)
{
if (!I2cEnabled(XI2C_24)) { return false; }
bool result = false;
if (FUNC_INIT == function) {
CCS811Detect();
}
else if (CCS811_devices_found) {
switch (function) {
case FUNC_EVERY_SECOND:
CCS811Update();
break;
case FUNC_COMMAND:
result = DecodeCommand( kCCS811Commands, CCS811Command);
break;
case FUNC_JSON_APPEND:
CCS811Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
CCS811Show(0);
break;
#endif // USE_WEBSERVER
}
}
return result;
}
#endif // USE_CCS811_V2
#endif // USE_I2C