2021-07-10 22:16:31 +01:00
|
|
|
/*
|
|
|
|
xsns_89_t67xx.ino - Telaire T6700 Series CO2 sensor support for Tasmota
|
|
|
|
|
2021-07-13 10:40:13 +01:00
|
|
|
Copyright (c) 2021 Alexander Savchenko (alexander@savchenko.by)
|
2021-07-10 22:16:31 +01:00
|
|
|
|
|
|
|
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
|