Add support for TMP1075 temperature sensor (#4776)

* Add support for TMP1075 temperature sensor

TMP1075 is a temperature sensor with I2C interface in industry standard
LM75 form factor and pinout.

https://www.ti.com/product/TMP1075

Example YAML:

```yaml
sensor:
  - platform: tmp1075
    name: TMP1075 Temperature
    id: radiator_temp
    update_interval: 10s
    i2c_id: i2c_bus_1
    conversion_rate: 27.5ms
    alert:
      limit_low: 50
      limit_high: 75
      fault_count: 1
      polarity: active_high
```

* Add myself as codeowner of the TMP1075 component

* Include '°C' unit when logging low/high limit setting

* Reformat

No functional changes.

* Fix logging: use %.4f for temperatures, not %d

* Fix config initialisation

* Use relative include for `tmp1075.h`

* Apply formatting changes suggested by script/clang-tidy for ESP32

* Add YAML to test1.yaml

* Fix test1.yaml by giving TMP1075 a name

* Less verbose logging (debug -> verbose level)

* Schema: reduce accuracy_decimals to 2

* I2C address as hexadecimal

* Proper name for enum in Python

The enum on the C++ side was renamed (clang-tidy) but I forgot to take that
into account in the Python code.

* Expose 'alert function' to the code generator/YAML params and remove 'shutdown'

Shutdown mode doesn't work the way I expect it, so remove it until someone
actually asks for it.

Also 'alert mode' was renamed to 'alert function' for clarity.

* Move simple setters to header file

* Remove `load_config_();` function
This commit is contained in:
Sybren A. Stüvel 2023-05-26 07:01:21 +02:00 committed by GitHub
parent 79abd773a2
commit 97c1c34708
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 326 additions and 0 deletions

View file

@ -285,6 +285,7 @@ esphome/components/tm1637/* @glmnet
esphome/components/tm1638/* @skykingjwc esphome/components/tm1638/* @skykingjwc
esphome/components/tm1651/* @freekode esphome/components/tm1651/* @freekode
esphome/components/tmp102/* @timsavage esphome/components/tmp102/* @timsavage
esphome/components/tmp1075/* @sybrenstuvel
esphome/components/tmp117/* @Azimath esphome/components/tmp117/* @Azimath
esphome/components/tof10120/* @wstrzalka esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81 esphome/components/toshiba/* @kbx81

View file

@ -0,0 +1 @@
CODEOWNERS = ["@sybrenstuvel"]

View file

@ -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]))

View file

@ -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, &regvalue)) {
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

View file

@ -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

View file

@ -1299,6 +1299,17 @@ sensor:
id: temp_etuve id: temp_etuve
humidity: humidity:
name: "Humidity hyt271" 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: esp32_touch:
setup_mode: false setup_mode: false