mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
[tmp102] Add text sensor for diagnostics (#20629)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
co-authored by
J. Nick Koston
parent
40e12cd0d8
commit
5718e4b779
@@ -0,0 +1,31 @@
|
||||
"""TMP102 threshold status entity."""
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import CONF_TMP102_ID, TMP102Component, validate_unique_child
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(CONF_TMP102_ID): cv.use_id(TMP102Component),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return validate_unique_child(config, "text_sensor")
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_TMP102_TEXT_SENSOR")
|
||||
parent = await cg.get_variable(config[CONF_TMP102_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
cg.add(parent.set_threshold_status_text_sensor(var))
|
||||
@@ -1,8 +1,14 @@
|
||||
#include "tmp102.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
#include "esphome/core/progmem.h"
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
namespace esphome::tmp102 {
|
||||
|
||||
@@ -12,6 +18,30 @@ static constexpr uint32_t READ_TEMP_TIMEOUT_ID = 0;
|
||||
|
||||
static constexpr uint8_t TMP102_REGISTER_TEMPERATURE = 0x00;
|
||||
static constexpr float TMP102_CONVERSION_FACTOR = 0.0625f;
|
||||
|
||||
enum TMP102ThresholdStatus : uint8_t {
|
||||
TMP102_THRESHOLD_STATUS_NOT_CONFIGURED,
|
||||
TMP102_THRESHOLD_STATUS_SETUP_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_OK,
|
||||
TMP102_THRESHOLD_STATUS_RESTORE_RESET,
|
||||
TMP102_THRESHOLD_STATUS_RESTORE_RESET_SAVE_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_PREFERENCE_SAVE_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_RECOVERY_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_RECOVERED,
|
||||
TMP102_THRESHOLD_STATUS_WRITE_HIGH_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_WRITE_LOW_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_SENSOR_SETUP_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_COUNT,
|
||||
};
|
||||
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
PROGMEM_STRING_TABLE(ThresholdStatusStrings, "Not configured", "Setup failed", "OK", "Restored thresholds reset",
|
||||
"Restore reset; save failed", "Preference save failed", "Configuration recovery failed",
|
||||
"Configuration restored", "Write failed: high threshold", "Write failed: low threshold",
|
||||
"Rejected: sensor setup failed");
|
||||
static_assert(ThresholdStatusStrings::COUNT == TMP102_THRESHOLD_STATUS_COUNT);
|
||||
#endif
|
||||
|
||||
#if defined(USE_TMP102_CONFIGURE) || defined(USE_TMP102_BINARY_SENSOR)
|
||||
static constexpr uint8_t TMP102_REGISTER_CONFIGURATION = 0x01;
|
||||
static constexpr uint16_t TMP102_ALERT_BIT = 0x0020;
|
||||
@@ -42,15 +72,30 @@ static uint16_t encode_temperature(float temperature, bool extended) {
|
||||
return static_cast<uint16_t>(static_cast<uint32_t>(static_cast<uint16_t>(steps)) << (extended ? 3 : 4));
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(USE_TMP102_CONFIGURE) || defined(USE_TMP102_TEXT_SENSOR)
|
||||
void TMP102Component::setup() {
|
||||
if (!this->configure_)
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
if (this->configure_) {
|
||||
this->setup_configured_();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_NOT_CONFIGURED);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
void TMP102Component::setup_configured_() {
|
||||
bool corrected = false;
|
||||
#ifdef USE_TMP102_NUMBER
|
||||
float restored[2]{};
|
||||
this->prepare_number_limits_(restored);
|
||||
bool restore_corrected[2]{};
|
||||
corrected = this->prepare_number_limits_(restored, restore_corrected);
|
||||
#endif
|
||||
if (!this->write_configuration_()) {
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_SETUP_FAILED);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -61,14 +106,17 @@ void TMP102Component::setup() {
|
||||
if (control == nullptr)
|
||||
continue;
|
||||
const float value = this->get_limit_temperature_(static_cast<TMP102LimitType>(limit));
|
||||
if (value != restored[limit])
|
||||
if (restore_corrected[limit] || value != restored[limit])
|
||||
saved &= control->save_value_(value);
|
||||
control->publish_state(value);
|
||||
}
|
||||
if (!saved) {
|
||||
ESP_LOGW(TAG, "Failed to save threshold preferences");
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_RESTORE_RESET_SAVE_FAILED);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
this->publish_threshold_status_(corrected ? TMP102_THRESHOLD_STATUS_RESTORE_RESET : TMP102_THRESHOLD_STATUS_OK);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -83,6 +131,9 @@ void TMP102Component::dump_config() {
|
||||
#ifdef USE_TMP102_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Alert", this->alert_binary_sensor_);
|
||||
#endif
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR(" ", "Threshold Status", this->threshold_status_text_sensor_);
|
||||
#endif
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
if (!this->configure_)
|
||||
return;
|
||||
@@ -203,6 +254,9 @@ bool TMP102Component::check_configuration_() {
|
||||
this->set_warning_and_invalidate_alert_();
|
||||
if (!this->write_configuration_()) {
|
||||
ESP_LOGW(TAG, "Failed to restore configuration and thresholds");
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_RECOVERY_FAILED);
|
||||
} else {
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_RECOVERED);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -228,22 +282,25 @@ void TMP102Component::set_limit_control(TMP102LimitNumber *number) {
|
||||
this->limit_controls_[number->limit_type_] = number;
|
||||
}
|
||||
|
||||
void TMP102Component::prepare_number_limits_(float *restored) {
|
||||
bool TMP102Component::prepare_number_limits_(float *restored, bool *restore_corrected) {
|
||||
const bool extended = this->config_ & TMP102_EXTENDED_MODE_BIT;
|
||||
uint16_t raw[2] = {this->low_limit_, this->high_limit_};
|
||||
bool corrected = false;
|
||||
for (uint8_t limit = 0; limit < 2; ++limit) {
|
||||
TMP102LimitNumber *control = this->limit_controls_[limit];
|
||||
if (control == nullptr)
|
||||
continue;
|
||||
restored[limit] = control->load_value_(decode_temperature(raw[limit], extended));
|
||||
restored[limit] = control->load_value_(decode_temperature(raw[limit], extended), restore_corrected[limit]);
|
||||
corrected |= restore_corrected[limit];
|
||||
raw[limit] = encode_temperature(restored[limit], extended);
|
||||
}
|
||||
if (static_cast<int16_t>(raw[TMP102_LIMIT_LOW]) > static_cast<int16_t>(raw[TMP102_LIMIT_HIGH])) {
|
||||
ESP_LOGW(TAG, "Restored thresholds are inverted; using configured values");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
this->low_limit_ = raw[TMP102_LIMIT_LOW];
|
||||
this->high_limit_ = raw[TMP102_LIMIT_HIGH];
|
||||
return corrected;
|
||||
}
|
||||
|
||||
float TMP102Component::get_limit_temperature_(TMP102LimitType limit) const {
|
||||
@@ -261,10 +318,13 @@ bool TMP102Component::set_limit_temperature_(TMP102LimitType limit, float temper
|
||||
ESP_LOGW(TAG, "Rejected %s threshold %.4f°C: crosses %s threshold %.4f°C",
|
||||
high ? LOG_STR_LITERAL("high") : LOG_STR_LITERAL("low"), decode_temperature(raw, extended),
|
||||
high ? LOG_STR_LITERAL("low") : LOG_STR_LITERAL("high"), decode_temperature(other, extended));
|
||||
this->publish_threshold_rejection_(high, decode_temperature(raw, extended), decode_temperature(other, extended));
|
||||
return false;
|
||||
}
|
||||
if (!this->write_register_(static_cast<uint8_t>(TMP102_REGISTER_LOW_LIMIT + limit), raw)) {
|
||||
this->status_set_warning();
|
||||
this->publish_threshold_status_(high ? TMP102_THRESHOLD_STATUS_WRITE_HIGH_FAILED
|
||||
: TMP102_THRESHOLD_STATUS_WRITE_LOW_FAILED);
|
||||
return false;
|
||||
}
|
||||
(high ? this->high_limit_ : this->low_limit_) = raw;
|
||||
@@ -273,6 +333,32 @@ bool TMP102Component::set_limit_temperature_(TMP102LimitType limit, float temper
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
void TMP102Component::publish_threshold_status_(uint8_t status) {
|
||||
if (this->threshold_status_text_sensor_ == nullptr)
|
||||
return;
|
||||
char text[40];
|
||||
ESPHOME_strncpy_P(
|
||||
text,
|
||||
reinterpret_cast<ESPHOME_PGM_P>(ThresholdStatusStrings::get_progmem_str(status, TMP102_THRESHOLD_STATUS_OK)),
|
||||
sizeof(text));
|
||||
text[sizeof(text) - 1] = '\0';
|
||||
this->threshold_status_text_sensor_->publish_state(text);
|
||||
}
|
||||
|
||||
void TMP102Component::publish_threshold_rejection_(bool high, float value, float other) {
|
||||
if (this->threshold_status_text_sensor_ == nullptr)
|
||||
return;
|
||||
char text[96];
|
||||
if (high) {
|
||||
ESPHOME_snprintf_P(text, sizeof(text), ESPHOME_PSTR("Rejected: high %.4f°C crosses low %.4f°C"), value, other);
|
||||
} else {
|
||||
ESPHOME_snprintf_P(text, sizeof(text), ESPHOME_PSTR("Rejected: low %.4f°C crosses high %.4f°C"), value, other);
|
||||
}
|
||||
this->threshold_status_text_sensor_->publish_state(text);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_BINARY_SENSOR
|
||||
void TMP102Component::invalidate_alert_() {
|
||||
if (this->alert_binary_sensor_ != nullptr)
|
||||
@@ -294,7 +380,7 @@ bool TMP102Component::read_alert_state_() {
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_NUMBER
|
||||
float TMP102LimitNumber::load_value_(float fallback) {
|
||||
float TMP102LimitNumber::load_value_(float fallback, bool &corrected) {
|
||||
float value = fallback;
|
||||
if (this->restore_value_) {
|
||||
this->pref_ = this->make_entity_preference<float>();
|
||||
@@ -303,6 +389,7 @@ float TMP102LimitNumber::load_value_(float fallback) {
|
||||
if (!std::isfinite(value) || value < this->traits.get_min_value() || value > this->traits.get_max_value()) {
|
||||
ESP_LOGW(TAG, "Invalid restored threshold; using configured value");
|
||||
value = fallback;
|
||||
corrected = true;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -313,6 +400,7 @@ void TMP102LimitNumber::control(float value) {
|
||||
// A chip that failed setup holds nothing the entity could show
|
||||
if (this->parent_->is_failed()) {
|
||||
ESP_LOGW(TAG, "Rejected threshold change: sensor setup failed");
|
||||
this->parent_->publish_threshold_status_(TMP102_THRESHOLD_STATUS_SENSOR_SETUP_FAILED);
|
||||
return;
|
||||
}
|
||||
const bool accepted = this->parent_->set_limit_temperature_(this->limit_type_, value);
|
||||
@@ -320,6 +408,9 @@ void TMP102LimitNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
if (accepted && !this->save_value_(value)) {
|
||||
ESP_LOGW(TAG, "Failed to save threshold preference");
|
||||
this->parent_->publish_threshold_status_(TMP102_THRESHOLD_STATUS_PREFERENCE_SAVE_FAILED);
|
||||
} else if (accepted) {
|
||||
this->parent_->publish_threshold_status_(TMP102_THRESHOLD_STATUS_OK);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -12,11 +12,20 @@
|
||||
#include "esphome/core/preferences.h"
|
||||
#endif
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome::tmp102 {
|
||||
|
||||
#ifdef USE_HOST
|
||||
namespace testing {
|
||||
class TMP102Test;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_NUMBER
|
||||
enum TMP102LimitType : uint8_t {
|
||||
TMP102_LIMIT_LOW = 0,
|
||||
@@ -31,8 +40,13 @@ class TMP102Component final : public PollingComponent, public i2c::I2CDevice, pu
|
||||
#ifdef USE_TMP102_BINARY_SENSOR
|
||||
SUB_BINARY_SENSOR(alert)
|
||||
#endif
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
SUB_TEXT_SENSOR(threshold_status)
|
||||
#endif
|
||||
#if defined(USE_TMP102_CONFIGURE) || defined(USE_TMP102_TEXT_SENSOR)
|
||||
void setup() override;
|
||||
#endif
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
void set_configuration(uint16_t config, uint16_t high_limit, uint16_t low_limit) {
|
||||
this->config_ = config;
|
||||
this->high_limit_ = high_limit;
|
||||
@@ -47,6 +61,13 @@ class TMP102Component final : public PollingComponent, public i2c::I2CDevice, pu
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
void publish_threshold_status_(uint8_t status);
|
||||
void publish_threshold_rejection_(bool high, float value, float other);
|
||||
#else
|
||||
void publish_threshold_status_(uint8_t) {}
|
||||
void publish_threshold_rejection_(bool, float, float) {}
|
||||
#endif
|
||||
void set_warning_and_invalidate_alert_() {
|
||||
this->status_set_warning();
|
||||
#ifdef USE_TMP102_BINARY_SENSOR
|
||||
@@ -58,6 +79,7 @@ class TMP102Component final : public PollingComponent, public i2c::I2CDevice, pu
|
||||
void invalidate_alert_();
|
||||
#endif
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
void setup_configured_();
|
||||
bool write_register_(uint8_t reg, uint16_t value);
|
||||
bool write_configuration_();
|
||||
bool check_configuration_();
|
||||
@@ -67,11 +89,10 @@ class TMP102Component final : public PollingComponent, public i2c::I2CDevice, pu
|
||||
friend class TMP102LimitNumber;
|
||||
bool set_limit_temperature_(TMP102LimitType limit, float temperature);
|
||||
float get_limit_temperature_(TMP102LimitType limit) const;
|
||||
void prepare_number_limits_(float *restored);
|
||||
bool prepare_number_limits_(float *restored, bool *restore_corrected);
|
||||
|
||||
TMP102LimitNumber *limit_controls_[2]{};
|
||||
#endif
|
||||
|
||||
uint16_t config_{0};
|
||||
uint16_t high_limit_{0};
|
||||
uint16_t low_limit_{0};
|
||||
@@ -87,7 +108,10 @@ class TMP102LimitNumber final : public number::Number, public Parented<TMP102Com
|
||||
|
||||
protected:
|
||||
friend class TMP102Component;
|
||||
float load_value_(float fallback);
|
||||
#ifdef USE_HOST
|
||||
friend class testing::TMP102Test;
|
||||
#endif
|
||||
float load_value_(float fallback, bool &corrected);
|
||||
bool save_value_(float value);
|
||||
void control(float value) override;
|
||||
|
||||
|
||||
@@ -272,6 +272,7 @@
|
||||
#define USE_TMP102_BINARY_SENSOR
|
||||
#define USE_TMP102_CONFIGURE
|
||||
#define USE_TMP102_NUMBER
|
||||
#define USE_TMP102_TEXT_SENSOR
|
||||
#define USE_TOUCHSCREEN
|
||||
#define USE_UART_DEBUGGER
|
||||
#define USE_UART_WAKE_LOOP_ON_RX
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
esphome:
|
||||
name: tmp102-test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
i2c:
|
||||
sda: GPIO21
|
||||
scl: GPIO22
|
||||
|
||||
sensor:
|
||||
- platform: tmp102
|
||||
id: parent_sensor
|
||||
name: Temperature
|
||||
|
||||
text_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: parent_sensor
|
||||
name: First Status
|
||||
- platform: tmp102
|
||||
tmp102_id: parent_sensor
|
||||
name: Second Status
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
esphome:
|
||||
name: tmp102-test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
i2c:
|
||||
sda: GPIO21
|
||||
scl: GPIO22
|
||||
|
||||
sensor:
|
||||
- platform: tmp102
|
||||
id: parent_sensor
|
||||
name: Temperature
|
||||
|
||||
text_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: parent_sensor
|
||||
name: Threshold Status
|
||||
@@ -58,6 +58,30 @@ def test_duplicate_alert_entities_are_rejected(
|
||||
)
|
||||
|
||||
|
||||
def test_status_entity_is_read_only(
|
||||
generate_main: Callable[[Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
source = generate_main(component_config_path("status.yaml"))
|
||||
assert "set_threshold_status_text_sensor" in source
|
||||
assert "set_configuration" not in source
|
||||
defines = " ".join(define.name for define in CORE.defines)
|
||||
assert "USE_TMP102_TEXT_SENSOR" in defines
|
||||
assert "USE_TMP102_CONFIGURE" not in defines
|
||||
|
||||
|
||||
def test_duplicate_status_entities_are_rejected(
|
||||
component_config_path: Callable[[str], Path],
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
CORE.config_path = component_config_path("duplicate_status.yaml")
|
||||
assert read_config({}) is None
|
||||
assert (
|
||||
"Only one TMP102 text_sensor may reference each temperature sensor"
|
||||
in capsys.readouterr().out
|
||||
)
|
||||
|
||||
|
||||
def test_threshold_numbers_enable_configuration_once(
|
||||
generate_main: Callable[[Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
|
||||
@@ -6,11 +6,12 @@ from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
async def to_code_testing(config: ConfigType) -> None:
|
||||
for platform in ("sensor", "binary_sensor", "number"):
|
||||
for platform in ("sensor", "binary_sensor", "number", "text_sensor"):
|
||||
CORE.testing_ensure_platform_registered(platform)
|
||||
cg.add_define("USE_TMP102_CONFIGURE")
|
||||
cg.add_define("USE_TMP102_BINARY_SENSOR")
|
||||
cg.add_define("USE_TMP102_NUMBER")
|
||||
cg.add_define("USE_TMP102_TEXT_SENSOR")
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
manifest.dependencies = manifest.dependencies + [
|
||||
@@ -18,4 +19,5 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
"sensor",
|
||||
"binary_sensor",
|
||||
"number",
|
||||
"text_sensor",
|
||||
]
|
||||
|
||||
@@ -30,3 +30,8 @@ binary_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: tmp102_extended_temperature
|
||||
name: TMP102 Alert
|
||||
|
||||
text_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: tmp102_extended_temperature
|
||||
name: TMP102 Threshold Status
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
sensor:
|
||||
- platform: tmp102
|
||||
id: tmp102_text_sensor_parent
|
||||
name: TMP102 Temperature
|
||||
|
||||
text_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: tmp102_text_sensor_parent
|
||||
name: TMP102 Threshold Status
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "esphome/components/host/preferences.h"
|
||||
#include "esphome/components/tmp102/tmp102.h"
|
||||
@@ -29,6 +30,7 @@ class RegisterBus final : public i2c::I2CBus {
|
||||
unsigned transactions{0};
|
||||
unsigned writes{0};
|
||||
uint8_t pointer{0};
|
||||
std::function<void()> on_write;
|
||||
|
||||
i2c::ErrorCode write_readv(uint8_t address, const uint8_t *data, size_t size, uint8_t *out,
|
||||
size_t out_size) override {
|
||||
@@ -42,6 +44,10 @@ class RegisterBus final : public i2c::I2CBus {
|
||||
return i2c::ERROR_NOT_ACKNOWLEDGED;
|
||||
++this->writes;
|
||||
this->registers[this->pointer] = (uint16_t(data[1]) << 8) | data[2];
|
||||
if (this->on_write) {
|
||||
auto callback = std::move(this->on_write);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (out_size != 0) {
|
||||
@@ -59,6 +65,7 @@ class TMP102Test : public ::testing::Test {
|
||||
RegisterBus bus_;
|
||||
TMP102Component chip_;
|
||||
binary_sensor::BinarySensor alert_;
|
||||
text_sensor::TextSensor status_;
|
||||
TMP102LimitNumber high_{TMP102_LIMIT_HIGH};
|
||||
TMP102LimitNumber low_{TMP102_LIMIT_LOW};
|
||||
unsigned publications_{0};
|
||||
@@ -122,6 +129,8 @@ class TMP102Test : public ::testing::Test {
|
||||
static void save(TMP102LimitNumber &number, float value) {
|
||||
ASSERT_TRUE(number.make_entity_preference<float>().save(&value));
|
||||
}
|
||||
|
||||
static void fail_saves(TMP102LimitNumber &number) { number.pref_ = {}; }
|
||||
};
|
||||
|
||||
TEST_F(TMP102Test, LegacySetupAndContinuousReadDoNotWriteRegisters) {
|
||||
@@ -136,6 +145,24 @@ TEST_F(TMP102Test, LegacySetupAndContinuousReadDoNotWriteRegisters) {
|
||||
EXPECT_EQ(this->bus_.registers, original);
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, ReadOnlyStatusDoesNotConfigureOrWriteRegisters) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
const auto original = this->bus_.registers;
|
||||
this->chip_.setup();
|
||||
EXPECT_EQ(this->bus_.transactions, 0);
|
||||
EXPECT_EQ(this->bus_.registers, original);
|
||||
ASSERT_TRUE(this->status_.has_state());
|
||||
EXPECT_EQ(this->status_.state, "Not configured");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, ConfiguredSetupPublishesStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->configure();
|
||||
this->chip_.setup();
|
||||
ASSERT_TRUE(this->status_.has_state());
|
||||
EXPECT_EQ(this->status_.state, "OK");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, SampleFormatOverridesConfiguredModeInBothDirections) {
|
||||
for (bool configured_extended : {false, true}) {
|
||||
this->configure(configured_extended ? 0x0010 : 0, configured_extended ? 0x2800 : 0x5000,
|
||||
@@ -408,6 +435,7 @@ TEST_F(TMP102Test, FailureOfAnySetupWriteMarksComponentFailed) {
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, InvalidRestoredPairIsRepairedAfterSuccessfulSetup) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 20);
|
||||
save(this->low_, 35);
|
||||
@@ -416,30 +444,53 @@ TEST_F(TMP102Test, InvalidRestoredPairIsRepairedAfterSuccessfulSetup) {
|
||||
EXPECT_FLOAT_EQ(this->low_.state, 25);
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 30);
|
||||
EXPECT_FLOAT_EQ(saved(this->low_), 25);
|
||||
EXPECT_EQ(this->status_.state, "Restored thresholds reset");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, InvalidRestoredPairAndSaveFailurePublishCombinedStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 20);
|
||||
save(this->low_, 35);
|
||||
this->bus_.on_write = [this]() {
|
||||
fail_saves(this->high_);
|
||||
fail_saves(this->low_);
|
||||
};
|
||||
this->chip_.setup();
|
||||
EXPECT_FLOAT_EQ(this->high_.state, 30);
|
||||
EXPECT_FLOAT_EQ(this->low_.state, 25);
|
||||
EXPECT_EQ(this->status_.state, "Restore reset; save failed");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, InvalidRestoredValuesAreRepaired) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, NAN);
|
||||
save(this->low_, INFINITY);
|
||||
this->chip_.setup();
|
||||
EXPECT_FLOAT_EQ(this->high_.state, 30);
|
||||
EXPECT_FLOAT_EQ(this->low_.state, 25);
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 30);
|
||||
EXPECT_FLOAT_EQ(saved(this->low_), 25);
|
||||
EXPECT_EQ(this->status_.state, "Restored thresholds reset");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, FailedSetupDoesNotOverwritePreferences) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 90);
|
||||
save(this->low_, 85);
|
||||
this->bus_.fail_write = 3;
|
||||
this->chip_.setup();
|
||||
EXPECT_TRUE(this->chip_.is_failed());
|
||||
EXPECT_EQ(this->status_.state, "Setup failed");
|
||||
EXPECT_FALSE(this->high_.has_state());
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 90);
|
||||
EXPECT_FLOAT_EQ(saved(this->low_), 85);
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, FailedSetupRejectsThresholdChanges) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 90);
|
||||
this->bus_.fail_write = 3;
|
||||
@@ -450,6 +501,7 @@ TEST_F(TMP102Test, FailedSetupRejectsThresholdChanges) {
|
||||
EXPECT_FALSE(this->high_.has_state());
|
||||
EXPECT_EQ(this->bus_.writes, writes);
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 90);
|
||||
EXPECT_EQ(this->status_.state, "Rejected: sensor setup failed");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, SetupOnlySavesChangedThresholds) {
|
||||
@@ -464,6 +516,7 @@ TEST_F(TMP102Test, SetupOnlySavesChangedThresholds) {
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, FailedNumberWriteKeepsAcceptedState) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 90);
|
||||
save(this->low_, 85);
|
||||
@@ -475,6 +528,47 @@ TEST_F(TMP102Test, FailedNumberWriteKeepsAcceptedState) {
|
||||
EXPECT_FLOAT_EQ(this->high_.state, 90);
|
||||
EXPECT_EQ(publications, 1);
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 90);
|
||||
EXPECT_EQ(this->status_.state, "Write failed: high threshold");
|
||||
|
||||
this->bus_.fail_write = 2;
|
||||
this->low_.make_call().set_value(80).perform();
|
||||
EXPECT_FLOAT_EQ(this->low_.state, 85);
|
||||
EXPECT_FLOAT_EQ(saved(this->low_), 85);
|
||||
EXPECT_EQ(this->status_.state, "Write failed: low threshold");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, PreferenceSaveFailurePublishesStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
this->chip_.setup();
|
||||
fail_saves(this->high_);
|
||||
this->high_.make_call().set_value(35).perform();
|
||||
EXPECT_FLOAT_EQ(this->high_.state, 35);
|
||||
EXPECT_EQ(this->status_.state, "Preference save failed");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, RejectedNumberWritePublishesStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
this->chip_.setup();
|
||||
this->high_.make_call().set_value(20).perform();
|
||||
EXPECT_EQ(this->status_.state, "Rejected: high 20.0000°C crosses low 25.0000°C");
|
||||
this->high_.make_call().set_value(35).perform();
|
||||
EXPECT_EQ(this->status_.state, "OK");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, ConfigurationRecoveryPublishesStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->configure();
|
||||
this->chip_.setup();
|
||||
this->bus_.registers[1] = 0;
|
||||
this->chip_.update();
|
||||
EXPECT_EQ(this->status_.state, "Configuration restored");
|
||||
|
||||
this->bus_.registers[1] = 0;
|
||||
this->bus_.fail_write = 1;
|
||||
this->chip_.update();
|
||||
EXPECT_EQ(this->status_.state, "Configuration recovery failed");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, RestoreDisabledNeverChangesSavedValue) {
|
||||
|
||||
Reference in New Issue
Block a user