diff --git a/BUILDS.md b/BUILDS.md
index 019ac4045..32aa8fb38 100644
--- a/BUILDS.md
+++ b/BUILDS.md
@@ -111,6 +111,7 @@
| USE_PCA9685 | - | - | - | - | - | - | - |
| USE_MPR121 | - | - | - | - | - | - | - |
| USE_CCS811 | - | - | - | - | x | - | - |
+| USE_CCS811_V2 | - | - | - | - | - | - | - |
| USE_MPU6050 | - | - | - | - | - | - | - |
| USE_DS3231 | - | - | - | - | - | - | - |
| USE_MGC3130 | - | - | - | - | - | - | - |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8a0f20cf..894211614 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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 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
+- Support for multiple CCS811 sensors with baseline control (USE_CCS811_V2) by clanganke (#10858)
### Changed
- PubSubClient library from EspEasy v2.7.12 to Tasmota v2.8.12
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 80ae43341..3a5ce4c30 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -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 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 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)
- Berry improvements [#11163](https://github.com/arendst/Tasmota/issues/11163)
- Extent compile time SetOptions support [#11204](https://github.com/arendst/Tasmota/issues/11204)
diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h
index 60da2294c..db7b87565 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -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_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_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_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)
diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h
index 59257154a..6b05ca191 100644
--- a/tasmota/tasmota_configurations.h
+++ b/tasmota/tasmota_configurations.h
@@ -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_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_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_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)
diff --git a/tasmota/tasmota_configurations_ESP32.h b/tasmota/tasmota_configurations_ESP32.h
index b4ccdde12..8d7295ce4 100644
--- a/tasmota/tasmota_configurations_ESP32.h
+++ b/tasmota/tasmota_configurations_ESP32.h
@@ -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_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_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_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)
diff --git a/tasmota/xsns_31_ccs811_v2.ino b/tasmota/xsns_31_ccs811_v2.ino
new file mode 100644
index 000000000..a6e54dabb
--- /dev/null
+++ b/tasmota/xsns_31_ccs811_v2.ino
@@ -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 .
+*/
+
+#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} =
, {m} = | , {e} = |
+ "{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
\ No newline at end of file