From 5b2176562bf3b1e86fee1cdc274d4f1ba7d8f89e Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Tue, 4 Jul 2023 04:25:48 +0400 Subject: [PATCH] binary_sensor filters templatable delays (#5029) --- esphome/components/binary_sensor/__init__.py | 76 ++++++++++++++------ esphome/components/binary_sensor/filter.cpp | 11 ++- esphome/components/binary_sensor/filter.h | 21 +++--- tests/test1.yaml | 7 ++ 4 files changed, 79 insertions(+), 36 deletions(-) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index f4a5c95b12..41b4c5a0d7 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -95,6 +95,14 @@ DEVICE_CLASSES = [ IS_PLATFORM_COMPONENT = True +CONF_TIME_OFF = "time_off" +CONF_TIME_ON = "time_on" + +DEFAULT_DELAY = "1s" +DEFAULT_TIME_OFF = "100ms" +DEFAULT_TIME_ON = "900ms" + + binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase) BinarySensorInitiallyOff = binary_sensor_ns.class_( @@ -138,47 +146,75 @@ FILTER_REGISTRY = Registry() validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) -@FILTER_REGISTRY.register("invert", InvertFilter, {}) +def register_filter(name, filter_type, schema): + return FILTER_REGISTRY.register(name, filter_type, schema) + + +@register_filter("invert", InvertFilter, {}) async def invert_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id) -@FILTER_REGISTRY.register( - "delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds +@register_filter( + "delayed_on_off", + DelayedOnOffFilter, + cv.Any( + cv.templatable(cv.positive_time_period_milliseconds), + cv.Schema( + { + cv.Required(CONF_TIME_ON): cv.templatable( + cv.positive_time_period_milliseconds + ), + cv.Required(CONF_TIME_OFF): cv.templatable( + cv.positive_time_period_milliseconds + ), + } + ), + msg="'delayed_on_off' filter requires either a delay time to be used for both " + "turn-on and turn-off delays, or two parameters 'time_on' and 'time_off' if " + "different delay times are required.", + ), ) async def delayed_on_off_filter_to_code(config, filter_id): - var = cg.new_Pvariable(filter_id, config) + var = cg.new_Pvariable(filter_id) await cg.register_component(var, {}) + if isinstance(config, dict): + template_ = await cg.templatable(config[CONF_TIME_ON], [], cg.uint32) + cg.add(var.set_on_delay(template_)) + template_ = await cg.templatable(config[CONF_TIME_OFF], [], cg.uint32) + cg.add(var.set_off_delay(template_)) + else: + template_ = await cg.templatable(config, [], cg.uint32) + cg.add(var.set_on_delay(template_)) + cg.add(var.set_off_delay(template_)) return var -@FILTER_REGISTRY.register( - "delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds +@register_filter( + "delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds) ) async def delayed_on_filter_to_code(config, filter_id): - var = cg.new_Pvariable(filter_id, config) + var = cg.new_Pvariable(filter_id) await cg.register_component(var, {}) + template_ = await cg.templatable(config, [], cg.uint32) + cg.add(var.set_delay(template_)) return var -@FILTER_REGISTRY.register( - "delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds +@register_filter( + "delayed_off", + DelayedOffFilter, + cv.templatable(cv.positive_time_period_milliseconds), ) async def delayed_off_filter_to_code(config, filter_id): - var = cg.new_Pvariable(filter_id, config) + var = cg.new_Pvariable(filter_id) await cg.register_component(var, {}) + template_ = await cg.templatable(config, [], cg.uint32) + cg.add(var.set_delay(template_)) return var -CONF_TIME_OFF = "time_off" -CONF_TIME_ON = "time_on" - -DEFAULT_DELAY = "1s" -DEFAULT_TIME_OFF = "100ms" -DEFAULT_TIME_ON = "900ms" - - -@FILTER_REGISTRY.register( +@register_filter( "autorepeat", AutorepeatFilter, cv.All( @@ -215,7 +251,7 @@ async def autorepeat_filter_to_code(config, filter_id): return var -@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) +@register_filter("lambda", LambdaFilter, cv.returning_lambda) async def lambda_filter_to_code(config, filter_id): lambda_ = await cg.process_lambda( config, [(bool, "x")], return_type=cg.optional.template(bool) diff --git a/esphome/components/binary_sensor/filter.cpp b/esphome/components/binary_sensor/filter.cpp index 836c341574..46957383c3 100644 --- a/esphome/components/binary_sensor/filter.cpp +++ b/esphome/components/binary_sensor/filter.cpp @@ -26,22 +26,20 @@ void Filter::input(bool value, bool is_initial) { } } -DelayedOnOffFilter::DelayedOnOffFilter(uint32_t delay) : delay_(delay) {} optional DelayedOnOffFilter::new_value(bool value, bool is_initial) { if (value) { - this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(true, is_initial); }); + this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); }); } else { - this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); }); + this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); }); } return {}; } float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; } -DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {} optional DelayedOnFilter::new_value(bool value, bool is_initial) { if (value) { - this->set_timeout("ON", this->delay_, [this, is_initial]() { this->output(true, is_initial); }); + this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); }); return {}; } else { this->cancel_timeout("ON"); @@ -51,10 +49,9 @@ optional DelayedOnFilter::new_value(bool value, bool is_initial) { float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; } -DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {} optional DelayedOffFilter::new_value(bool value, bool is_initial) { if (!value) { - this->set_timeout("OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); }); + this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); }); return {}; } else { this->cancel_timeout("OFF"); diff --git a/esphome/components/binary_sensor/filter.h b/esphome/components/binary_sensor/filter.h index 0f0ab6875f..9514cb3fe2 100644 --- a/esphome/components/binary_sensor/filter.h +++ b/esphome/components/binary_sensor/filter.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -29,38 +30,40 @@ class Filter { class DelayedOnOffFilter : public Filter, public Component { public: - explicit DelayedOnOffFilter(uint32_t delay); - optional new_value(bool value, bool is_initial) override; float get_setup_priority() const override; + template void set_on_delay(T delay) { this->on_delay_ = delay; } + template void set_off_delay(T delay) { this->off_delay_ = delay; } + protected: - uint32_t delay_; + TemplatableValue on_delay_{}; + TemplatableValue off_delay_{}; }; class DelayedOnFilter : public Filter, public Component { public: - explicit DelayedOnFilter(uint32_t delay); - optional new_value(bool value, bool is_initial) override; float get_setup_priority() const override; + template void set_delay(T delay) { this->delay_ = delay; } + protected: - uint32_t delay_; + TemplatableValue delay_{}; }; class DelayedOffFilter : public Filter, public Component { public: - explicit DelayedOffFilter(uint32_t delay); - optional new_value(bool value, bool is_initial) override; float get_setup_priority() const override; + template void set_delay(T delay) { this->delay_ = delay; } + protected: - uint32_t delay_; + TemplatableValue delay_{}; }; class InvertFilter : public Filter { diff --git a/tests/test1.yaml b/tests/test1.yaml index f8928430f4..05ba07d1d8 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1355,8 +1355,15 @@ binary_sensor: device_class: window filters: - invert: + - delayed_on_off: 40ms + - delayed_on_off: + time_on: 10s + time_off: !lambda "return 1000;" - delayed_on: 40ms - delayed_off: 40ms + - delayed_on_off: !lambda "return 10;" + - delayed_on: !lambda "return 1000;" + - delayed_off: !lambda "return 0;" on_press: then: - lambda: >-