[tmp102] Add hardware configuration options (#20063)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
techdregs
2026-10-11 09:06:40 -10:00
committed by GitHub
co-authored by J. Nick Koston J. Nick Koston
parent c8a300164b
commit 6322cef8e2
15 changed files with 684 additions and 35 deletions
+82 -8
View File
@@ -1,11 +1,85 @@
"""
The TMP102 is a two-wire, serial output temperature
sensor available in a tiny SOT563 package. Requiring
no external components, the TMP102 is capable of
reading temperatures to a resolution of 0.0625°C.
"""Shared TMP102 types and configuration validation."""
https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf
"""
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components.const import CONF_CONVERSION_RATE
import esphome.config_validation as cv
from esphome.types import ConfigType
DOMAIN = "tmp102"
tmp102_ns = cg.esphome_ns.namespace("tmp102")
TMP102Component = tmp102_ns.class_(
"TMP102Component", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor
)
CONF_EXTENDED_MODE = "extended_mode"
CONF_ONE_SHOT_MODE = "one_shot_mode"
CONF_TEMPERATURE_HIGH = "temperature_high"
CONF_TEMPERATURE_LOW = "temperature_low"
CONF_ALERT_POLARITY = "alert_polarity"
CONF_THERMOSTAT_MODE = "thermostat_mode"
CONF_FAULT_QUEUE = "fault_queue"
CONVERSION_RATES = {"0.25Hz": 0, "1Hz": 1, "4Hz": 2, "8Hz": 3}
THERMOSTAT_MODES = {"comparator": 0, "interrupt": 1}
ALERT_POLARITIES = {"active_low": 0, "active_high": 1}
FAULT_QUEUE_BITS = {1: 0, 2: 1, 4: 2, 6: 3}
TMP102_CONVERSION_FACTOR = 0.0625
TMP102_DEFAULT_THIGH = 80.0
TMP102_DEFAULT_TLOW = 75.0
def encode_temperature(temperature: float, extended: bool) -> int:
"""Encode a temperature as a TMP102 limit-register word."""
steps = round(temperature / TMP102_CONVERSION_FACTOR)
return (steps << (3 if extended else 4)) & 0xFFFF
def build_configuration(config: ConfigType) -> int:
"""Build the writable TMP102 configuration-register bits."""
value = CONVERSION_RATES[config.get(CONF_CONVERSION_RATE, "4Hz")] << 6
value |= FAULT_QUEUE_BITS[config.get(CONF_FAULT_QUEUE, 1)] << 11
value |= ALERT_POLARITIES[config.get(CONF_ALERT_POLARITY, "active_low")] << 10
value |= THERMOSTAT_MODES[config.get(CONF_THERMOSTAT_MODE, "comparator")] << 9
if config.get(CONF_ONE_SHOT_MODE, False):
value |= 1 << 8
if config.get(CONF_EXTENDED_MODE, False):
value |= 1 << 4
return value
def validate_tmp102_thresholds(config: ConfigType) -> ConfigType:
extended = config.get(CONF_EXTENDED_MODE, False)
t_min = -55.0
t_max = 150.0 if extended else 127.9375
high = config.get(CONF_TEMPERATURE_HIGH)
low = config.get(CONF_TEMPERATURE_LOW)
for key, value in ((CONF_TEMPERATURE_HIGH, high), (CONF_TEMPERATURE_LOW, low)):
if value is not None and not t_min <= value <= t_max:
raise cv.Invalid(
f"{key} ({value}°C) out of range for "
f"{'extended' if extended else 'normal'} mode "
f"({t_min}°C to {t_max}°C)",
[key],
)
if high is not None and low is not None and low > high:
raise cv.Invalid(
f"low limit ({low}°C) must be <= high limit ({high}°C)",
[CONF_TEMPERATURE_LOW],
)
if high is not None and low is None and high < TMP102_DEFAULT_TLOW:
raise cv.Invalid(
f"high limit ({high}°C) is below the chip power-up TLOW default "
f"({TMP102_DEFAULT_TLOW}°C). Set temperature_low explicitly.",
[CONF_TEMPERATURE_HIGH],
)
if low is not None and high is None and low > TMP102_DEFAULT_THIGH:
raise cv.Invalid(
f"low limit ({low}°C) is above the chip power-up THIGH default "
f"({TMP102_DEFAULT_THIGH}°C). Set temperature_high explicitly.",
[CONF_TEMPERATURE_LOW],
)
return config
+51 -14
View File
@@ -1,15 +1,8 @@
"""
The TMP102 is a two-wire, serial output temperature
sensor available in a tiny SOT563 package. Requiring
no external components, the TMP102 is capable of
reading temperatures to a resolution of 0.0625°C.
https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf
"""
"""TMP102 temperature sensor and optional hardware configuration."""
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components.const import CONF_CONVERSION_RATE
import esphome.config_validation as cv
from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
@@ -18,15 +11,42 @@ from esphome.const import (
)
from esphome.types import ConfigType
from . import (
ALERT_POLARITIES,
CONF_ALERT_POLARITY,
CONF_EXTENDED_MODE,
CONF_FAULT_QUEUE,
CONF_ONE_SHOT_MODE,
CONF_TEMPERATURE_HIGH,
CONF_TEMPERATURE_LOW,
CONF_THERMOSTAT_MODE,
CONVERSION_RATES,
FAULT_QUEUE_BITS,
THERMOSTAT_MODES,
TMP102_DEFAULT_THIGH,
TMP102_DEFAULT_TLOW,
TMP102Component,
build_configuration,
encode_temperature,
validate_tmp102_thresholds,
)
CODEOWNERS = ["@timsavage"]
DEPENDENCIES = ["i2c"]
tmp102_ns = cg.esphome_ns.namespace("tmp102")
TMP102Component = tmp102_ns.class_(
"TMP102Component", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor
)
# No defaults here: absence of all advanced options preserves upstream's read-only behavior and firmware size.
_ADVANCED_SCHEMA = {
cv.Optional(CONF_EXTENDED_MODE): cv.boolean,
cv.Optional(CONF_CONVERSION_RATE): cv.enum(CONVERSION_RATES),
cv.Optional(CONF_ONE_SHOT_MODE): cv.boolean,
cv.Optional(CONF_ALERT_POLARITY): cv.enum(ALERT_POLARITIES),
cv.Optional(CONF_THERMOSTAT_MODE): cv.enum(THERMOSTAT_MODES),
cv.Optional(CONF_FAULT_QUEUE): cv.enum(FAULT_QUEUE_BITS, int=True),
cv.Optional(CONF_TEMPERATURE_HIGH): cv.temperature,
cv.Optional(CONF_TEMPERATURE_LOW): cv.temperature,
}
CONFIG_SCHEMA = (
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
TMP102Component,
unit_of_measurement=UNIT_CELSIUS,
@@ -36,6 +56,8 @@ CONFIG_SCHEMA = (
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x48))
.extend(_ADVANCED_SCHEMA),
validate_tmp102_thresholds,
)
@@ -43,3 +65,18 @@ async def to_code(config: ConfigType) -> None:
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if not any(key in config for key in _ADVANCED_SCHEMA):
return
extended = config.get(CONF_EXTENDED_MODE, False)
high = config.get(CONF_TEMPERATURE_HIGH, TMP102_DEFAULT_THIGH)
low = config.get(CONF_TEMPERATURE_LOW, TMP102_DEFAULT_TLOW)
cg.add_define("USE_TMP102_CONFIGURE")
cg.add(
var.set_configuration(
build_configuration(config),
encode_temperature(high, extended),
encode_temperature(low, extended),
)
)
+132 -6
View File
@@ -8,13 +8,33 @@ ESPHOME_LOG_TAG(TAG, "tmp102");
static constexpr uint32_t READ_TEMP_TIMEOUT_ID = 0;
static const uint8_t TMP102_ADDRESS = 0x48;
static const uint8_t TMP102_REGISTER_TEMPERATURE = 0x00;
static const uint8_t TMP102_REGISTER_CONFIGURATION = 0x01;
static const uint8_t TMP102_REGISTER_LOW_LIMIT = 0x02;
static const uint8_t TMP102_REGISTER_HIGH_LIMIT = 0x03;
static constexpr uint8_t TMP102_REGISTER_TEMPERATURE = 0x00;
static constexpr float TMP102_CONVERSION_FACTOR = 0.0625f;
#ifdef USE_TMP102_CONFIGURE
static constexpr uint8_t TMP102_REGISTER_CONFIGURATION = 0x01;
static constexpr uint8_t TMP102_REGISTER_LOW_LIMIT = 0x02;
static constexpr uint8_t TMP102_REGISTER_HIGH_LIMIT = 0x03;
static const float TMP102_CONVERSION_FACTOR = 0.0625;
static constexpr uint16_t TMP102_CONFIGURATION_MASK = 0x1FD0;
static constexpr uint16_t TMP102_RESET_CONFIGURATION = 0x60A0;
static constexpr uint16_t TMP102_ONE_SHOT_BIT = 0x8000;
static constexpr uint16_t TMP102_SHUTDOWN_BIT = 0x0100;
static constexpr uint16_t TMP102_THERMOSTAT_MODE_BIT = 0x0200;
static constexpr uint16_t TMP102_ALERT_POLARITY_BIT = 0x0400;
static constexpr uint16_t TMP102_EXTENDED_MODE_BIT = 0x0010;
static constexpr uint32_t TMP102_ONESHOT_DELAY_MS = 40;
static float decode_temperature(uint16_t raw, bool extended) {
return (static_cast<int16_t>(raw) >> (extended ? 3 : 4)) * TMP102_CONVERSION_FACTOR;
}
void TMP102Component::setup() {
if (!this->configure_)
return;
if (!this->write_configuration_())
this->mark_failed();
}
#endif
void TMP102Component::dump_config() {
ESP_LOGCONFIG(TAG, "TMP102:");
@@ -24,9 +44,44 @@ void TMP102Component::dump_config() {
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this);
#ifdef USE_TMP102_CONFIGURE
if (!this->configure_)
return;
const uint8_t rate = (this->config_ >> 6) & 0x03;
const LogString *rate_str = rate == 0 ? LOG_STR("0.25Hz")
: rate == 1 ? LOG_STR("1Hz")
: rate == 2 ? LOG_STR("4Hz")
: LOG_STR("8Hz");
const uint8_t fault_bits = (this->config_ >> 11) & 0x03;
const bool extended = this->config_ & TMP102_EXTENDED_MODE_BIT;
ESP_LOGCONFIG(
TAG,
" Extended Mode: %s\n"
" One-Shot Mode: %s\n"
" Conversion Rate: %s\n"
" Thermostat Mode: %s\n"
" Alert Polarity: %s\n"
" Fault Queue: %u\n"
" Temperature High: %.4f°C\n"
" Temperature Low: %.4f°C",
YESNO(extended), YESNO(this->config_ & TMP102_SHUTDOWN_BIT), LOG_STR_ARG(rate_str),
this->config_ & TMP102_THERMOSTAT_MODE_BIT ? LOG_STR_LITERAL("Interrupt") : LOG_STR_LITERAL("Comparator"),
this->config_ & TMP102_ALERT_POLARITY_BIT ? LOG_STR_LITERAL("Active High") : LOG_STR_LITERAL("Active Low"),
fault_bits == 3 ? 6 : 1U << fault_bits, decode_temperature(this->high_limit_, extended),
decode_temperature(this->low_limit_, extended));
if (this->config_ & TMP102_THERMOSTAT_MODE_BIT) {
ESP_LOGW(TAG, "Register reads clear the interrupt-mode ALERT pin");
}
#endif
}
void TMP102Component::update() {
#ifdef USE_TMP102_CONFIGURE
if (this->configure_) {
this->update_configured_();
return;
}
#endif
if (this->write(&TMP102_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
@@ -47,4 +102,75 @@ void TMP102Component::update() {
});
}
#ifdef USE_TMP102_CONFIGURE
void TMP102Component::update_configured_() {
if (!this->check_configuration_())
return;
if (this->config_ & TMP102_SHUTDOWN_BIT) {
if (!this->write_register_(TMP102_REGISTER_CONFIGURATION, this->config_ | TMP102_ONE_SHOT_BIT)) {
this->status_set_warning();
return;
}
this->set_timeout(READ_TEMP_TIMEOUT_ID, TMP102_ONESHOT_DELAY_MS, [this]() { this->read_temperature_(); });
} else {
this->read_temperature_();
}
}
bool TMP102Component::write_register_(uint8_t reg, uint16_t value) {
uint8_t frame[3] = {reg, static_cast<uint8_t>(value >> 8), static_cast<uint8_t>(value)};
if (this->write(frame, 3) == i2c::ERROR_OK)
return true;
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
return false;
}
bool TMP102Component::write_configuration_() {
return this->write_register_(TMP102_REGISTER_CONFIGURATION, this->config_) &&
this->write_register_(TMP102_REGISTER_HIGH_LIMIT, this->high_limit_) &&
this->write_register_(TMP102_REGISTER_LOW_LIMIT, this->low_limit_);
}
bool TMP102Component::check_configuration_() {
uint16_t config;
if (!this->read_byte_16(TMP102_REGISTER_CONFIGURATION, &config)) {
this->status_set_warning();
return false;
}
bool matches = (config & TMP102_CONFIGURATION_MASK) == (this->config_ & TMP102_CONFIGURATION_MASK);
// A chip reset is invisible in the configuration register when it matches the reset default; check the limits.
if (matches &&
(this->config_ & TMP102_CONFIGURATION_MASK) == (TMP102_RESET_CONFIGURATION & TMP102_CONFIGURATION_MASK)) {
uint16_t high, low;
if (!this->read_byte_16(TMP102_REGISTER_HIGH_LIMIT, &high) ||
!this->read_byte_16(TMP102_REGISTER_LOW_LIMIT, &low)) {
this->status_set_warning();
return false;
}
matches = high == this->high_limit_ && low == this->low_limit_;
}
if (matches)
return true;
ESP_LOGW(TAG, "Configuration changed; restoring configured settings and thresholds");
this->status_set_warning();
if (!this->write_configuration_()) {
ESP_LOGW(TAG, "Failed to restore configuration and thresholds");
}
return false;
}
void TMP102Component::read_temperature_() {
uint16_t raw;
if (!this->read_byte_16(TMP102_REGISTER_TEMPERATURE, &raw)) {
this->status_set_warning();
return;
}
const float temperature = decode_temperature(raw, raw & 0x0001);
ESP_LOGD(TAG, "Got Temperature=%.2f°C", temperature);
this->publish_state(temperature);
this->status_clear_warning();
}
#endif
} // namespace esphome::tmp102
+27 -1
View File
@@ -1,15 +1,41 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/defines.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include <cstdint>
namespace esphome::tmp102 {
class TMP102Component final : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
public:
#ifdef USE_TMP102_CONFIGURE
void setup() override;
void set_configuration(uint16_t config, uint16_t high_limit, uint16_t low_limit) {
this->config_ = config;
this->high_limit_ = high_limit;
this->low_limit_ = low_limit;
this->configure_ = true;
}
#endif
void dump_config() override;
void update() override;
protected:
#ifdef USE_TMP102_CONFIGURE
bool write_register_(uint8_t reg, uint16_t value);
bool write_configuration_();
bool check_configuration_();
void read_temperature_();
void update_configured_();
uint16_t config_{0};
uint16_t high_limit_{0};
uint16_t low_limit_{0};
bool configure_{false};
#endif
};
} // namespace esphome::tmp102
+1
View File
@@ -243,6 +243,7 @@
#define USE_TEXT_SENSOR_FILTER
#define USE_TIME
#define USE_TIME_TRIGGERS
#define USE_TMP102_CONFIGURE
#define USE_TOUCHSCREEN
#define USE_UART_DEBUGGER
#define USE_UART_WAKE_LOOP_ON_RX
+1
View File
@@ -0,0 +1 @@
"""TMP102 regression tests."""
@@ -0,0 +1,23 @@
---
esphome:
name: tmp102-test
esp32:
board: esp32dev
i2c:
sda: GPIO21
scl: GPIO22
sensor:
- platform: tmp102
id: chip
name: Temperature
extended_mode: false
conversion_rate: 1Hz
one_shot_mode: false
alert_polarity: active_high
thermostat_mode: interrupt
fault_queue: 2
temperature_high: 30
temperature_low: 25
@@ -0,0 +1,15 @@
---
esphome:
name: tmp102-test
esp32:
board: esp32dev
i2c:
sda: GPIO21
scl: GPIO22
sensor:
- platform: tmp102
id: chip
name: Temperature
@@ -0,0 +1,95 @@
"""Exercise TMP102 sensor validation and code generation."""
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.components import tmp102 as component
from esphome.components.tmp102.sensor import CONFIG_SCHEMA
import esphome.config_validation as cv
from esphome.core import CORE
def test_basic_unchanged(
generate_main: Callable[[Path], str],
component_config_path: Callable[[str], Path],
) -> None:
source = generate_main(component_config_path("basic.yaml"))
assert "set_configuration" not in source
defines = " ".join(define.name for define in CORE.defines)
assert "USE_TMP102_" not in defines
def test_advanced_options_enable_configuration_once(
generate_main: Callable[[Path], str],
component_config_path: Callable[[str], Path],
) -> None:
source = generate_main(component_config_path("advanced.yaml"))
assert source.count("set_configuration(") == 1
assert "set_configuration(3648, 7680, 6400)" in source
defines = " ".join(define.name for define in CORE.defines)
assert "USE_TMP102_CONFIGURE" in defines
@pytest.mark.parametrize(
("extended", "high", "low"),
[
(False, 127.9375, -55),
(True, 150, -55),
(False, 30, 30),
],
)
def test_valid_limits(extended: bool, high: float, low: float) -> None:
validated = CONFIG_SCHEMA(
{
"name": "Temperature",
"extended_mode": extended,
"temperature_high": high,
"temperature_low": low,
}
)
assert validated["temperature_high"] == high
assert validated["temperature_low"] == low
@pytest.mark.parametrize(
"config",
[
{"temperature_high": float("nan")},
{"temperature_low": float("inf")},
{"temperature_low": -55.0625},
{"temperature_high": 128},
{"extended_mode": True, "temperature_high": 150.0625},
{"temperature_high": 30, "temperature_low": 31},
{"temperature_high": 74},
{"temperature_low": 81},
{"fault_queue": 3},
],
)
def test_invalid_limits(config: dict) -> None:
with pytest.raises(cv.Invalid):
CONFIG_SCHEMA({"name": "Temperature", **config})
def test_precomputed_register_values() -> None:
config = CONFIG_SCHEMA(
{
"name": "Temperature",
"extended_mode": True,
"conversion_rate": "0.25Hz",
"one_shot_mode": True,
"alert_polarity": "active_high",
"thermostat_mode": "interrupt",
"fault_queue": 6,
"temperature_high": 12.22,
"temperature_low": -12.22,
}
)
assert component.build_configuration(config) == 0x1F10
assert component.encode_temperature(config["temperature_high"], True) == 0x0620
assert component.encode_temperature(config["temperature_low"], True) == 0xF9E0
def test_defaults_match_chip_reset() -> None:
assert component.build_configuration({}) == 0x0080
+13
View File
@@ -0,0 +1,13 @@
import esphome.codegen as cg
from esphome.core import CORE
from esphome.types import ConfigType
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
async def to_code_testing(config: ConfigType) -> None:
CORE.testing_ensure_platform_registered("sensor")
cg.add_define("USE_TMP102_CONFIGURE")
manifest.to_code = to_code_testing
manifest.dependencies = manifest.dependencies + ["i2c", "sensor"]
+13
View File
@@ -2,3 +2,16 @@ sensor:
- platform: tmp102
i2c_id: i2c_bus
name: TMP102 Temperature
- platform: tmp102
id: tmp102_extended_temperature
name: TMP102 Extended Temperature
i2c_id: i2c_bus
address: 0x49
extended_mode: true
one_shot_mode: true
conversion_rate: 8Hz
alert_polarity: active_high
thermostat_mode: interrupt
fault_queue: 6
temperature_high: 140°C
temperature_low: 120°C
+1 -2
View File
@@ -1,4 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
<<: !include common.yaml
tmp102: !include common.yaml
@@ -1,4 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
<<: !include common.yaml
tmp102: !include common.yaml
+1 -2
View File
@@ -1,4 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
<<: !include common.yaml
tmp102: !include common.yaml
+228
View File
@@ -0,0 +1,228 @@
#ifdef USE_HOST
#include <gtest/gtest.h>
#include <array>
#include "esphome/components/tmp102/tmp102.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
namespace esphome::tmp102::testing {
class RegisterBus final : public i2c::I2CBus {
public:
std::array<uint16_t, 4> registers{0x1900, 0x60A0, 0x4B00, 0x5000};
int fail_read{-1};
int fail_write{-1};
int fail_select{-1};
unsigned transactions{0};
unsigned writes{0};
uint8_t pointer{0};
i2c::ErrorCode write_readv(uint8_t address, const uint8_t *data, size_t size, uint8_t *out,
size_t out_size) override {
++this->transactions;
if (size != 0) {
if (data[0] >= this->registers.size() || data[0] == this->fail_select)
return i2c::ERROR_NOT_ACKNOWLEDGED;
this->pointer = data[0];
if (size == 3) {
if (this->pointer == this->fail_write)
return i2c::ERROR_NOT_ACKNOWLEDGED;
++this->writes;
this->registers[this->pointer] = (uint16_t(data[1]) << 8) | data[2];
}
}
if (out_size != 0) {
if (out_size != 2 || this->pointer == this->fail_read)
return i2c::ERROR_NOT_ACKNOWLEDGED;
out[0] = this->registers[this->pointer] >> 8;
out[1] = this->registers[this->pointer];
}
return i2c::ERROR_OK;
}
};
class TMP102Test : public ::testing::Test {
protected:
RegisterBus bus_;
TMP102Component chip_;
unsigned publications_{0};
void SetUp() override {
new (&App) Application;
char name[] = "tmp102-regression-tests";
char friendly_name[] = "TMP102 tests";
App.pre_setup(name, 23, friendly_name, 12);
this->chip_.set_i2c_bus(&this->bus_);
this->chip_.set_i2c_address(0x48);
this->chip_.add_on_state_callback([this](float) { ++this->publications_; });
}
void TearDown() override {
App.scheduler.cancel_timeout(&this->chip_, uint32_t{0});
App.scheduler.call(millis());
App.~Application();
}
static void run_timers(uint32_t duration) {
App.scheduler.call(millis());
delay(duration);
App.scheduler.call(millis());
}
void configure(uint16_t config = 0x0080, uint16_t high = 0x5000, uint16_t low = 0x4B00) {
this->chip_.set_configuration(config, high, low);
}
};
TEST_F(TMP102Test, LegacySetupAndContinuousReadDoNotWriteRegisters) {
const auto original = this->bus_.registers;
this->chip_.setup();
EXPECT_EQ(this->bus_.transactions, 0);
this->chip_.update();
run_timers(55);
EXPECT_FLOAT_EQ(this->chip_.state, 25);
EXPECT_EQ(this->publications_, 1);
EXPECT_EQ(this->bus_.writes, 0);
EXPECT_EQ(this->bus_.registers, original);
}
TEST_F(TMP102Test, SampleFormatOverridesConfiguredModeInBothDirections) {
for (bool configured_extended : {false, true}) {
this->configure(configured_extended ? 0x0010 : 0, configured_extended ? 0x2800 : 0x5000,
configured_extended ? 0x2580 : 0x4B00);
this->chip_.setup();
const auto writes = this->bus_.writes;
for (auto raw : {0x1900, 0x0C81, 0xFF00, 0xFF81}) {
this->bus_.registers[0] = raw;
this->chip_.update();
EXPECT_FLOAT_EQ(this->chip_.state, raw & 0x8000 ? -1 : 25);
EXPECT_FALSE(this->chip_.status_has_warning());
EXPECT_EQ(this->bus_.writes, writes);
}
}
}
TEST_F(TMP102Test, WritableConfigurationFields) {
this->configure(0x1FD0, 0x2800, 0x2580);
this->chip_.setup();
EXPECT_EQ(this->bus_.registers[1], 0x1FD0);
EXPECT_EQ(this->bus_.registers[3], 0x2800);
EXPECT_EQ(this->bus_.registers[2], 0x2580);
}
TEST_F(TMP102Test, RestoresRegistersAfterChipOnlyResetBeforePublishing) {
this->configure(0x0090, 0x2D00, 0x2A80);
this->chip_.setup();
const auto configured = this->bus_.registers;
this->chip_.update();
this->bus_.registers = {0x1900, 0x60A0, 0x4B00, 0x5000};
this->chip_.update();
EXPECT_EQ(this->publications_, 1);
EXPECT_TRUE(this->chip_.status_has_warning());
for (size_t reg = 1; reg < 4; ++reg)
EXPECT_EQ(this->bus_.registers[reg], configured[reg]);
this->chip_.update();
EXPECT_EQ(this->publications_, 2);
EXPECT_FLOAT_EQ(this->chip_.state, 25);
EXPECT_FALSE(this->chip_.status_has_warning());
}
TEST_F(TMP102Test, RecoveryRetriesFailedWritesAndDetectsThresholdOnlyChange) {
this->configure();
this->chip_.setup();
this->bus_.registers[3] = 0;
this->bus_.fail_write = 3;
this->chip_.update();
EXPECT_EQ(this->publications_, 0);
this->bus_.fail_write = -1;
this->chip_.update();
EXPECT_EQ(this->bus_.registers[3], 0x5000);
this->chip_.update();
EXPECT_EQ(this->publications_, 1);
}
TEST_F(TMP102Test, ConfigurationReadFailuresDoNotPublishOrOverwriteRegisters) {
this->configure();
this->chip_.setup();
for (int reg : {1, 2, 3}) {
const auto publications = this->publications_;
const auto writes = this->bus_.writes;
this->bus_.fail_read = reg;
this->chip_.update();
EXPECT_TRUE(this->chip_.status_has_warning());
EXPECT_EQ(this->publications_, publications);
EXPECT_EQ(this->bus_.writes, writes);
this->bus_.fail_read = -1;
}
}
TEST_F(TMP102Test, ReadOnlyConfigurationBitsDoNotTriggerRecovery) {
this->configure();
this->chip_.setup();
const auto writes = this->bus_.writes;
this->bus_.registers[1] ^= 0xE020;
this->chip_.update();
EXPECT_EQ(this->publications_, 1);
EXPECT_EQ(this->bus_.writes, writes);
EXPECT_FALSE(this->chip_.status_has_warning());
}
TEST_F(TMP102Test, OneShotTriggerFailureSetsWarning) {
this->configure(0x0180);
this->chip_.setup();
this->bus_.fail_write = 1;
this->chip_.update();
run_timers(45);
EXPECT_EQ(this->publications_, 0);
EXPECT_TRUE(this->chip_.status_has_warning());
}
TEST_F(TMP102Test, OneShotRestartsPendingReadAndReselectsTemperature) {
this->configure(0x0180);
this->chip_.setup();
this->chip_.update();
this->chip_.update();
EXPECT_EQ(this->publications_, 0);
this->bus_.pointer = 3;
run_timers(45);
EXPECT_EQ(this->publications_, 1);
EXPECT_FLOAT_EQ(this->chip_.state, 25);
}
TEST_F(TMP102Test, LimitsAreOnlyCheckedWhenConfigurationMatchesResetDefault) {
this->configure();
this->chip_.setup();
this->bus_.registers[2] = 0;
this->chip_.update();
EXPECT_EQ(this->publications_, 0);
EXPECT_TRUE(this->chip_.status_has_warning());
EXPECT_EQ(this->bus_.registers[2], 0x4B00);
this->configure(0x00C0);
this->chip_.setup();
this->bus_.registers[2] = 0;
const auto transactions = this->bus_.transactions;
const auto writes = this->bus_.writes;
this->chip_.update();
EXPECT_EQ(this->bus_.transactions - transactions, 2);
EXPECT_EQ(this->bus_.writes, writes);
EXPECT_EQ(this->publications_, 1);
}
TEST_F(TMP102Test, FailureOfAnySetupWriteMarksComponentFailed) {
for (int reg : {1, 2, 3}) {
TMP102Component failed;
failed.set_i2c_bus(&this->bus_);
failed.set_i2c_address(0x48);
failed.set_configuration(0x0080, 0x5000, 0x4B00);
this->bus_.fail_write = reg;
failed.setup();
EXPECT_TRUE(failed.is_failed());
}
}
} // namespace esphome::tmp102::testing
#endif