/* xsns_14_sht3x.ino - SHT3X, SHT4X and SHTCX temperature and humidity sensor support for Tasmota Copyright (C) 2024 Theo Arends, Stefan Tibus, Jan-David Förster 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_SHT3X /*********************************************************************************************\ * Sensirion I2C temperature and humidity sensors * * This driver supports the following sensors: * - SHT3x series: SHT30, SHT31, SHT35 (addresses: A: 0x44, B: 0x45) * - SHTC series: SHTC1, SHTC3 (address: 0x70) * - SHT4x series: SHT40, SHT41, SHT45 (addresses: A: 0x44, B: 0x45, C: 0x46) \*********************************************************************************************/ #define XSNS_14 14 #define XI2C_15 15 // See I2CDEVICES.md #define SHT3X_TYPES 3 // SHT3X, SHTCX and SHT4X #define SHT3X_ADDRESSES 4 // 0x44, 0x45, 0x46 and 0x70 enum SHT3X_Types { SHT3X_TYPE_SHT3X, SHT3X_TYPE_SHTCX, SHT3X_TYPE_SHT4X }; const char kSht3xTypes[] PROGMEM = "SHT3X|SHTC3|SHT4X"; uint8_t sht3x_addresses[] = { 0x44, 0x45, 0x46, 0x70 }; uint8_t sht3x_count = 0; struct SHT3XSTRUCT { uint8_t type; // Sensor type uint8_t address; // I2C bus address uint8_t bus; char types[6]; // Sensor type name and address, e.g. "SHT3X" } sht3x_sensors[SHT3X_ADDRESSES]; uint8_t Sht3xComputeCrc(uint8_t data[], uint8_t len) { // Compute CRC as per datasheet uint8_t crc = 0xFF; for (uint8_t x = 0; x < len; x++) { crc ^= data[x]; for (uint8_t i = 0; i < 8; i++) { if (crc & 0x80) { crc = (crc << 1) ^ 0x31; } else { crc <<= 1; } } } return crc; } bool Sht3xRead(uint32_t sensor, float &t, float &h) { t = NAN; h = NAN; TwoWire& myWire = I2cGetWire(sht3x_sensors[sensor].bus); if (&myWire == nullptr) { return false; } // No valid I2c bus uint32_t type = sht3x_sensors[sensor].type; uint8_t i2c_address = sht3x_sensors[sensor].address; myWire.beginTransmission(i2c_address); switch (type) { case SHT3X_TYPE_SHT3X: // TODO: Clock stretching is used for SHT3x but not for SHTC3. Why? myWire.write(0x2C); // Enable clock stretching myWire.write(0x06); // High repeatability measurement break; case SHT3X_TYPE_SHTCX: myWire.write(0x35); // Wake from myWire.write(0x17); // sleep myWire.endTransmission(); myWire.beginTransmission(i2c_address); // TODO: Clock stretching is used for SHT3x but not for SHTC3. Why? myWire.write(0x78); // Disable clock stretching myWire.write(0x66); // Normal mode measurement break; case SHT3X_TYPE_SHT4X: myWire.write(0xFD); // High repeatability measurement break; } if (myWire.endTransmission() != 0) { // Stop I2C transmission return false; } delay(30); // Timing verified with logic analyzer (10 is to short) uint8_t data[6]; myWire.requestFrom(i2c_address, (uint8_t)6); // Request 6 bytes of data for (uint32_t i = 0; i < 6; i++) { data[i] = myWire.read(); // temperature (MSB, LSB, CRC), humidity (MSB, LSB, CRC) }; if ((Sht3xComputeCrc(&data[0], 2) != data[2]) || (Sht3xComputeCrc(&data[3], 2) != data[5])) { return false; } t = ((float)(((data[0] << 8) | data[1]) * 175) / 65535.0) - 45.0; if (type == SHT3X_TYPE_SHT4X) { h = ((float)(((data[3] << 8) | data[4]) * 125) / 65535.0) - 6.0; } else { h = ((float)(((data[3] << 8) | data[4]) * 100) / 65535.0); } return (!isnan(t) && !isnan(h)); } /********************************************************************************************/ void Sht3xDetect(void) { float t; float h; for (uint32_t bus = 0; bus < 2; bus++) { for (uint32_t k = 0; k < SHT3X_TYPES; k++) { for (uint32_t i = 0; i < SHT3X_ADDRESSES; i++) { if (!I2cSetDevice(sht3x_addresses[i], bus)) { continue; } sht3x_sensors[sht3x_count].type = k; sht3x_sensors[sht3x_count].address = sht3x_addresses[i]; sht3x_sensors[sht3x_count].bus = bus; if (Sht3xRead(sht3x_count, t, h)) { GetTextIndexed(sht3x_sensors[sht3x_count].types, sizeof(sht3x_sensors[sht3x_count].types), sht3x_sensors[sht3x_count].type, kSht3xTypes); I2cSetActiveFound(sht3x_sensors[sht3x_count].address, sht3x_sensors[sht3x_count].types, sht3x_sensors[sht3x_count].bus); sht3x_count++; if (SHT3X_ADDRESSES == sht3x_count) { return; } } } } } } void Sht3xShow(bool json) { float t; float h; char types[11]; for (uint32_t idx = 0; idx < sht3x_count; idx++) { if (Sht3xRead(idx, t, h)) { t = ConvertTemp(t); h = ConvertHumidity(h); strlcpy(types, sht3x_sensors[idx].types, sizeof(types)); if (sht3x_count > 1) { snprintf_P(types, sizeof(types), PSTR("%s%c%02X"), types, IndexSeparator(), sht3x_sensors[idx].address); // "SHT3X-0xXX" #ifdef ESP32 if (TasmotaGlobal.i2c_enabled_2) { for (uint32_t i = 1; i < sht3x_count; i++) { if (sht3x_sensors[0].bus != sht3x_sensors[i].bus) { snprintf_P(types, sizeof(types), PSTR("%s%c%d"), types, IndexSeparator(), sht3x_sensors[idx].bus + 1); // "SHT3X-0xXX-X" break; } } } #endif } TempHumDewShow(json, ((0 == TasmotaGlobal.tele_period) && (0 == idx)), types, t, h); } } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xsns14(uint32_t function) { if (!I2cEnabled(XI2C_15)) { return false; } bool result = false; if (FUNC_INIT == function) { Sht3xDetect(); } else if (sht3x_count) { switch (function) { case FUNC_JSON_APPEND: Sht3xShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Sht3xShow(0); break; #endif // USE_WEBSERVER } } return result; } #endif // USE_SHT3X #endif // USE_I2C