mirror of
https://github.com/esphome/esphome.git
synced 2026-10-20 04:56:48 +00:00
[sensor] Add SensorWithDedup for child sensors that skip repeated values
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome::sensor {
|
||||
|
||||
/// Declare an optional child sensor that skips publishing a value equal to the last one.
|
||||
#define SUB_SENSOR_WITH_DEDUP(name, dedup_type) \
|
||||
protected: \
|
||||
sensor::SensorWithDedup<dedup_type> name##_sensor_{}; \
|
||||
\
|
||||
public: \
|
||||
void set_##name##_sensor(sensor::Sensor *sensor) { this->name##_sensor_.set_sensor(sensor); }
|
||||
|
||||
#define LOG_SENSOR_WITH_DEDUP_SAFE(tag, name, sensor) \
|
||||
if ((sensor).has_sensor()) { \
|
||||
LOG_SENSOR(tag, name, (sensor).get_sensor()); \
|
||||
}
|
||||
|
||||
/// A sensor pointer that may be null plus the last value it published, stored inline.
|
||||
/// Publishing does nothing when no sensor is set, and skips a value equal to the last one.
|
||||
template<typename T> class SensorWithDedup {
|
||||
public:
|
||||
void set_sensor(Sensor *sens) {
|
||||
this->sens_ = sens;
|
||||
this->dedup_ = {};
|
||||
}
|
||||
|
||||
void publish_state_if_not_dup(T state) {
|
||||
if (this->sens_ != nullptr && this->dedup_.next(state)) {
|
||||
this->sens_->publish_state(static_cast<float>(state));
|
||||
}
|
||||
}
|
||||
|
||||
void publish_state_unknown() {
|
||||
if (this->sens_ != nullptr && this->dedup_.next_unknown()) {
|
||||
this->sens_->publish_state(NAN);
|
||||
}
|
||||
}
|
||||
|
||||
bool has_sensor() const { return this->sens_ != nullptr; }
|
||||
Sensor *get_sensor() const { return this->sens_; }
|
||||
|
||||
protected:
|
||||
Sensor *sens_{nullptr};
|
||||
Deduplicator<T> dedup_;
|
||||
};
|
||||
|
||||
} // namespace esphome::sensor
|
||||
@@ -0,0 +1,63 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cmath>
|
||||
|
||||
#include "esphome/components/sensor/sensor_with_dedup.h"
|
||||
|
||||
namespace esphome::sensor::testing {
|
||||
|
||||
class SensorWithDedupTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
this->sensor_.add_on_state_callback([this](float value) { this->published_.push_back(value); });
|
||||
}
|
||||
|
||||
Sensor sensor_;
|
||||
std::vector<float> published_;
|
||||
};
|
||||
|
||||
TEST_F(SensorWithDedupTest, NoSensorDoesNothing) {
|
||||
SensorWithDedup<uint8_t> dedup;
|
||||
EXPECT_FALSE(dedup.has_sensor());
|
||||
EXPECT_EQ(dedup.get_sensor(), nullptr);
|
||||
dedup.publish_state_if_not_dup(5);
|
||||
dedup.publish_state_unknown();
|
||||
EXPECT_TRUE(this->published_.empty());
|
||||
}
|
||||
|
||||
TEST_F(SensorWithDedupTest, SkipsRepeatedValues) {
|
||||
SensorWithDedup<int16_t> dedup;
|
||||
dedup.set_sensor(&this->sensor_);
|
||||
EXPECT_TRUE(dedup.has_sensor());
|
||||
EXPECT_EQ(dedup.get_sensor(), &this->sensor_);
|
||||
dedup.publish_state_if_not_dup(-3);
|
||||
dedup.publish_state_if_not_dup(-3);
|
||||
dedup.publish_state_if_not_dup(7);
|
||||
dedup.publish_state_if_not_dup(7);
|
||||
ASSERT_EQ(this->published_.size(), 2u);
|
||||
EXPECT_EQ(this->published_[0], -3.0f);
|
||||
EXPECT_EQ(this->published_[1], 7.0f);
|
||||
}
|
||||
|
||||
TEST_F(SensorWithDedupTest, UnknownPublishedOnceThenValueAgain) {
|
||||
SensorWithDedup<float> dedup;
|
||||
dedup.set_sensor(&this->sensor_);
|
||||
dedup.publish_state_if_not_dup(1.5f);
|
||||
dedup.publish_state_unknown();
|
||||
dedup.publish_state_unknown();
|
||||
dedup.publish_state_if_not_dup(1.5f);
|
||||
ASSERT_EQ(this->published_.size(), 3u);
|
||||
EXPECT_EQ(this->published_[0], 1.5f);
|
||||
EXPECT_TRUE(std::isnan(this->published_[1]));
|
||||
EXPECT_EQ(this->published_[2], 1.5f);
|
||||
}
|
||||
|
||||
TEST_F(SensorWithDedupTest, SetSensorResetsHistory) {
|
||||
SensorWithDedup<uint16_t> dedup;
|
||||
dedup.set_sensor(&this->sensor_);
|
||||
dedup.publish_state_if_not_dup(100);
|
||||
dedup.set_sensor(&this->sensor_);
|
||||
dedup.publish_state_if_not_dup(100);
|
||||
EXPECT_EQ(this->published_.size(), 2u);
|
||||
}
|
||||
|
||||
} // namespace esphome::sensor::testing
|
||||
Reference in New Issue
Block a user