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