1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-16 06:45:48 +00:00

[sensor] Replace timeout filter scheduler with loop-based implementation

This commit is contained in:
J. Nick Koston
2025-11-15 12:56:25 -06:00
parent 1df996601d
commit 894ba341ba
3 changed files with 77 additions and 18 deletions

View File

@@ -271,6 +271,9 @@ ThrottleWithPriorityFilter = sensor_ns.class_(
"ThrottleWithPriorityFilter", ValueListFilter
)
TimeoutFilter = sensor_ns.class_("TimeoutFilter", Filter, cg.Component)
TimeoutFilterConfigured = sensor_ns.class_(
"TimeoutFilterConfigured", Filter, cg.Component
)
DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component)
HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component)
DeltaFilter = sensor_ns.class_("DeltaFilter", Filter)
@@ -684,8 +687,13 @@ TIMEOUT_SCHEMA = cv.maybe_simple_value(
@FILTER_REGISTRY.register("timeout", TimeoutFilter, TIMEOUT_SCHEMA)
async def timeout_filter_to_code(config, filter_id):
if config[CONF_VALUE] == "last":
# Use TimeoutFilter for "last" mode (smaller, more common - LD2450, LD2412, etc.)
var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT])
else:
# Use TimeoutFilterConfigured for configured value mode
# Change the type to TimeoutFilterConfigured (similar to stateless lambda pattern)
filter_id = filter_id.copy()
filter_id.type = TimeoutFilterConfigured
template_ = await cg.templatable(config[CONF_VALUE], [], float)
var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_)
await cg.register_component(var, {})

View File

@@ -339,20 +339,43 @@ void OrFilter::initialize(Sensor *parent, Filter *next) {
this->phi_.initialize(parent, nullptr);
}
// TimeoutFilter
optional<float> TimeoutFilter::new_value(float value) {
if (this->value_.has_value()) {
this->set_timeout("timeout", this->time_period_, [this]() { this->output(this->value_.value().value()); });
} else {
this->set_timeout("timeout", this->time_period_, [this, value]() { this->output(value); });
// TimeoutFilterBase - shared loop logic
void TimeoutFilterBase::loop() {
// Check if timeout period has elapsed
// Use cached loop start time to avoid repeated millis() calls
const uint32_t now = App.get_loop_component_start_time();
if (now - this->timeout_start_time_ >= this->time_period_) {
// Timeout fired - get output value from derived class and output it
this->output(this->get_output_value());
// Disable loop until next value arrives
this->disable_loop();
}
}
float TimeoutFilterBase::get_setup_priority() const { return setup_priority::HARDWARE; }
// TimeoutFilter - "last" mode implementation
optional<float> TimeoutFilter::new_value(float value) {
// Store the value to output when timeout fires
this->pending_value_ = value;
// Record when timeout started and enable loop
this->timeout_start_time_ = millis();
this->enable_loop();
return value;
}
TimeoutFilter::TimeoutFilter(uint32_t time_period) : time_period_(time_period) {}
TimeoutFilter::TimeoutFilter(uint32_t time_period, const TemplatableValue<float> &new_value)
: time_period_(time_period), value_(new_value) {}
float TimeoutFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
// TimeoutFilterConfigured - configured value mode implementation
optional<float> TimeoutFilterConfigured::new_value(float value) {
// Record when timeout started and enable loop
// Note: we don't store the incoming value since we have a configured value
this->timeout_start_time_ = millis();
this->enable_loop();
return value;
}
// DebounceFilter
optional<float> DebounceFilter::new_value(float value) {

View File

@@ -380,18 +380,46 @@ class ThrottleWithPriorityFilter : public ValueListFilter {
uint32_t min_time_between_inputs_;
};
class TimeoutFilter : public Filter, public Component {
// Base class for timeout filters - contains common loop logic
class TimeoutFilterBase : public Filter, public Component {
public:
explicit TimeoutFilter(uint32_t time_period);
explicit TimeoutFilter(uint32_t time_period, const TemplatableValue<float> &new_value);
optional<float> new_value(float value) override;
void loop() override;
float get_setup_priority() const override;
protected:
uint32_t time_period_;
optional<TemplatableValue<float>> value_;
explicit TimeoutFilterBase(uint32_t time_period) : time_period_(time_period) { this->disable_loop(); }
virtual float get_output_value() = 0;
uint32_t time_period_; // 4 bytes (timeout duration in ms)
uint32_t timeout_start_time_{0}; // 4 bytes (when the timeout was started)
// Total base: 8 bytes
};
// Timeout filter for "last" mode - outputs the last received value after timeout
class TimeoutFilter : public TimeoutFilterBase {
public:
explicit TimeoutFilter(uint32_t time_period) : TimeoutFilterBase(time_period) {}
optional<float> new_value(float value) override;
protected:
float get_output_value() override { return this->pending_value_; }
float pending_value_{0}; // 4 bytes (value to output when timeout fires)
// Total: 8 (base) + 4 = 12 bytes + vtable ptr + Component overhead
};
// Timeout filter with configured value - evaluates TemplatableValue after timeout
class TimeoutFilterConfigured : public TimeoutFilterBase {
public:
explicit TimeoutFilterConfigured(uint32_t time_period, const TemplatableValue<float> &new_value)
: TimeoutFilterBase(time_period), value_(new_value) {}
optional<float> new_value(float value) override;
protected:
float get_output_value() override { return this->value_.value(); }
TemplatableValue<float> value_; // 16 bytes (configured output value, can be lambda)
// Total: 8 (base) + 16 = 24 bytes + vtable ptr + Component overhead
};
class DebounceFilter : public Filter, public Component {