/* 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 (!I2cSetDevice(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