Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS811 baseline support #10858

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 245 additions & 41 deletions tasmota/xsns_31_ccs811.ino
Original file line number Diff line number Diff line change
Expand Up @@ -24,80 +24,281 @@
*
* Source: Adafruit
*
* I2C Address: 0x5A assumes ADDR connected to Gnd, Wake also must be grounded
* 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"

Adafruit_CCS811 ccs;
uint8_t CCS811_ready = 0;
uint8_t CCS811_type = 0;;
uint16_t eCO2;
uint16_t TVOC;
uint8_t tcnt = 0;
uint8_t ecnt = 0;
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 (I2cActive(CCS811_ADDRESS)) { return; }

if (!ccs.begin(CCS811_ADDRESS)) {
CCS811_type = 1;
I2cSetActiveFound(CCS811_ADDRESS, "CCS811");
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
{
tcnt++;
if (tcnt >= EVERYNSECONDS) {
tcnt = 0;
CCS811_ready = 0;
if (ccs.available()) {
if (!ccs.readData()){
TVOC = ccs.getTVOC();
eCO2 = ccs.geteCO2();
CCS811_ready = 1;
if (TasmotaGlobal.global_update && (TasmotaGlobal.humidity > 0) && !isnan(TasmotaGlobal.temperature_celsius)) {
ccs.setEnvironmentalData((uint8_t)TasmotaGlobal.humidity, TasmotaGlobal.temperature_celsius);
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;
}
ecnt = 0;
}
} else {
// failed, count up
ecnt++;
if (ecnt > 6) {
// after 30 seconds, restart
ccs.begin(CCS811_ADDRESS);
}
}
}


// 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}CCS811 " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
"{s}CCS811 " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}";
"{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)
{
if (CCS811_ready) {
if (json) {
ResponseAppend_P(PSTR(",\"CCS811\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d}"), eCO2,TVOC);
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) DomoticzSensor(DZ_AIRQUALITY, eCO2);
if (0 == TasmotaGlobal.tele_period) {
if (pccsd->device_ready) {
pccsd = ccsd;
DomoticzSensor(DZ_AIRQUALITY, pccsd->eCO2);
}
}
#endif // USE_DOMOTICZ
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_CCS811, eCO2, TVOC);
#endif
} 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
}
}

Expand All @@ -114,11 +315,14 @@ bool Xsns31(uint8_t function)
if (FUNC_INIT == function) {
CCS811Detect();
}
else if (CCS811_type) {
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;
Expand Down