Merge pull request #12618 from dizel-by/t67xx

Telaire T67XX CO2 sensor support
This commit is contained in:
Theo Arends 2021-07-13 11:20:07 +02:00 committed by GitHub
commit f7439ad1c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 355 additions and 1 deletions

View File

@ -93,4 +93,5 @@ Index | Define | Driver | Device | Address(es) | Description
57 | USE_TOF10120 | xsns_84 | TOF10120 | 0x52 | Time-of-flight (ToF) distance sensor
58 | USE_MPU_ACCEL | xsns_85 | MPU_ACCEL| 0x68 | MPU6886/MPU9250 6-axis MotionTracking sensor from M5Stack
59 | USE_BM8563 | xdrv_56 | BM8563 | 0x51 | BM8563 RTC from M5Stack
60 | USE_AM2320 | xsns_88 | AM2320 | 0x5C | Temperature and Humidity sensor
60 | USE_AM2320 | xsns_88 | AM2320 | 0x5C | Temperature and Humidity sensor
61 | USE_T67XX | xsns_89 | T67XX | 0x15 | CO2 sensor

View File

@ -575,6 +575,7 @@
#define USE_APDS9960_PROXIMITY // Enable APDS9960 Proximity feature (>50 code)
#define USE_APDS9960_COLOR // Enable APDS9960 Color feature (+0.8k code)
#define USE_APDS9960_STARTMODE 0 // Default to enable Gesture mode
// #define USE_T67XX // Enable Telaire T67XX CO2 sensor (+1.3k code)
// #define USE_MCP230xx // [I2cDriver22] Enable MCP23008/MCP23017 - Must define I2C Address in #define USE_MCP230xx_ADDR below - range 0x20 - 0x27 (+5k1 code)
// #define USE_MCP230xx_ADDR 0x20 // Enable MCP23008/MCP23017 I2C Address to use (Must be within range 0x20 through 0x26 - set according to your wired setup)
// #define USE_MCP230xx_OUTPUT // Enable MCP23008/MCP23017 OUTPUT support through sensor29 commands (+2k2 code)

View File

@ -148,6 +148,7 @@
//#define USE_RC522 // Add support for MFRC522 13.56Mhz Rfid reader (+6k code)
#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
#define USE_T67XX // Add support for T67XX CO2 sensor (+1.3k code)
#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)
#ifndef CO2_LOW
#define CO2_LOW 800 // Below this CO2 value show green light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1)

View File

@ -303,6 +303,7 @@
//#define USE_EZOPMP // [I2cDriver55] Enable support for EZO's PMP sensor (+0k3 code) - Shared EZO code required for any EZO device (+1k2 code)
//#define USE_SEESAW_SOIL // [I2cDriver56] Enable Capacitice Soil Moisture & Temperature Sensor (I2C addresses 0x36 - 0x39) (+1k3 code)
//#define USE_T67XX // Add support for T67XX CO2 sensor (+1.3k code)
//#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
//#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)
#ifndef CO2_LOW

350
tasmota/xsns_89_t67xx.ino Normal file
View File

