/* xsns_07_sht1x.ino - SHT1x temperature and sensor support for Tasmota Copyright (C) 2021 Theo Arends 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_SHT /*********************************************************************************************\ * SHT1x - Temperature and Humidity * * Reading temperature and humidity takes about 320 milliseconds! * Source: Marinus vd Broek https://github.com/ESP8266nu/ESPEasy * * I2C Address: None and ruins I2C bus hence reinit I2C after each call \*********************************************************************************************/ #define XSNS_07 7 #define XI2C_08 8 // See I2CDEVICES.md enum { SHT1X_CMD_MEASURE_TEMP = B00000011, SHT1X_CMD_MEASURE_RH = B00000101, SHT1X_CMD_SOFT_RESET = B00011110 }; struct { float temperature = 0; float humidity = 0; int8_t sda_pin; int8_t scl_pin; uint8_t type = 0; uint8_t valid = 0; char types[6] = "SHT1X"; } Sht1x; bool ShtReset(void) { pinMode(Sht1x.sda_pin, INPUT_PULLUP); pinMode(Sht1x.scl_pin, OUTPUT); delay(11); for (uint32_t i = 0; i < 9; i++) { digitalWrite(Sht1x.scl_pin, HIGH); digitalWrite(Sht1x.scl_pin, LOW); } bool success = ShtSendCommand(SHT1X_CMD_SOFT_RESET); delay(11); return success; } bool ShtSendCommand(const uint8_t cmd) { pinMode(Sht1x.sda_pin, OUTPUT); // Transmission Start sequence digitalWrite(Sht1x.sda_pin, HIGH); digitalWrite(Sht1x.scl_pin, HIGH); digitalWrite(Sht1x.sda_pin, LOW); digitalWrite(Sht1x.scl_pin, LOW); digitalWrite(Sht1x.scl_pin, HIGH); digitalWrite(Sht1x.sda_pin, HIGH); digitalWrite(Sht1x.scl_pin, LOW); // Send the command (address must be 000b) TasShiftOut(Sht1x.sda_pin, Sht1x.scl_pin, MSBFIRST, cmd); // Wait for ACK bool ackerror = false; digitalWrite(Sht1x.scl_pin, HIGH); pinMode(Sht1x.sda_pin, INPUT_PULLUP); if (digitalRead(Sht1x.sda_pin) != LOW) { ackerror = true; } digitalWrite(Sht1x.scl_pin, LOW); delayMicroseconds(1); // Give the sensor time to release the data line if (digitalRead(Sht1x.sda_pin) != HIGH) { ackerror = true; } if (ackerror) { // Sht1x.type = 0; AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_SHT1 D_SENSOR_DID_NOT_ACK_COMMAND)); } return (!ackerror); } bool ShtAwaitResult(void) { // Maximum 320ms for 14 bit measurement for (uint32_t i = 0; i < 16; i++) { if (LOW == digitalRead(Sht1x.sda_pin)) { return true; } delay(20); } AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_SHT1 D_SENSOR_BUSY)); // Sht1x.type = 0; return false; } int ShtReadData(void) { int val = 0; // Read most significant byte val = TasShiftIn(Sht1x.sda_pin, Sht1x.scl_pin, 8); val <<= 8; // Send ACK pinMode(Sht1x.sda_pin, OUTPUT); digitalWrite(Sht1x.sda_pin, LOW); digitalWrite(Sht1x.scl_pin, HIGH); digitalWrite(Sht1x.scl_pin, LOW); pinMode(Sht1x.sda_pin, INPUT_PULLUP); // Read least significant byte val |= TasShiftIn(Sht1x.sda_pin, Sht1x.scl_pin, 8); // Keep DATA pin high to skip CRC digitalWrite(Sht1x.scl_pin, HIGH); digitalWrite(Sht1x.scl_pin, LOW); return val; } bool ShtRead(void) { if (Sht1x.valid) { Sht1x.valid--; } if (!ShtReset()) { return false; } if (!ShtSendCommand(SHT1X_CMD_MEASURE_TEMP)) { return false; } if (!ShtAwaitResult()) { return false; } float tempRaw = ShtReadData(); if (!ShtSendCommand(SHT1X_CMD_MEASURE_RH)) { return false; } if (!ShtAwaitResult()) { return false; } float humRaw = ShtReadData(); // Temperature conversion coefficients from SHT1X datasheet for version 4 const float d1 = -39.7f; // 3.5V const float d2 = 0.01f; // 14-bit Sht1x.temperature = d1 + (tempRaw * d2); const float c1 = -2.0468f; const float c2 = 0.0367f; const float c3 = -1.5955E-6f; const float t1 = 0.01f; const float t2 = 0.00008f; float rhLinear = c1 + c2 * humRaw + c3 * humRaw * humRaw; Sht1x.humidity = (Sht1x.temperature - 25) * (t1 + t2 * humRaw) + rhLinear; Sht1x.temperature = ConvertTemp(Sht1x.temperature); Sht1x.humidity = ConvertHumidity(Sht1x.humidity); Sht1x.valid = SENSOR_MAX_MISS; return true; } /********************************************************************************************/ void ShtDetect(void) { Sht1x.sda_pin = Pin(GPIO_I2C_SDA); Sht1x.scl_pin = Pin(GPIO_I2C_SCL); if (ShtRead()) { Sht1x.type = 1; AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C D_SHT1X_FOUND)); } I2cBegin(Sht1x.sda_pin, Sht1x.scl_pin); // Reinit I2C bus } void ShtEverySecond(void) { if (!(TasmotaGlobal.uptime %4)) { // Every 4 seconds // 344mS if (!ShtRead()) { AddLogMissed(Sht1x.types, Sht1x.valid); } I2cBegin(Sht1x.sda_pin, Sht1x.scl_pin); // Reinit I2C bus } } void ShtShow(bool json) { if (Sht1x.valid) { TempHumDewShow(json, (0 == TasmotaGlobal.tele_period), Sht1x.types, Sht1x.temperature, Sht1x.humidity); } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xsns07(uint8_t function) { if (!I2cEnabled(XI2C_08)) { return false; } bool result = false; if (FUNC_INIT == function) { ShtDetect(); } else if (Sht1x.type) { switch (function) { case FUNC_EVERY_SECOND: ShtEverySecond(); break; case FUNC_JSON_APPEND: ShtShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: ShtShow(0); break; #endif // USE_WEBSERVER } } return result; } #endif // USE_SHT #endif // USE_I2C