From edd6839e484c1084f425e4734c1c2c579e794275 Mon Sep 17 00:00:00 2001 From: Alexander Savchenko Date: Sun, 11 Jul 2021 00:16:31 +0300 Subject: [PATCH] Telaire T67XX CO2 sensor support --- I2CDEVICES.md | 3 +- tasmota/my_user_config.h | 1 + tasmota/tasmota_configurations.h | 1 + tasmota/tasmota_configurations_ESP32.h | 1 + tasmota/xsns_89_t67xx.ino | 350 +++++++++++++++++++++++++ 5 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 tasmota/xsns_89_t67xx.ino diff --git a/I2CDEVICES.md b/I2CDEVICES.md index 2b18c3d4c..730d24113 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -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 \ No newline at end of file + 60 | USE_AM2320 | xsns_88 | AM2320 | 0x5C | Temperature and Humidity sensor + 61 | USE_T67XX | xsns_89 | T67XX | 0x15 | CO2 sensor diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 1a105d8c4..dbb51063c 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -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) diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h index b2aff921c..1330accc2 100644 --- a/tasmota/tasmota_configurations.h +++ b/tasmota/tasmota_configurations.h @@ -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) diff --git a/tasmota/tasmota_configurations_ESP32.h b/tasmota/tasmota_configurations_ESP32.h index 5ebf6ba9e..c0380cb47 100644 --- a/tasmota/tasmota_configurations_ESP32.h +++ b/tasmota/tasmota_configurations_ESP32.h @@ -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 diff --git a/tasmota/xsns_89_t67xx.ino b/tasmota/xsns_89_t67xx.ino new file mode 100644 index 000000000..204b5fadb --- /dev/null +++ b/tasmota/xsns_89_t67xx.ino @@ -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 . +*/ + +#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