mirror of https://github.com/arendst/Tasmota.git
Merge pull request #12618 from dizel-by/t67xx
Telaire T67XX CO2 sensor support
This commit is contained in:
commit
f7439ad1c4
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue