1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-16 23:05:46 +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 "ThrottleWithPriorityFilter", ValueListFilter
) )
TimeoutFilter = sensor_ns.class_("TimeoutFilter", Filter, cg.Component) TimeoutFilter = sensor_ns.class_("TimeoutFilter", Filter, cg.Component)
TimeoutFilterConfigured = sensor_ns.class_(
"TimeoutFilterConfigured", Filter, cg.Component
)
DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component) DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component)
HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component) HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component)
DeltaFilter = sensor_ns.class_("DeltaFilter", Filter) DeltaFilter = sensor_ns.class_("DeltaFilter", Filter)
@@ -684,8 +687,13 @@ TIMEOUT_SCHEMA = cv.maybe_simple_value(
@FILTER_REGISTRY.register("timeout", TimeoutFilter, TIMEOUT_SCHEMA) @FILTER_REGISTRY.register("timeout", TimeoutFilter, TIMEOUT_SCHEMA)
async def timeout_filter_to_code(config, filter_id): async def timeout_filter_to_code(config, filter_id):
if config[CONF_VALUE] == "last": 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]) var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT])
else: 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) template_ = await cg.templatable(config[CONF_VALUE], [], float)
var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_) var = cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_)
await cg.register_component(var, {}) await cg.register_component(var, {})

View File

@@ -339,20 +339,43 @@ void OrFilter::initialize(Sensor *parent, Filter *next) {
this->phi_.initialize(parent, nullptr); this->phi_.initialize(parent, nullptr);
} }
// TimeoutFilter // TimeoutFilterBase - shared loop logic
optional<float> TimeoutFilter::new_value(float value) { void TimeoutFilterBase::loop() {
if (this->value_.has_value()) { // Check if timeout period has elapsed
this->set_timeout("timeout", this->time_period_, [this]() { this->output(this->value_.value().value()); }); // Use cached loop start time to avoid repeated millis() calls
} else { const uint32_t now = App.get_loop_component_start_time();
this->set_timeout("timeout", this->time_period_, [this, value]() { this->output(value); }); 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; return value;
} }
TimeoutFilter::TimeoutFilter(uint32_t time_period) : time_period_(time_period) {} // TimeoutFilterConfigured - configured value mode implementation
TimeoutFilter::TimeoutFilter(uint32_t time_period, const TemplatableValue<float> &new_value) optional<float> TimeoutFilterConfigured::new_value(float value) {
: time_period_(time_period), value_(new_value) {} // Record when timeout started and enable loop
float TimeoutFilter::get_setup_priority() const { return setup_priority::HARDWARE; } // 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 // DebounceFilter
optional<float> DebounceFilter::new_value(float value) { optional<float> DebounceFilter::new_value(float value) {

View File

@@ -380,18 +380,46 @@ class ThrottleWithPriorityFilter : public ValueListFilter {
uint32_t min_time_between_inputs_; 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: public:
explicit TimeoutFilter(uint32_t time_period); void loop() override;
explicit TimeoutFilter(uint32_t time_period, const TemplatableValue<float> &new_value);
optional<float> new_value(float value) override;
float get_setup_priority() const override; float get_setup_priority() const override;
protected: protected:
uint32_t time_period_; explicit TimeoutFilterBase(uint32_t time_period) : time_period_(time_period) { this->disable_loop(); }
optional<TemplatableValue<float>> value_; 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 { class DebounceFilter : public Filter, public Component {