mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Autorepeat filter for the binary sensors (#1681)
* add the autorepeat filter * add a test for the autorepeat filter * make no timing equivalent to a single default one
This commit is contained in:
		| @@ -4,6 +4,7 @@ from esphome import automation, core | ||||
| from esphome.automation import Condition, maybe_simple_id | ||||
| from esphome.components import mqtt | ||||
| from esphome.const import ( | ||||
|     CONF_DELAY, | ||||
|     CONF_DEVICE_CLASS, | ||||
|     CONF_FILTERS, | ||||
|     CONF_ID, | ||||
| @@ -120,6 +121,7 @@ DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Co | ||||
| DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) | ||||
| DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) | ||||
| InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) | ||||
| AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component) | ||||
| LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) | ||||
|  | ||||
| FILTER_REGISTRY = Registry() | ||||
| @@ -158,6 +160,51 @@ def delayed_off_filter_to_code(config, filter_id): | ||||
|     yield 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( | ||||
|     "autorepeat", | ||||
|     AutorepeatFilter, | ||||
|     cv.All( | ||||
|         cv.ensure_list( | ||||
|             { | ||||
|                 cv.Optional( | ||||
|                     CONF_DELAY, default=DEFAULT_DELAY | ||||
|                 ): cv.positive_time_period_milliseconds, | ||||
|                 cv.Optional( | ||||
|                     CONF_TIME_OFF, default=DEFAULT_TIME_OFF | ||||
|                 ): cv.positive_time_period_milliseconds, | ||||
|                 cv.Optional( | ||||
|                     CONF_TIME_ON, default=DEFAULT_TIME_ON | ||||
|                 ): cv.positive_time_period_milliseconds, | ||||
|             } | ||||
|         ), | ||||
|     ), | ||||
| ) | ||||
| def autorepeat_filter_to_code(config, filter_id): | ||||
|     timings = [] | ||||
|     if len(config) > 0: | ||||
|         for conf in config: | ||||
|             timings.append((conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON])) | ||||
|     else: | ||||
|         timings.append( | ||||
|             ( | ||||
|                 cv.time_period_str_unit(DEFAULT_DELAY).total_milliseconds, | ||||
|                 cv.time_period_str_unit(DEFAULT_TIME_OFF).total_milliseconds, | ||||
|                 cv.time_period_str_unit(DEFAULT_TIME_ON).total_milliseconds, | ||||
|             ) | ||||
|         ) | ||||
|     var = cg.new_Pvariable(filter_id, timings) | ||||
|     yield cg.register_component(var, {}) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) | ||||
| def lambda_filter_to_code(config, filter_id): | ||||
|     lambda_ = yield cg.process_lambda( | ||||
|   | ||||
| @@ -64,6 +64,50 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD | ||||
|  | ||||
| optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; } | ||||
|  | ||||
| AutorepeatFilter::AutorepeatFilter(const std::vector<AutorepeatFilterTiming> &timings) : timings_(timings) {} | ||||
|  | ||||
| optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) { | ||||
|   if (value) { | ||||
|     // Ignore if already running | ||||
|     if (this->active_timing_ != 0) | ||||
|       return {}; | ||||
|  | ||||
|     this->next_timing_(); | ||||
|     return true; | ||||
|   } else { | ||||
|     this->cancel_timeout("TIMING"); | ||||
|     this->cancel_timeout("ON_OFF"); | ||||
|     this->active_timing_ = 0; | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AutorepeatFilter::next_timing_() { | ||||
|   // Entering this method | ||||
|   // 1st time: starts waiting the first delay | ||||
|   // 2nd time: starts waiting the second delay and starts toggling with the first time_off / _on | ||||
|   // last time: no delay to start but have to bump the index to reflect the last | ||||
|   if (this->active_timing_ < this->timings_.size()) | ||||
|     this->set_timeout("TIMING", this->timings_[this->active_timing_].delay, [this]() { this->next_timing_(); }); | ||||
|  | ||||
|   if (this->active_timing_ <= this->timings_.size()) { | ||||
|     this->active_timing_++; | ||||
|   } | ||||
|  | ||||
|   if (this->active_timing_ == 2) | ||||
|     this->next_value_(false); | ||||
|  | ||||
|   // Leaving this method: if the toggling is started, it has to use [active_timing_ - 2] for the intervals | ||||
| } | ||||
|  | ||||
| void AutorepeatFilter::next_value_(bool val) { | ||||
|   const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2]; | ||||
|   this->output(val, false);  // This is at least the second one so not initial | ||||
|   this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); }); | ||||
| } | ||||
|  | ||||
| float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
|  | ||||
| LambdaFilter::LambdaFilter(const std::function<optional<bool>(bool)> &f) : f_(f) {} | ||||
|  | ||||
| optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); } | ||||
|   | ||||
| @@ -66,6 +66,33 @@ class InvertFilter : public Filter { | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
| }; | ||||
|  | ||||
| struct AutorepeatFilterTiming { | ||||
|   AutorepeatFilterTiming(uint32_t delay, uint32_t off, uint32_t on) { | ||||
|     this->delay = delay; | ||||
|     this->time_off = off; | ||||
|     this->time_on = on; | ||||
|   } | ||||
|   uint32_t delay; | ||||
|   uint32_t time_off; | ||||
|   uint32_t time_on; | ||||
| }; | ||||
|  | ||||
| class AutorepeatFilter : public Filter, public Component { | ||||
|  public: | ||||
|   explicit AutorepeatFilter(const std::vector<AutorepeatFilterTiming> &timings); | ||||
|  | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|  protected: | ||||
|   void next_timing_(); | ||||
|   void next_value_(bool val); | ||||
|  | ||||
|   std::vector<AutorepeatFilterTiming> timings_; | ||||
|   uint8_t active_timing_{0}; | ||||
| }; | ||||
|  | ||||
| class LambdaFilter : public Filter { | ||||
|  public: | ||||
|   explicit LambdaFilter(const std::function<optional<bool>(bool)> &f); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user