mirror of
https://github.com/esphome/esphome.git
synced 2026-05-21 17:39:00 +08:00
[sensor] Drop Component from timeout filters, use self-keyed scheduler
Migrates TimeoutFilterBase / TimeoutFilterLast / TimeoutFilterConfigured off Component, mirroring the migration #16132 did for the other Component-based sensor filters. They now arm/re-arm via App.scheduler.set_timeout(this, ...) keyed on the filter pointer; the scheduler cancels and replaces any pending arm in O(1) on each new_value(). Filters live for the program's lifetime, so the self-key never dangles. Net effect: this reverts the design from #11922, which moved timeout filters off the scheduler specifically because the bounded SchedulerItem pool churned on devices with many timers (LD2450 etc.). With #16172 replacing that pool with an unbounded intrusive freelist, the original churn problem is gone and the scheduler is once again the right primitive for this workload. Per-instance footprint shrinks: lose Component (second vptr, packed bookkeeping bytes, ComponentRuntimeStats block when runtime_stats: is enabled) and drop the now-unused timeout_start_time_ field. get_setup_priority() and the loop() poll go with it. Sensor timeout-filter syntax is unchanged. No user-facing change.
This commit is contained in:
@@ -280,7 +280,7 @@ ThrottleWithPriorityFilter = sensor_ns.class_(
|
||||
ThrottleWithPriorityNanFilter = sensor_ns.class_(
|
||||
"ThrottleWithPriorityNanFilter", Filter
|
||||
)
|
||||
TimeoutFilterBase = sensor_ns.class_("TimeoutFilterBase", Filter, cg.Component)
|
||||
TimeoutFilterBase = sensor_ns.class_("TimeoutFilterBase", Filter)
|
||||
TimeoutFilterLast = sensor_ns.class_("TimeoutFilterLast", TimeoutFilterBase)
|
||||
TimeoutFilterConfigured = sensor_ns.class_("TimeoutFilterConfigured", TimeoutFilterBase)
|
||||
DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component)
|
||||
@@ -730,7 +730,6 @@ async def timeout_filter_to_code(config, filter_id):
|
||||
filter_id.type = TimeoutFilterConfigured
|
||||
template_ = await cg.templatable(config[CONF_VALUE], [], cg.float_)
|
||||
var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_)
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
|
||||
|
||||
@@ -322,41 +322,19 @@ optional<float> or_filter_new_value(Filter **filters, size_t count, float value,
|
||||
return {};
|
||||
}
|
||||
|
||||
// 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; }
|
||||
|
||||
// TimeoutFilterLast - "last" mode implementation
|
||||
// TimeoutFilterLast - "last" mode: re-arm on every input; output the latest value if no further
|
||||
// input arrives within time_period_. Self-keyed scheduler.set_timeout(this, ...) cancels and
|
||||
// replaces any pending arm in O(1).
|
||||
optional<float> TimeoutFilterLast::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();
|
||||
|
||||
App.scheduler.set_timeout(this, this->time_period_, [this]() { this->output(this->pending_value_); });
|
||||
return value;
|
||||
}
|
||||
|
||||
// TimeoutFilterConfigured - configured value mode implementation
|
||||
// TimeoutFilterConfigured - configured-value mode: re-arm on every input; output the configured
|
||||
// value (static or lambda) if no further input arrives within time_period_.
|
||||
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();
|
||||
|
||||
App.scheduler.set_timeout(this, this->time_period_, [this]() { this->output(this->value_.value()); });
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -412,22 +412,15 @@ class ThrottleWithPriorityNanFilter : public Filter {
|
||||
uint32_t min_time_between_inputs_;
|
||||
};
|
||||
|
||||
// Base class for timeout filters - contains common loop logic
|
||||
class TimeoutFilterBase : public Filter, public Component {
|
||||
public:
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
// Base class for timeout filters. Self-keyed scheduler timeout (`this` as key) re-arms on each
|
||||
// new_value(). Filter instances live for the program's lifetime, so the scheduler key never dangles.
|
||||
class TimeoutFilterBase : public Filter {
|
||||
protected:
|
||||
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
|
||||
explicit TimeoutFilterBase(uint32_t time_period) : time_period_(time_period) {}
|
||||
uint32_t time_period_;
|
||||
};
|
||||
|
||||
// Timeout filter for "last" mode - outputs the last received value after timeout
|
||||
// "last" mode — outputs the most recent input after time_period_ ms of silence.
|
||||
class TimeoutFilterLast : public TimeoutFilterBase {
|
||||
public:
|
||||
explicit TimeoutFilterLast(uint32_t time_period) : TimeoutFilterBase(time_period) {}
|
||||
@@ -435,12 +428,10 @@ class TimeoutFilterLast : public TimeoutFilterBase {
|
||||
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
|
||||
float pending_value_{0};
|
||||
};
|
||||
|
||||
// Timeout filter with configured value - evaluates TemplatableValue after timeout
|
||||
// Configured-value mode — outputs a static or lambda value after time_period_ ms of silence.
|
||||
class TimeoutFilterConfigured : public TimeoutFilterBase {
|
||||
public:
|
||||
explicit TimeoutFilterConfigured(uint32_t time_period, const TemplatableFn<float> &new_value)
|
||||
@@ -449,9 +440,7 @@ class TimeoutFilterConfigured : public TimeoutFilterBase {
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
float get_output_value() override { return this->value_.value(); }
|
||||
TemplatableFn<float> value_; // 4 bytes (configured output value, can be lambda)
|
||||
// Total: 8 (base) + 4 = 12 bytes + vtable ptr + Component overhead
|
||||
TemplatableFn<float> value_;
|
||||
};
|
||||
|
||||
class DebounceFilter : public Filter, public Component {
|
||||
|
||||
Reference in New Issue
Block a user