mirror of https://github.com/arendst/Tasmota.git
287 lines
9.8 KiB
Arduino
287 lines
9.8 KiB
Arduino
|
/*
|
||
|
xsns_108_tc74.ino - TC74 I2C temperature sensor support for Tasmota
|
||
|
|
||
|
Copyright (C) 2023 Michael Loftis
|
||
|
|
||
|
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_TC74
|
||
|
|
||
|
/*********************************************************************************************\
|
||
|
* TC74 - Temperature Sensor
|
||
|
*
|
||
|
* TC74 I2C Address: 0x4D
|
||
|
* Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/21462D.pdf
|
||
|
*
|
||
|
* Eight I2C address variant parts are available. The part numbers are TC74An where N is
|
||
|
* one of the eight addresses noted in the (unused) #defines below. Be sure to check your
|
||
|
* part number for the correct address before building your firmware image! Due to the
|
||
|
* limited RAM in ESP8266's the default configuration does not try to work with all eight.
|
||
|
*
|
||
|
* For I2C 0x4D, TC74A5 part, is the standard address, other addresses are available as a
|
||
|
* custom order, eight total addresses. This driver thus supports up to
|
||
|
* eight sensors, sensors beyond/other than the default 0x4D require setting
|
||
|
* of defines, there is also no good way to ID these sensors as there's no MSR
|
||
|
* and nothing unique about their information.
|
||
|
*
|
||
|
* These sensors only have a nominal 1C resolution. The git devices are sold
|
||
|
* calibrated to a specific supply voltage but will work from 2.7V-5.5V, but
|
||
|
* will suffer 1C per Volt deviation from the rating of accuracy loss. They're
|
||
|
* only accurate to about +/-2C to begin with.
|
||
|
\*********************************************************************************************/
|
||
|
|
||
|
#define XSNS_108 108
|
||
|
#define XI2C_80 80
|
||
|
|
||
|
//#define TC74_MAX_SENSORS 8 // Support non-default/multiple I2C addresses
|
||
|
//#define TC74_I2C_PROBE_ADDRESSES { 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F } // Addresses to probe/support
|
||
|
//#define TC74_MAX_FAILCOUNT 8 // maximum failed polls before it's marked inactive until reprobing later
|
||
|
//#define TC74_EXTRA_DEBUG // Compile in some extra debug logging statements around failures and measurements
|
||
|
|
||
|
#define TC74_ADDR0 0x48
|
||
|
#define TC74_ADDR1 0x49
|
||
|
#define TC74_ADDR2 0x4A
|
||
|
#define TC74_ADDR3 0x4B
|
||
|
#define TC74_ADDR4 0x4C
|
||
|
#define TC74_ADDR5 0x4D
|
||
|
#define TC74_ADDR6 0x4E
|
||
|
#define TC74_ADDR7 0x4F
|
||
|
#define TC74_ADDR_BASE TC74_ADDR0
|
||
|
#define TC74_ADDR_DEFAULT TC74_ADDR5
|
||
|
|
||
|
#ifndef TC74_MAX_SENSORS
|
||
|
#define TC74_MAX_SENSORS 1
|
||
|
uint8_t tc74_probes[TC74_MAX_SENSORS] PROGMEM = { TC74_ADDR_DEFAULT };
|
||
|
#else
|
||
|
uint8_t tc74_probes[TC74_MAX_SENSORS] PROGMEM = TC74_I2C_PROBE_ADDRESSES;
|
||
|
#endif
|
||
|
|
||
|
#ifndef TC74_MAX_FAILCOUNT
|
||
|
#define TC74_MAX_FAILCOUNT 8
|
||
|
#endif
|
||
|
|
||
|
const uint8_t TC74_CMD_RTR = 0x00; // Read Temperature
|
||
|
const uint8_t TC74_CMD_RWCR = 0x01; // Read/Write CONFIG
|
||
|
|
||
|
/* CONFIG register
|
||
|
* D7 = STANDBY (R/W)
|
||
|
* D6 = READY (R/O)
|
||
|
* D5-D0 = Reserved - always zero
|
||
|
* POR state is all zeroes.
|
||
|
*/
|
||
|
const uint8_t TC74_CONFIG_DRDY = 0b00000010;
|
||
|
|
||
|
// only used once...
|
||
|
#define TC74_CONFIG_MASK 0b11111100
|
||
|
|
||
|
struct {
|
||
|
uint8_t found_count = 0;
|
||
|
uint8_t tcnt = 0;
|
||
|
} tc74_status;
|
||
|
|
||
|
struct {
|
||
|
// Last read temperature
|
||
|
float temperature = NAN;
|
||
|
// flag if we could/couldn't read in most recent poll
|
||
|
bool is_active;
|
||
|
// failure count, if this goes above N we set active to false and
|
||
|
// the sensor will be retried later
|
||
|
uint8_t failed_count;
|
||
|
uint8_t address;
|
||
|
} tc74_sensors[TC74_MAX_SENSORS];
|
||
|
|
||
|
|
||
|
void TC74InitState() {
|
||
|
#ifdef TC74_EXTRA_DEBUG
|
||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TC7: InitState"));
|
||
|
#endif
|
||
|
for (uint32_t i = 0; i < TC74_MAX_SENSORS; i++) {
|
||
|
tc74_sensors[i].is_active = false;
|
||
|
tc74_sensors[i].failed_count = 0;
|
||
|
tc74_sensors[i].address = pgm_read_byte(tc74_probes + i);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TC74Detect(bool forced) {
|
||
|
for (uint32_t i = 0; i < TC74_MAX_SENSORS; i++) {
|
||
|
uint8_t config_reg;
|
||
|
uint8_t addr = tc74_sensors[i].address;
|
||
|
#ifdef TC74_EXTRA_DEBUG
|
||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TC7: Addr %X probing"), addr);
|
||
|
#endif
|
||
|
|
||
|
// if we failed more than N times, unless we're being forced to, skip it.
|
||
|
if (!forced && tc74_sensors[i].failed_count >= TC74_MAX_FAILCOUNT ) {
|
||
|
tc74_sensors[i].is_active = false;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// I2cSetDevice ALWAYs returns false if a device is marked active already...
|
||
|
// So if we have the is_active flag for this device SKIP this check
|
||
|
if (!tc74_sensors[i].is_active && !I2cSetDevice(addr)) {
|
||
|
if (tc74_sensors[i].failed_count < 253) { tc74_sensors[i].failed_count++; }
|
||
|
#ifdef TC74_EXTRA_DEBUG
|
||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TC7: Addr %X failed I2cSetDevice"), addr);
|
||
|
#endif
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Pull CONFIG and check it, best we can do to keep away from other I2C devices
|
||
|
if (!I2cValidRead8(&config_reg, addr, TC74_CMD_RWCR)) {
|
||
|
tc74_sensors[i].is_active = false;
|
||
|
tc74_sensors[i].failed_count++;
|
||
|
if(I2cActive(addr)) { I2cResetActive(addr); }
|
||
|
#ifdef TC74_EXTRA_DEBUG
|
||
|
AddLog(LOG_LEVEL_ERROR, PSTR("TC7: Addr %X failed CONFIG read, deactivated"), addr);
|
||
|
#endif
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// if any reserved bits are set, not our device
|
||
|
if (config_reg & TC74_CONFIG_MASK != 0x00 ) {
|
||
|
tc74_sensors[i].is_active = false;
|
||
|
tc74_sensors[i].failed_count++;
|
||
|
if(I2cActive(addr)) { I2cResetActive(addr); }
|
||
|
#ifdef TC74_EXTRA_DEBUG
|
||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TC7: Addr %X found reserved bits set [%x]"), addr, config_reg);
|
||
|
#endif
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Make sure STANDBY is not set, POR should be clear, but, if another I2C driver toggled it....
|
||
|
I2cWrite8(addr, TC74_CMD_RWCR, 0x0);
|
||
|
|
||
|
if (!tc74_sensors[i].is_active) {
|
||
|
tc74_sensors[i].is_active = true;
|
||
|
#ifdef TC74_EXTRA_DEBUG
|
||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TC7: Addr %X set active"), addr);
|
||
|
#endif
|
||
|
I2cSetActiveFound(addr, "TC74");
|
||
|
}
|
||
|
tc74_sensors[i].failed_count = 0;
|
||
|
} // for sensors...
|
||
|
}
|
||
|
|
||
|
void TC74PollActive() {
|
||
|
for (uint8_t i = 0; i < TC74_MAX_SENSORS; i++) {
|
||
|
uint8_t addr = tc74_sensors[i].address;
|
||
|
uint8_t config_reg;
|
||
|
int8_t temperature_register;
|
||
|
|
||
|
if (!tc74_sensors[i].is_active) { continue; }
|
||
|
|
||
|
// if timing were a problem we could read both bytes from the RTR, then
|
||
|
// do mask/shift to recover the parts we want.
|
||
|
if (!I2cValidRead8(&config_reg, addr, TC74_CMD_RWCR)) {
|
||
|
tc74_sensors[i].failed_count++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// check if a measurement is ready
|
||
|
if (config_reg & TC74_CONFIG_DRDY != 0b00000010) { continue; }
|
||
|
|
||
|
// grab most recent reading
|
||
|
if (!I2cValidRead8((uint8_t*)&temperature_register, addr, TC74_CMD_RTR)) {
|
||
|
tc74_sensors[i].failed_count++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
tc74_sensors[i].temperature = ConvertTemp(temperature_register);
|
||
|
tc74_sensors[i].failed_count = 0;
|
||
|
#ifdef TC74_EXTRA_DEBUG
|
||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TC7: Addr %X register temperature was %hhi or [%i]"), addr, temperature_register, temperature_register);
|
||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("TC7: Addr %X stored temperature %*_f"), addr, Settings->flag2.temperature_resolution, &tc74_sensors[i].temperature);
|
||
|
#endif
|
||
|
|
||
|
} // for sensors...
|
||
|
}
|
||
|
|
||
|
void TC74Show(bool json) {
|
||
|
bool once = true;
|
||
|
// loop over sensors, only output if active
|
||
|
for (uint8_t i = 0; i < TC74_MAX_SENSORS; i++) {
|
||
|
if (tc74_sensors[i].is_active) {
|
||
|
char sname[10];
|
||
|
snprintf_P(sname, sizeof(sname), PSTR("TC74%c%02X"), IndexSeparator(), tc74_sensors[i].address);
|
||
|
if (json) {
|
||
|
ResponseAppend_P(JSON_SNS_F_TEMP, sname, Settings->flag2.temperature_resolution, &tc74_sensors[i].temperature);
|
||
|
// also send KNX and Domoticz if enabled...and first sensor reporting
|
||
|
// might beed guarded by some sort of enable (compile time or otherwise)
|
||
|
if ((0 == TasmotaGlobal.tele_period) && once) {
|
||
|
#ifdef USE_DOMOTICZ
|
||
|
DomoticzFloatSensor(DZ_TEMP, tc74_sensors[i].temperature);
|
||
|
#endif // USE_DOMOTICZ
|
||
|
#ifdef USE_KNX
|
||
|
KnxSensor(KNX_TEMPERATURE, tc74_sensors[i].temperature);
|
||
|
#endif // USE_KNX
|
||
|
once = false;
|
||
|
} // tele_period, first reporter
|
||
|
#ifdef USE_WEBSERVER
|
||
|
} else {
|
||
|
WSContentSend_Temp(sname, tc74_sensors[i].temperature);
|
||
|
#endif // USE_WEBSERVER
|
||
|
} // if(json)
|
||
|
} // if(is_active)
|
||
|
} // for sensors...
|
||
|
}
|
||
|
|
||
|
/*********************************************************************************************\
|
||
|
* Interface
|
||
|
\*********************************************************************************************/
|
||
|
|
||
|
bool Xsns108(uint32_t function) {
|
||
|
if (!I2cEnabled(XI2C_80)) { return false; }
|
||
|
|
||
|
bool result = false;
|
||
|
|
||
|
// Unlike some other drivers we DO support sensors disappearing and reappearing later
|
||
|
// or simply not available during initial startup
|
||
|
|
||
|
switch (function) {
|
||
|
case FUNC_INIT:
|
||
|
TC74InitState();
|
||
|
TC74Detect(false);
|
||
|
break;
|
||
|
case FUNC_HOTPLUG_SCAN:
|
||
|
TC74Detect(true);
|
||
|
tc74_status.tcnt = 1;
|
||
|
break;
|
||
|
case FUNC_EVERY_SECOND:
|
||
|
/* if(tc74_status.tcnt == 0 || tc74_status.tcnt == 60) {
|
||
|
TC74Detect(true);
|
||
|
} */
|
||
|
tc74_status.tcnt++;
|
||
|
if(tc74_status.tcnt > 60) {
|
||
|
tc74_status.tcnt = 1;
|
||
|
}
|
||
|
TC74PollActive();
|
||
|
break;
|
||
|
case FUNC_JSON_APPEND:
|
||
|
TC74Show(true);
|
||
|
break;
|
||
|
#ifdef USE_WEBSERVER
|
||
|
case FUNC_WEB_SENSOR:
|
||
|
TC74Show(false);
|
||
|
break;
|
||
|
#endif
|
||
|
} // switch(function)
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
#endif // USE_TC74
|
||
|
#endif // USE_I2C
|