diff --git a/CODEOWNERS b/CODEOWNERS index ded5501c62..1b75203654 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -285,6 +285,7 @@ esphome/components/tm1637/* @glmnet esphome/components/tm1638/* @skykingjwc esphome/components/tm1651/* @freekode esphome/components/tmp102/* @timsavage +esphome/components/tmp1075/* @sybrenstuvel esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka esphome/components/toshiba/* @kbx81 diff --git a/esphome/components/tmp1075/__init__.py b/esphome/components/tmp1075/__init__.py new file mode 100644 index 0000000000..ddd04ad11a --- /dev/null +++ b/esphome/components/tmp1075/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@sybrenstuvel"] diff --git a/esphome/components/tmp1075/sensor.py b/esphome/components/tmp1075/sensor.py new file mode 100644 index 0000000000..25ec350b7a --- /dev/null +++ b/esphome/components/tmp1075/sensor.py @@ -0,0 +1,92 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + ICON_THERMOMETER, +) + +DEPENDENCIES = ["i2c"] + +tmp1075_ns = cg.esphome_ns.namespace("tmp1075") + +TMP1075Sensor = tmp1075_ns.class_( + "TMP1075Sensor", cg.PollingComponent, sensor.Sensor, i2c.I2CDevice +) + +EConversionRate = tmp1075_ns.enum("EConversionRate") +CONVERSION_RATES = { + "27.5ms": EConversionRate.CONV_RATE_27_5_MS, + "55ms": EConversionRate.CONV_RATE_55_MS, + "110ms": EConversionRate.CONV_RATE_110_MS, + "220ms": EConversionRate.CONV_RATE_220_MS, +} + +POLARITY = { + "ACTIVE_LOW": 0, + "ACTIVE_HIGH": 1, +} + +EAlertFunction = tmp1075_ns.enum("EAlertFunction") +ALERT_FUNCTION = { + "COMPARATOR": EAlertFunction.ALERT_COMPARATOR, + "INTERRUPT": EAlertFunction.ALERT_INTERRUPT, +} + +CONF_ALERT = "alert" +CONF_LIMIT_LOW = "limit_low" +CONF_LIMIT_HIGH = "limit_high" +CONF_FAULT_COUNT = "fault_count" +CONF_POLARITY = "polarity" +CONF_CONVERSION_RATE = "conversion_rate" +CONF_FUNCTION = "function" + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + TMP1075Sensor, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.Optional(CONF_CONVERSION_RATE): cv.enum(CONVERSION_RATES, lower=True), + cv.Optional(CONF_ALERT, default={}): cv.Schema( + { + cv.Optional(CONF_LIMIT_LOW): cv.temperature, + cv.Optional(CONF_LIMIT_HIGH): cv.temperature, + cv.Optional(CONF_FAULT_COUNT): cv.int_range(min=1, max=4), + cv.Optional(CONF_POLARITY): cv.enum(POLARITY, upper=True), + cv.Optional(CONF_FUNCTION): cv.enum(ALERT_FUNCTION, upper=True), + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x48)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_CONVERSION_RATE in config: + cg.add(var.set_conversion_rate(config[CONF_CONVERSION_RATE])) + + alert = config[CONF_ALERT] + if CONF_LIMIT_LOW in alert: + cg.add(var.set_alert_limit_low(alert[CONF_LIMIT_LOW])) + if CONF_LIMIT_HIGH in alert: + cg.add(var.set_alert_limit_high(alert[CONF_LIMIT_HIGH])) + if CONF_FAULT_COUNT in alert: + cg.add(var.set_fault_count(alert[CONF_FAULT_COUNT])) + if CONF_POLARITY in alert: + cg.add(var.set_alert_polarity(alert[CONF_POLARITY])) + if CONF_FUNCTION in alert: + cg.add(var.set_alert_function(alert[CONF_FUNCTION])) diff --git a/esphome/components/tmp1075/tmp1075.cpp b/esphome/components/tmp1075/tmp1075.cpp new file mode 100644 index 0000000000..38ed2bea31 --- /dev/null +++ b/esphome/components/tmp1075/tmp1075.cpp @@ -0,0 +1,129 @@ +#include "esphome/core/log.h" +#include "tmp1075.h" + +namespace esphome { +namespace tmp1075 { + +static const char *const TAG = "tmp1075"; + +constexpr uint8_t REG_TEMP = 0x0; // Temperature result +constexpr uint8_t REG_CFGR = 0x1; // Configuration +constexpr uint8_t REG_LLIM = 0x2; // Low limit +constexpr uint8_t REG_HLIM = 0x3; // High limit +constexpr uint8_t REG_DIEID = 0xF; // Device ID + +constexpr uint16_t EXPECT_DIEID = 0x0075; // Expected Device ID. + +static uint16_t temp2regvalue(float temp); +static float regvalue2temp(uint16_t regvalue); + +void TMP1075Sensor::setup() { + uint8_t die_id; + if (!this->read_byte(REG_DIEID, &die_id)) { + ESP_LOGW(TAG, "'%s' - unable to read ID", this->name_.c_str()); + this->mark_failed(); + return; + } + if (die_id != EXPECT_DIEID) { + ESP_LOGW(TAG, "'%s' - unexpected ID 0x%x found, expected 0x%x", this->name_.c_str(), die_id, EXPECT_DIEID); + this->mark_failed(); + return; + } + + this->write_config(); +} + +void TMP1075Sensor::update() { + uint16_t regvalue; + if (!read_byte_16(REG_TEMP, ®value)) { + ESP_LOGW(TAG, "'%s' - unable to read temperature register", this->name_.c_str()); + this->status_set_warning(); + return; + } + + const float temp = regvalue2temp(regvalue); + this->publish_state(temp); +} + +void TMP1075Sensor::dump_config() { + LOG_SENSOR("", "TMP1075 Sensor", this); + if (this->is_failed()) { + ESP_LOGE(TAG, " Communication with TMP1075 failed!"); + return; + } + ESP_LOGCONFIG(TAG, " limit low : %.4f °C", alert_limit_low_); + ESP_LOGCONFIG(TAG, " limit high : %.4f °C", alert_limit_high_); + ESP_LOGCONFIG(TAG, " oneshot : %d", config_.fields.oneshot); + ESP_LOGCONFIG(TAG, " rate : %d", config_.fields.rate); + ESP_LOGCONFIG(TAG, " fault_count: %d", config_.fields.faults); + ESP_LOGCONFIG(TAG, " polarity : %d", config_.fields.polarity); + ESP_LOGCONFIG(TAG, " alert_mode : %d", config_.fields.alert_mode); + ESP_LOGCONFIG(TAG, " shutdown : %d", config_.fields.shutdown); +} + +void TMP1075Sensor::set_fault_count(const int faults) { + if (faults < 1) { + ESP_LOGE(TAG, "'%s' - fault_count too low: %d", this->name_.c_str(), faults); + return; + } + if (faults > 4) { + ESP_LOGE(TAG, "'%s' - fault_count too high: %d", this->name_.c_str(), faults); + return; + } + config_.fields.faults = faults - 1; +} + +void TMP1075Sensor::log_config_() { + ESP_LOGV(TAG, " oneshot : %d", config_.fields.oneshot); + ESP_LOGV(TAG, " rate : %d", config_.fields.rate); + ESP_LOGV(TAG, " faults : %d", config_.fields.faults); + ESP_LOGV(TAG, " polarity : %d", config_.fields.polarity); + ESP_LOGV(TAG, " alert_mode: %d", config_.fields.alert_mode); + ESP_LOGV(TAG, " shutdown : %d", config_.fields.shutdown); +} + +void TMP1075Sensor::write_config() { + send_alert_limit_low_(); + send_alert_limit_high_(); + send_config_(); +} + +void TMP1075Sensor::send_config_() { + ESP_LOGV(TAG, "'%s' - sending configuration %04x", this->name_.c_str(), config_.regvalue); + log_config_(); + if (!this->write_byte_16(REG_CFGR, config_.regvalue)) { + ESP_LOGW(TAG, "'%s' - unable to write configuration register", this->name_.c_str()); + return; + } +} + +void TMP1075Sensor::send_alert_limit_low_() { + ESP_LOGV(TAG, "'%s' - sending alert limit low %.3f °C", this->name_.c_str(), alert_limit_low_); + const uint16_t regvalue = temp2regvalue(alert_limit_low_); + if (!this->write_byte_16(REG_LLIM, regvalue)) { + ESP_LOGW(TAG, "'%s' - unable to write low limit register", this->name_.c_str()); + return; + } +} + +void TMP1075Sensor::send_alert_limit_high_() { + ESP_LOGV(TAG, "'%s' - sending alert limit high %.3f °C", this->name_.c_str(), alert_limit_high_); + const uint16_t regvalue = temp2regvalue(alert_limit_high_); + if (!this->write_byte_16(REG_HLIM, regvalue)) { + ESP_LOGW(TAG, "'%s' - unable to write high limit register", this->name_.c_str()); + return; + } +} + +static uint16_t temp2regvalue(const float temp) { + const uint16_t regvalue = temp / 0.0625f; + return regvalue << 4; +} + +static float regvalue2temp(const uint16_t regvalue) { + const int16_t signed_value = regvalue; + return (signed_value >> 4) * 0.0625f; +} + +} // namespace tmp1075 +} // namespace esphome diff --git a/esphome/components/tmp1075/tmp1075.h b/esphome/components/tmp1075/tmp1075.h new file mode 100644 index 0000000000..db2bac517a --- /dev/null +++ b/esphome/components/tmp1075/tmp1075.h @@ -0,0 +1,92 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace tmp1075 { + +struct TMP1075Config { + union { + struct { + uint8_t oneshot : 1; // One-shot conversion mode. Writing 1, starts a single temperature + // conversion. Read returns 0. + + uint8_t rate : 2; // Conversion rate setting when device is in continuous conversion mode. + // 00: 27.5 ms conversion rate + // 01: 55 ms conversion rate + // 10: 110 ms conversion rate + // 11: 220 ms conversion rate (35 ms TMP1075N) + + uint8_t faults : 2; // Consecutive fault measurements to trigger the alert function. + // 00: 1 fault + // 01: 2 faults + // 10: 3 faults (4 faults TMP1075N) + // 11: 4 faults (6 faults TMP1075N) + + uint8_t polarity : 1; // Polarity of the output pin. + // 0: Active low ALERT pin + // 1: Active high ALERT pin + + uint8_t alert_mode : 1; // Selects the function of the ALERT pin. + // 0: ALERT pin functions in comparator mode + // 1: ALERT pin functions in interrupt mode + + uint8_t shutdown : 1; // Sets the device in shutdown mode to conserve power. + // 0: Device is in continuous conversion + // 1: Device is in shutdown mode + uint8_t unused : 8; + } fields; + uint16_t regvalue; + }; +}; + +enum EConversionRate { + CONV_RATE_27_5_MS, + CONV_RATE_55_MS, + CONV_RATE_110_MS, + CONV_RATE_220_MS, +}; + +enum EAlertFunction { + ALERT_COMPARATOR = 0, + ALERT_INTERRUPT = 1, +}; + +class TMP1075Sensor : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + void dump_config() override; + + // Call write_config() after calling any of these to send the new config to + // the IC. The setup() function also does this. + void set_alert_limit_low(const float temp) { this->alert_limit_low_ = temp; } + void set_alert_limit_high(const float temp) { this->alert_limit_high_ = temp; } + void set_oneshot(const bool oneshot) { config_.fields.oneshot = oneshot; } + void set_conversion_rate(const enum EConversionRate rate) { config_.fields.rate = rate; } + void set_alert_polarity(const bool polarity) { config_.fields.polarity = polarity; } + void set_alert_function(const enum EAlertFunction function) { config_.fields.alert_mode = function; } + void set_fault_count(int faults); + + void write_config(); + + protected: + TMP1075Config config_ = {}; + + // Disable the alert pin by default. + float alert_limit_low_ = -128.0f; + float alert_limit_high_ = 127.9375f; + + void send_alert_limit_low_(); + void send_alert_limit_high_(); + void send_config_(); + void log_config_(); +}; + +} // namespace tmp1075 +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 0058c08c74..bee0d93faf 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1299,6 +1299,17 @@ sensor: id: temp_etuve humidity: name: "Humidity hyt271" + - platform: tmp1075 + name: "Temperature TMP1075" + update_interval: 10s + i2c_id: i2c_bus + conversion_rate: 27.5ms + alert: + limit_low: 50 + limit_high: 75 + fault_count: 1 + polarity: active_high + function: comparator esp32_touch: setup_mode: false