/* xsns_65_hdc1080.ino - Texas Instruments HDC1080 temperature and humidity sensor support for Tasmota Copyright (C) 2020 Luis Teixeira 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_HDC1080 /*********************************************************************************************\ * HDC1080 - Temperature and Humidity sensor * * Source: Luis Teixeira * * I2C Address: 0x40 \*********************************************************************************************/ #define XSNS_65 65 #define XI2C_45 45 // See I2CDEVICES.md #define HDC1080_ADDR 0x40 // Registers: #define HDC_REG_TEMP 0x00 // Temperature register #define HDC_REG_RH 0x01 // Humidity register #define HDC_REG_CONFIG 0x02 // Configuration register #define HDC_REG_SERIAL1 0xFB // First 2 bytes of the serial ID #define HDC_REG_SERIAL2 0xFC // Mid 2 bytes of the serial ID #define HDC_REG_SERIAL3 0xFD // Last bits of the serial ID #define HDC_REG_MAN_ID 0xFE // Manufacturer ID #define HDC_REG_DEV_ID 0xFF // Device ID // Expected constant values of some of the registers: #define HDC1080_MAN_ID 0x5449 // Manufacturer ID (Texas Instruments) #define HDC1080_DEV_ID 0x1050 // Device ID (valid for the HDC1080) // Possible values for the configuration register fields: #define HDC1080_RST_ON 0x8000 #define HDC1080_HEAT_ON 0x2000 #define HDC1080_MODE_ON 0x1000 // acquision mode (temperature + humidity) #define HDC1080_TRES_11 0x400 #define HDC1080_HRES_11 0x100 #define HDC1080_HRES_8 0x80 // Constants: #define HDC1080_CONV_TIME 15 // Assume 6.50 + 6.35 ms + x of conversion delay for this device #define HDC1080_TEMP_MULT 0.0025177 #define HDC1080_RH_MULT 0.0015258 #define HDC1080_TEMP_OFFSET 40.0 const char* hdc_type_name = "HDC1080"; uint16_t hdc_manufacturer_id = 0; uint16_t hdc_device_id = 0; float hdc_temperature = 0.0; float hdc_humidity = 0.0; uint8_t hdc_valid = 0; bool is_reading = false; uint32_t hdc_next_read; /** * Reads the device ID register. * */ uint16_t HdcReadDeviceId(void) { return I2cRead16(HDC1080_ADDR, HDC_REG_DEV_ID); } /** * Reads the manufacturer ID register. * */ uint16_t HdcReadManufacturerId(void) { return I2cRead16(HDC1080_ADDR, HDC_REG_MAN_ID); } /** * Overwrites the configuration register with the provided config */ void HdcConfig(uint16_t config) { I2cWrite16(HDC1080_ADDR, HDC_REG_CONFIG, config); } /** * Performs a soft reset on the device. * * RST = 1 -> software reset * */ void HdcReset(void) { uint16_t current = I2cRead16(HDC1080_ADDR, HDC_REG_CONFIG); // bit 15 of the configuration register contains the RST flag // so we set it to 1: current |= 0x8000; I2cWrite16(HDC1080_ADDR, HDC_REG_CONFIG, current); delay(HDC1080_CONV_TIME); // Not sure how long it takes to reset. Assuming this value. } /** * Performs the write portion of the HDC1080 sensor transaction. This * action of writing to a register signals the beginning of the operation * (e.g. data acquisition). * * addr: the address of the I2C device we are talking to. * reg: the register where we are writing to. * * returns: 0 if the transmission was successfully completed, != 0 otherwise. */ int8_t HdcTransactionOpen(uint8_t addr, uint8_t reg) { Wire.beginTransmission((uint8_t) addr); Wire.write((uint8_t) reg); return Wire.endTransmission(); } /** * Performs the read portion of the HDC1080 sensor transaction. * * addr: the address of the I2C device we are talking to. * reg_data: the pointer to the memory location where we will place the bytes that were read from the device * len: the number of bytes we expect to read * * returns: if the read operation was successful. != 0 otherwise. */ int8_t HdcTransactionClose(uint8_t addr, uint8_t *reg_data, uint16_t len) { if (len != Wire.requestFrom((uint8_t) addr, (uint8_t) len)) { return 1; } while (len--) { *reg_data = (uint8_t)Wire.read(); reg_data++; } return 0; } /** * The various initialization steps for this sensor. * */ void HdcInit(void) { HdcReset(); HdcConfig(HDC1080_MODE_ON); } /** * Triggers the single transaction read of the T/RH sensor. * */ bool HdcTriggerRead(void) { int8_t status = HdcTransactionOpen(HDC1080_ADDR, HDC_REG_TEMP); hdc_next_read = millis() + HDC1080_CONV_TIME; if(status) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("HdcTriggerRead: failed to open the transaction for HDC_REG_TEMP. Status = %d"), status); return false; } is_reading = true; return true; } /** * Performs a temperature and humidity measurement, and calls * the conversion function providing the results in the correct * unit according to the device settings. * * returns: false if something failed during the read process. * */ bool HdcRead(void) { int8_t status = 0; uint8_t sensor_data[4]; uint16_t temp_data = 0; uint16_t rh_data = 0; is_reading = false; status = HdcTransactionClose(HDC1080_ADDR, sensor_data, 4); if(status) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("HdcRead: failed to read HDC_REG_TEMP. Status = %d"), status); return false; } temp_data = (uint16_t) ((sensor_data[0] << 8) | sensor_data[1]); rh_data = (uint16_t) ((sensor_data[2] << 8) | sensor_data[3]); AddLog_P(LOG_LEVEL_DEBUG, PSTR("HdcRead: temperature raw data: 0x%04x; humidity raw data: 0x%04x"), temp_data, rh_data); // read the temperature from the first 16 bits of the result hdc_temperature = ConvertTemp(HDC1080_TEMP_MULT * (float) (temp_data) - HDC1080_TEMP_OFFSET); hdc_humidity = HDC1080_RH_MULT * (float) (rh_data); if (hdc_humidity > 100) { hdc_humidity = 100.0; } if (hdc_humidity < 0) { hdc_humidity = 0.01; } hdc_humidity = ConvertHumidity(hdc_humidity); hdc_valid = SENSOR_MAX_MISS; return true; } /** * Performs the detection of the HTC1080 sensor. * */ void HdcDetect(void) { if (I2cActive(HDC1080_ADDR)) { // AddLog_P(LOG_LEVEL_DEBUG, PSTR("HdcDetect: Address = 0x%02X already in use."), HDC1080_ADDR); return; } hdc_manufacturer_id = HdcReadManufacturerId(); hdc_device_id = HdcReadDeviceId(); AddLog_P(LOG_LEVEL_DEBUG, PSTR("HdcDetect: detected device with manufacturerId = 0x%04X and deviceId = 0x%04X"), hdc_manufacturer_id, hdc_device_id); if (hdc_device_id == HDC1080_DEV_ID) { HdcInit(); I2cSetActiveFound(HDC1080_ADDR, hdc_type_name); } } /** * As the name suggests, this function is called every second * for performing driver related logic. * */ void HdcEverySecond(void) { if (TasmotaGlobal.uptime &1) { // Every 2 seconds if (!HdcTriggerRead()) { AddLogMissed((char*) hdc_type_name, hdc_valid); } } } /** * Tasmota boilerplate for presenting the sensor data in the web UI, JSON for * the MQTT messages, and so on. * */ void HdcShow(bool json) { if (hdc_valid) { TempHumDewShow(json, (0 == TasmotaGlobal.tele_period), hdc_type_name, hdc_temperature, hdc_humidity); } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xsns65(uint8_t function) { if (!I2cEnabled(XI2C_45)) { // AddLog_P(LOG_LEVEL_DEBUG, PSTR("Xsns65: I2C driver not enabled for this device.")); return false; } bool result = false; if (FUNC_INIT == function) { HdcDetect(); } else if (hdc_device_id) { switch (function) { case FUNC_EVERY_50_MSECOND: if(is_reading && TimeReached(hdc_next_read)) { if(!HdcRead()) { AddLogMissed((char*) hdc_type_name, hdc_valid); } } break; case FUNC_EVERY_SECOND: HdcEverySecond(); break; case FUNC_JSON_APPEND: HdcShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: HdcShow(0); break; #endif // USE_WEBSERVER } } return result; } #endif // USE_HDC1080 #endif // USE_I2C