@ -0,0 +1,350 @@
/*
xsns_89_t67xx.ino - Telaire T6700 Series CO2 sensor support for Tasmota
Copyright (c) 2021 Alexander Savchenko (alexander@savchenko.by)
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_T67XX
/*********************************************************************************************\
* Telaire T6703/T6713 - CO2
*
* Based on https://github.com/drug123/T67XX/ by Yaroslav Osadchyy
* (drug123@gmail.com)
*
* I2C Address: 0x15
\*********************************************************************************************/
#define XSNS_89 89
#define XI2C_61 61 // See I2CDEVICES.md
#define T67XX_I2C_ADDR 0x15
#define T67XX_REG_VAL_ENABLE 0xFF00
#define T67XX_REG_VAL_DISABLE 0x0000
#define T67XX_READ_DELAY 10 // Recommended value
#define T67XX_MEASURE_INTERVAL 15 // Seconds
#define T67XX_MEASURE_DELAY 115 // Seconds
// Registers
enum
{
T67XX_REG_FIRMWARE = 0x1389,
T67XX_REG_STATUS = 0x138A,
T67XX_REG_PPM = 0x138B,
T67XX_REG_RESET = 0x03E8,
T67XX_REG_SPCAL = 0x03EC,
T67XX_REG_FLASH_UPDATE = 0x03ED,
T67XX_REG_ADDRESS = 0x0FA5,
T67XX_REG_ABC_LOGIC = 0x03EE,
T67XX_REG_MOD_MODE = 0x100B
};
class T67XX
{
public:
T67XX();
void Read(void);
uint16_t readPPM(void);
uint16_t PPM(void);
uint16_t getStatus(void);
uint16_t getFirmwareVersion(void);
void detect(void);
void reset(void);
void setABCMode(bool Enabled);
void setSlaveAddress(uint8_t Address);
void flashUpdate(void);
void beginCalibration(void);
void endCalibration(void);
const char *getLastStatusMsg(void);
bool found(void);
// 0: no error; 1: error has occurred
struct status
{
uint16_t ERROR : 1;
uint16_t FLASH_ERROR : 1;
uint16_t CALIBRATION_ERROR : 1;
uint16_t REBOOT : 1;
uint16_t WARMUP : 1;
uint16_t SINGLE_POINT_CAL : 1;
void set(uint16_t data)
{
ERROR = data & 0x01;
FLASH_ERROR = (data >> 0x01) & 0x01;
CALIBRATION_ERROR = (data >> 0x02) & 0x01;
REBOOT = (data >> 0x0A) & 0x01;
WARMUP = (data >> 0x0B) & 0x01;
SINGLE_POINT_CAL = (data >> 0x0F) & 0x01;
}
};
private:
uint8_t _data[6];
uint16_t _ppm = 0;
const char *getStatusMsg(uint16_t sensorStatus);
uint16_t read16(uint16_t reg);
void write16(uint16_t reg, uint16_t data);
uint8_t _init = 0;
uint8_t _sta = 0;
status _status;
bool _found = 0;
};
T67XX::T67XX()
{
}
uint16_t T67XX::readPPM(void)
{
_ppm = read16(T67XX_REG_PPM);
return _ppm;
}
uint16_t T67XX::PPM(void)
{
return _ppm;
}
uint16_t T67XX::getStatus(void)
{
_sta = read16(T67XX_REG_STATUS);
_status.set(_sta);
return _sta;
};
uint16_t T67XX::getFirmwareVersion(void)
{
return read16(T67XX_REG_FIRMWARE);
};
void T67XX::detect(void)
{
if (I2cActive(T67XX_I2C_ADDR))
{
return;
}
reset();
delay(100);
uint16_t fw = getFirmwareVersion();
if ((fw > 0) && (fw < 0xFFFF))
{
AddLog(LOG_LEVEL_INFO, PSTR("T67XX firmware version: %d"), fw);
I2cSetActiveFound(T67XX_I2C_ADDR, "T67XX");
_found = true;
}
}
bool T67XX::found(void)
{
return _found;
}
void T67XX::reset(void)
{
write16(T67XX_REG_RESET, T67XX_REG_VAL_ENABLE);
_init = 1;
};
void T67XX::setABCMode(bool Enabled)
{
write16(T67XX_REG_ABC_LOGIC,
Enabled ? T67XX_REG_VAL_ENABLE : T67XX_REG_VAL_DISABLE);
};
void T67XX::setSlaveAddress(uint8_t Address){
// TODO:
};
void T67XX::flashUpdate(void)
{
write16(T67XX_REG_FLASH_UPDATE, T67XX_REG_VAL_ENABLE);
}
void T67XX::beginCalibration(void)
{
write16(T67XX_REG_SPCAL, T67XX_REG_VAL_ENABLE);
};
void T67XX::endCalibration(void)
{
write16(T67XX_REG_SPCAL, T67XX_REG_VAL_DISABLE);
};
/*** Private Section ***/
uint16_t T67XX::read16(uint16_t reg)
{
Wire.beginTransmission(T67XX_I2C_ADDR);
Wire.write(0x04);
Wire.write(byte(reg >> 8));
Wire.write(byte(reg & 0xFF));
Wire.write(0x00);
Wire.write(0x01);
Wire.endTransmission();
delay(T67XX_READ_DELAY);
Wire.requestFrom(int(T67XX_I2C_ADDR), 4);
_data[0] = Wire.read();
_data[1] = Wire.read();
_data[2] = Wire.read();
_data[3] = Wire.read();
return ((_data[2] << 8) | _data[3]);
}
void T67XX::write16(uint16_t reg, uint16_t data)
{
Wire.beginTransmission(T67XX_I2C_ADDR);
Wire.write(0x05);
Wire.write(byte(reg >> 8));
Wire.write(byte(reg & 0xFF));
Wire.write(byte(data >> 8));
Wire.write(byte(data & 0xFF));
Wire.endTransmission();
delay(T67XX_READ_DELAY);
Wire.requestFrom(int(T67XX_I2C_ADDR), 5);
_data[0] = Wire.read();
_data[1] = Wire.read();
_data[2] = Wire.read();
_data[3] = Wire.read();
_data[4] = Wire.read();
}
const char *T67XX::getLastStatusMsg()
{
return getStatusMsg(_sta);
}
const char *T67XX::getStatusMsg(uint16_t sensorStatus)
{
T67XX::status statusStruct;
statusStruct.set(sensorStatus);
if (sensorStatus)
{
if (statusStruct.ERROR)
return PSTR("GENERAL ERROR");
if (statusStruct.CALIBRATION_ERROR)
return PSTR("CALIBRATION ERROR");
if (statusStruct.FLASH_ERROR)
return PSTR("FLASH ERROR");
if (statusStruct.REBOOT)
return PSTR("REBOOT");
if (statusStruct.WARMUP)
return PSTR("WARMUP");
if (statusStruct.SINGLE_POINT_CAL)
return PSTR("CALIBRATING");
}
if (TasmotaGlobal.uptime < T67XX_MEASURE_DELAY)
{
return PSTR("WARMUP");
}
return PSTR("OK");
}
void T67XX::Read(void)
{
if ((TasmotaGlobal.uptime < T67XX_MEASURE_DELAY) || (TasmotaGlobal.uptime % T67XX_MEASURE_INTERVAL > 0))
{
return;
}
uint16_t sensorStatus = getStatus();
if (!sensorStatus)
{
if (_init)
{
setABCMode(true);
_init = 0;
}
_ppm = readPPM();
//AddLog_P(LOG_LEVEL_INFO, PSTR("T67XX %d PPM"), _ppm);
}
else
{
//AddLog_P(LOG_LEVEL_INFO, PSTR("T67XX status: %s"), getLastStatusMsg());
}
}
T67XX t67xx;
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns89(uint8_t function)
{
uint16_t ppm = 0;
if (!I2cEnabled(XI2C_61))
{
return false;
}
bool result = false;
if (FUNC_INIT == function)
{
t67xx.detect();
}
else if (t67xx.found())
{
switch (function)
{
case FUNC_INIT:
break;
case FUNC_EVERY_SECOND:
t67xx.Read();
break;
case FUNC_JSON_APPEND:
ppm = t67xx.PPM();
if ((ppm > 100) && (ppm < 5000))
{
ResponseAppend_P(PSTR(",\"T67XX\":{\"" D_JSON_CO2 "\":%d,\"" D_JSON_STATUS "\":\"%s\"}"), t67xx.PPM(), t67xx.getLastStatusMsg());
}
else
{
ResponseAppend_P(PSTR(",\"T67XX\":{\"" D_JSON_CO2 "\":null,\"" D_JSON_STATUS "\":\"%s\"}"), t67xx.getLastStatusMsg());
}
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
ppm = t67xx.PPM();
if (ppm > 0)
{
WSContentSend_PD(PSTR("{s}T67XX " D_CO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"), ppm);
}
break;
#endif // USE_WEBSERVER
}
}
return result;
}
#endif // USE_T67xx
#endif // USE_I2C