mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'dev' into ble_events
This commit is contained in:
		| @@ -227,7 +227,7 @@ bool APIServer::check_password(const std::string &password) const { | ||||
| void APIServer::handle_disconnect(APIConnection *conn) {} | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { | ||||
| void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) { | ||||
|   if (obj->is_internal()) | ||||
|     return; | ||||
|   for (auto &c : this->clients_) | ||||
|   | ||||
| @@ -54,7 +54,7 @@ class APIServer : public Component, public Controller { | ||||
|  | ||||
|   void handle_disconnect(APIConnection *conn); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; | ||||
|   void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   void on_cover_update(cover::Cover *obj) override; | ||||
|   | ||||
| @@ -46,12 +46,10 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None: | ||||
|         time_ = datetime.now() | ||||
|         message: bytes = msg.message | ||||
|         text = message.decode("utf8", "backslashreplace") | ||||
|         if dashboard: | ||||
|             text = text.replace("\033", "\\033") | ||||
|         for parsed_msg in parse_log_message( | ||||
|             text, f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]" | ||||
|         ): | ||||
|             print(parsed_msg) | ||||
|             print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg) | ||||
|  | ||||
|     stop = await async_run(cli, on_log, name=name) | ||||
|     try: | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| from logging import getLogger | ||||
|  | ||||
| from esphome import automation, core | ||||
| from esphome.automation import Condition, maybe_simple_id | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import mqtt, web_server | ||||
| from esphome.components.const import CONF_ON_STATE_CHANGE | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_DELAY, | ||||
| @@ -98,6 +101,7 @@ IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| CONF_TIME_OFF = "time_off" | ||||
| CONF_TIME_ON = "time_on" | ||||
| CONF_TRIGGER_ON_INITIAL_STATE = "trigger_on_initial_state" | ||||
|  | ||||
| DEFAULT_DELAY = "1s" | ||||
| DEFAULT_TIME_OFF = "100ms" | ||||
| @@ -127,9 +131,17 @@ MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent") | ||||
| StateTrigger = binary_sensor_ns.class_( | ||||
|     "StateTrigger", automation.Trigger.template(bool) | ||||
| ) | ||||
| StateChangeTrigger = binary_sensor_ns.class_( | ||||
|     "StateChangeTrigger", | ||||
|     automation.Trigger.template(cg.optional.template(bool), cg.optional.template(bool)), | ||||
| ) | ||||
|  | ||||
| BinarySensorPublishAction = binary_sensor_ns.class_( | ||||
|     "BinarySensorPublishAction", automation.Action | ||||
| ) | ||||
| BinarySensorInvalidateAction = binary_sensor_ns.class_( | ||||
|     "BinarySensorInvalidateAction", automation.Action | ||||
| ) | ||||
|  | ||||
| # Condition | ||||
| BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition) | ||||
| @@ -144,6 +156,8 @@ AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Compon | ||||
| LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) | ||||
| SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component) | ||||
|  | ||||
| _LOGGER = getLogger(__name__) | ||||
|  | ||||
| FILTER_REGISTRY = Registry() | ||||
| validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) | ||||
|  | ||||
| @@ -386,6 +400,14 @@ def validate_click_timing(value): | ||||
|     return value | ||||
|  | ||||
|  | ||||
| def validate_publish_initial_state(value): | ||||
|     value = cv.boolean(value) | ||||
|     _LOGGER.warning( | ||||
|         "The 'publish_initial_state' option has been replaced by 'trigger_on_initial_state' and will be removed in a future release" | ||||
|     ) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| _BINARY_SENSOR_SCHEMA = ( | ||||
|     cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) | ||||
|     .extend(cv.MQTT_COMPONENT_SCHEMA) | ||||
| @@ -395,7 +417,12 @@ _BINARY_SENSOR_SCHEMA = ( | ||||
|             cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( | ||||
|                 mqtt.MQTTBinarySensorComponent | ||||
|             ), | ||||
|             cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean, | ||||
|             cv.Exclusive( | ||||
|                 CONF_PUBLISH_INITIAL_STATE, CONF_TRIGGER_ON_INITIAL_STATE | ||||
|             ): validate_publish_initial_state, | ||||
|             cv.Exclusive( | ||||
|                 CONF_TRIGGER_ON_INITIAL_STATE, CONF_TRIGGER_ON_INITIAL_STATE | ||||
|             ): cv.boolean, | ||||
|             cv.Optional(CONF_DEVICE_CLASS): validate_device_class, | ||||
|             cv.Optional(CONF_FILTERS): validate_filters, | ||||
|             cv.Optional(CONF_ON_PRESS): automation.validate_automation( | ||||
| @@ -454,6 +481,11 @@ _BINARY_SENSOR_SCHEMA = ( | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateChangeTrigger), | ||||
|                 } | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
| ) | ||||
| @@ -493,8 +525,10 @@ async def setup_binary_sensor_core_(var, config): | ||||
|  | ||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||
|         cg.add(var.set_device_class(device_class)) | ||||
|     if publish_initial_state := config.get(CONF_PUBLISH_INITIAL_STATE): | ||||
|         cg.add(var.set_publish_initial_state(publish_initial_state)) | ||||
|     trigger = config.get(CONF_TRIGGER_ON_INITIAL_STATE, False) or config.get( | ||||
|         CONF_PUBLISH_INITIAL_STATE, False | ||||
|     ) | ||||
|     cg.add(var.set_trigger_on_initial_state(trigger)) | ||||
|     if inverted := config.get(CONF_INVERTED): | ||||
|         cg.add(var.set_inverted(inverted)) | ||||
|     if filters_config := config.get(CONF_FILTERS): | ||||
| @@ -542,6 +576,17 @@ async def setup_binary_sensor_core_(var, config): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation(trigger, [(bool, "x")], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_STATE_CHANGE, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation( | ||||
|             trigger, | ||||
|             [ | ||||
|                 (cg.optional.template(bool), "x_previous"), | ||||
|                 (cg.optional.template(bool), "x"), | ||||
|             ], | ||||
|             conf, | ||||
|         ) | ||||
|  | ||||
|     if mqtt_id := config.get(CONF_MQTT_ID): | ||||
|         mqtt_ = cg.new_Pvariable(mqtt_id, var) | ||||
|         await mqtt.register_mqtt_component(mqtt_, config) | ||||
| @@ -591,3 +636,18 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args) | ||||
| async def to_code(config): | ||||
|     cg.add_define("USE_BINARY_SENSOR") | ||||
|     cg.add_global(binary_sensor_ns.using) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "binary_sensor.invalidate_state", | ||||
|     BinarySensorInvalidateAction, | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(BinarySensor), | ||||
|         }, | ||||
|         key=CONF_ID, | ||||
|     ), | ||||
| ) | ||||
| async def binary_sensor_invalidate_state_to_code(config, action_id, template_arg, args): | ||||
|     paren = await cg.get_variable(config[CONF_ID]) | ||||
|     return cg.new_Pvariable(action_id, template_arg, paren) | ||||
|   | ||||
| @@ -96,7 +96,7 @@ class MultiClickTrigger : public Trigger<>, public Component { | ||||
|       : parent_(parent), timing_(std::move(timing)) {} | ||||
|  | ||||
|   void setup() override { | ||||
|     this->last_state_ = this->parent_->state; | ||||
|     this->last_state_ = this->parent_->get_state_default(false); | ||||
|     auto f = std::bind(&MultiClickTrigger::on_state_, this, std::placeholders::_1); | ||||
|     this->parent_->add_on_state_callback(f); | ||||
|   } | ||||
| @@ -130,6 +130,14 @@ class StateTrigger : public Trigger<bool> { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class StateChangeTrigger : public Trigger<optional<bool>, optional<bool> > { | ||||
|  public: | ||||
|   explicit StateChangeTrigger(BinarySensor *parent) { | ||||
|     parent->add_full_state_callback( | ||||
|         [this](optional<bool> old_state, optional<bool> state) { this->trigger(old_state, state); }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BinarySensorCondition : public Condition<Ts...> { | ||||
|  public: | ||||
|   BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {} | ||||
| @@ -154,5 +162,15 @@ template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...> | ||||
|   BinarySensor *sensor_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BinarySensorInvalidateAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit BinarySensorInvalidateAction(BinarySensor *sensor) : sensor_(sensor) {} | ||||
|  | ||||
|   void play(Ts... x) override { this->sensor_->invalidate_state(); } | ||||
|  | ||||
|  protected: | ||||
|   BinarySensor *sensor_; | ||||
| }; | ||||
|  | ||||
| }  // namespace binary_sensor | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -7,42 +7,25 @@ namespace binary_sensor { | ||||
|  | ||||
| static const char *const TAG = "binary_sensor"; | ||||
|  | ||||
| void BinarySensor::add_on_state_callback(std::function<void(bool)> &&callback) { | ||||
|   this->state_callback_.add(std::move(callback)); | ||||
| } | ||||
|  | ||||
| void BinarySensor::publish_state(bool state) { | ||||
|   if (!this->publish_dedup_.next(state)) | ||||
|     return; | ||||
| void BinarySensor::publish_state(bool new_state) { | ||||
|   if (this->filter_list_ == nullptr) { | ||||
|     this->send_state_internal(state, false); | ||||
|     this->send_state_internal(new_state); | ||||
|   } else { | ||||
|     this->filter_list_->input(state, false); | ||||
|     this->filter_list_->input(new_state); | ||||
|   } | ||||
| } | ||||
| void BinarySensor::publish_initial_state(bool state) { | ||||
|   if (!this->publish_dedup_.next(state)) | ||||
|     return; | ||||
|   if (this->filter_list_ == nullptr) { | ||||
|     this->send_state_internal(state, true); | ||||
|   } else { | ||||
|     this->filter_list_->input(state, true); | ||||
| void BinarySensor::publish_initial_state(bool new_state) { | ||||
|   this->invalidate_state(); | ||||
|   this->publish_state(new_state); | ||||
| } | ||||
| void BinarySensor::send_state_internal(bool new_state) { | ||||
|   // copy the new state to the visible property for backwards compatibility, before any callbacks | ||||
|   this->state = new_state; | ||||
|   // Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed | ||||
|   if (this->set_state_(new_state)) { | ||||
|     ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(new_state)); | ||||
|   } | ||||
| } | ||||
| void BinarySensor::send_state_internal(bool state, bool is_initial) { | ||||
|   if (is_initial) { | ||||
|     ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state)); | ||||
|   } else { | ||||
|     ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), ONOFF(state)); | ||||
|   } | ||||
|   this->has_state_ = true; | ||||
|   this->state = state; | ||||
|   if (!is_initial || this->publish_initial_state_) { | ||||
|     this->state_callback_.call(state); | ||||
|   } | ||||
| } | ||||
|  | ||||
| BinarySensor::BinarySensor() : state(false) {} | ||||
|  | ||||
| void BinarySensor::add_filter(Filter *filter) { | ||||
|   filter->parent_ = this; | ||||
| @@ -60,7 +43,6 @@ void BinarySensor::add_filters(const std::vector<Filter *> &filters) { | ||||
|     this->add_filter(filter); | ||||
|   } | ||||
| } | ||||
| bool BinarySensor::has_state() const { return this->has_state_; } | ||||
| bool BinarySensor::is_status_binary_sensor() const { return false; } | ||||
|  | ||||
| }  // namespace binary_sensor | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/entity_base.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/components/binary_sensor/filter.h" | ||||
| @@ -34,52 +33,39 @@ namespace binary_sensor { | ||||
|  * The sub classes should notify the front-end of new states via the publish_state() method which | ||||
|  * handles inverted inputs for you. | ||||
|  */ | ||||
| class BinarySensor : public EntityBase, public EntityBase_DeviceClass { | ||||
| class BinarySensor : public StatefulEntityBase<bool>, public EntityBase_DeviceClass { | ||||
|  public: | ||||
|   explicit BinarySensor(); | ||||
|  | ||||
|   /** Add a callback to be notified of state changes. | ||||
|    * | ||||
|    * @param callback The void(bool) callback. | ||||
|    */ | ||||
|   void add_on_state_callback(std::function<void(bool)> &&callback); | ||||
|   explicit BinarySensor(){}; | ||||
|  | ||||
|   /** Publish a new state to the front-end. | ||||
|    * | ||||
|    * @param state The new state. | ||||
|    * @param new_state The new state. | ||||
|    */ | ||||
|   void publish_state(bool state); | ||||
|   void publish_state(bool new_state); | ||||
|  | ||||
|   /** Publish the initial state, this will not make the callback manager send callbacks | ||||
|    * and is meant only for the initial state on boot. | ||||
|    * | ||||
|    * @param state The new state. | ||||
|    * @param new_state The new state. | ||||
|    */ | ||||
|   void publish_initial_state(bool state); | ||||
|  | ||||
|   /// The current reported state of the binary sensor. | ||||
|   bool state{false}; | ||||
|   void publish_initial_state(bool new_state); | ||||
|  | ||||
|   void add_filter(Filter *filter); | ||||
|   void add_filters(const std::vector<Filter *> &filters); | ||||
|  | ||||
|   void set_publish_initial_state(bool publish_initial_state) { this->publish_initial_state_ = publish_initial_state; } | ||||
|  | ||||
|   // ========== INTERNAL METHODS ========== | ||||
|   // (In most use cases you won't need these) | ||||
|   void send_state_internal(bool state, bool is_initial); | ||||
|   void send_state_internal(bool new_state); | ||||
|  | ||||
|   /// Return whether this binary sensor has outputted a state. | ||||
|   virtual bool has_state() const; | ||||
|  | ||||
|   virtual bool is_status_binary_sensor() const; | ||||
|  | ||||
|   // For backward compatibility, provide an accessible property | ||||
|  | ||||
|   bool state{}; | ||||
|  | ||||
|  protected: | ||||
|   CallbackManager<void(bool)> state_callback_{}; | ||||
|   Filter *filter_list_{nullptr}; | ||||
|   bool has_state_{false}; | ||||
|   bool publish_initial_state_{false}; | ||||
|   Deduplicator<bool> publish_dedup_; | ||||
| }; | ||||
|  | ||||
| class BinarySensorInitiallyOff : public BinarySensor { | ||||
|   | ||||
| @@ -9,37 +9,36 @@ namespace binary_sensor { | ||||
|  | ||||
| static const char *const TAG = "sensor.filter"; | ||||
|  | ||||
| void Filter::output(bool value, bool is_initial) { | ||||
| void Filter::output(bool value) { | ||||
|   if (this->next_ == nullptr) { | ||||
|     this->parent_->send_state_internal(value); | ||||
|   } else { | ||||
|     this->next_->input(value); | ||||
|   } | ||||
| } | ||||
| void Filter::input(bool value) { | ||||
|   if (!this->dedup_.next(value)) | ||||
|     return; | ||||
|  | ||||
|   if (this->next_ == nullptr) { | ||||
|     this->parent_->send_state_internal(value, is_initial); | ||||
|   } else { | ||||
|     this->next_->input(value, is_initial); | ||||
|   } | ||||
| } | ||||
| void Filter::input(bool value, bool is_initial) { | ||||
|   auto b = this->new_value(value, is_initial); | ||||
|   auto b = this->new_value(value); | ||||
|   if (b.has_value()) { | ||||
|     this->output(*b, is_initial); | ||||
|     this->output(*b); | ||||
|   } | ||||
| } | ||||
|  | ||||
| optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) { | ||||
| optional<bool> DelayedOnOffFilter::new_value(bool value) { | ||||
|   if (value) { | ||||
|     this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); }); | ||||
|     this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); }); | ||||
|   } else { | ||||
|     this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); }); | ||||
|     this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); }); | ||||
|   } | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
| float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
|  | ||||
| optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) { | ||||
| optional<bool> DelayedOnFilter::new_value(bool value) { | ||||
|   if (value) { | ||||
|     this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); }); | ||||
|     this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); }); | ||||
|     return {}; | ||||
|   } else { | ||||
|     this->cancel_timeout("ON"); | ||||
| @@ -49,9 +48,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) { | ||||
|  | ||||
| float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
|  | ||||
| optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) { | ||||
| optional<bool> DelayedOffFilter::new_value(bool value) { | ||||
|   if (!value) { | ||||
|     this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); }); | ||||
|     this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); }); | ||||
|     return {}; | ||||
|   } else { | ||||
|     this->cancel_timeout("OFF"); | ||||
| @@ -61,11 +60,11 @@ optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) { | ||||
|  | ||||
| float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
|  | ||||
| optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; } | ||||
| optional<bool> InvertFilter::new_value(bool value) { return !value; } | ||||
|  | ||||
| AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {} | ||||
|  | ||||
| optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) { | ||||
| optional<bool> AutorepeatFilter::new_value(bool value) { | ||||
|   if (value) { | ||||
|     // Ignore if already running | ||||
|     if (this->active_timing_ != 0) | ||||
| @@ -101,7 +100,7 @@ void AutorepeatFilter::next_timing_() { | ||||
|  | ||||
| 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->output(val);  // 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); }); | ||||
| } | ||||
|  | ||||
| @@ -109,18 +108,18 @@ float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARD | ||||
|  | ||||
| LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {} | ||||
|  | ||||
| optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); } | ||||
| optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); } | ||||
|  | ||||
| optional<bool> SettleFilter::new_value(bool value, bool is_initial) { | ||||
| optional<bool> SettleFilter::new_value(bool value) { | ||||
|   if (!this->steady_) { | ||||
|     this->set_timeout("SETTLE", this->delay_.value(), [this, value, is_initial]() { | ||||
|     this->set_timeout("SETTLE", this->delay_.value(), [this, value]() { | ||||
|       this->steady_ = true; | ||||
|       this->output(value, is_initial); | ||||
|       this->output(value); | ||||
|     }); | ||||
|     return {}; | ||||
|   } else { | ||||
|     this->steady_ = false; | ||||
|     this->output(value, is_initial); | ||||
|     this->output(value); | ||||
|     this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; }); | ||||
|     return value; | ||||
|   } | ||||
|   | ||||
| @@ -14,11 +14,11 @@ class BinarySensor; | ||||
|  | ||||
| class Filter { | ||||
|  public: | ||||
|   virtual optional<bool> new_value(bool value, bool is_initial) = 0; | ||||
|   virtual optional<bool> new_value(bool value) = 0; | ||||
|  | ||||
|   void input(bool value, bool is_initial); | ||||
|   void input(bool value); | ||||
|  | ||||
|   void output(bool value, bool is_initial); | ||||
|   void output(bool value); | ||||
|  | ||||
|  protected: | ||||
|   friend BinarySensor; | ||||
| @@ -30,7 +30,7 @@ class Filter { | ||||
|  | ||||
| class DelayedOnOffFilter : public Filter, public Component { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|   optional<bool> new_value(bool value) override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
| @@ -44,7 +44,7 @@ class DelayedOnOffFilter : public Filter, public Component { | ||||
|  | ||||
| class DelayedOnFilter : public Filter, public Component { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|   optional<bool> new_value(bool value) override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
| @@ -56,7 +56,7 @@ class DelayedOnFilter : public Filter, public Component { | ||||
|  | ||||
| class DelayedOffFilter : public Filter, public Component { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|   optional<bool> new_value(bool value) override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
| @@ -68,7 +68,7 @@ class DelayedOffFilter : public Filter, public Component { | ||||
|  | ||||
| class InvertFilter : public Filter { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|   optional<bool> new_value(bool value) override; | ||||
| }; | ||||
|  | ||||
| struct AutorepeatFilterTiming { | ||||
| @@ -86,7 +86,7 @@ class AutorepeatFilter : public Filter, public Component { | ||||
|  public: | ||||
|   explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings); | ||||
|  | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|   optional<bool> new_value(bool value) override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
| @@ -102,7 +102,7 @@ class LambdaFilter : public Filter { | ||||
|  public: | ||||
|   explicit LambdaFilter(std::function<optional<bool>(bool)> f); | ||||
|  | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|   optional<bool> new_value(bool value) override; | ||||
|  | ||||
|  protected: | ||||
|   std::function<optional<bool>(bool)> f_; | ||||
| @@ -110,7 +110,7 @@ class LambdaFilter : public Filter { | ||||
|  | ||||
| class SettleFilter : public Filter, public Component { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|   optional<bool> new_value(bool value) override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|   | ||||
| @@ -3,4 +3,5 @@ | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
|  | ||||
| CONF_DRAW_ROUNDING = "draw_rounding" | ||||
| CONF_ON_STATE_CHANGE = "on_state_change" | ||||
| CONF_REQUEST_HEADERS = "request_headers" | ||||
|   | ||||
| @@ -337,23 +337,26 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { | ||||
|  | ||||
| bool Nextion::upload_end_(bool successful) { | ||||
|   ESP_LOGD(TAG, "TFT upload done: %s", YESNO(successful)); | ||||
|   this->is_updating_ = false; | ||||
|   this->ignore_is_setup_ = false; | ||||
|  | ||||
|   uint32_t baud_rate = this->parent_->get_baud_rate(); | ||||
|   if (baud_rate != this->original_baud_rate_) { | ||||
|     ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_); | ||||
|     this->parent_->set_baud_rate(this->original_baud_rate_); | ||||
|     this->parent_->load_settings(); | ||||
|   } | ||||
|  | ||||
|   if (successful) { | ||||
|     ESP_LOGD(TAG, "Restart"); | ||||
|     delay(1500);  // NOLINT | ||||
|     App.safe_reboot(); | ||||
|     delay(1500);  // NOLINT | ||||
|   } else { | ||||
|     ESP_LOGE(TAG, "TFT upload failed"); | ||||
|  | ||||
|     this->is_updating_ = false; | ||||
|     this->ignore_is_setup_ = false; | ||||
|  | ||||
|     uint32_t baud_rate = this->parent_->get_baud_rate(); | ||||
|     if (baud_rate != this->original_baud_rate_) { | ||||
|       ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_); | ||||
|       this->parent_->set_baud_rate(this->original_baud_rate_); | ||||
|       this->parent_->load_settings(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return successful; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -337,15 +337,6 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { | ||||
|  | ||||
| bool Nextion::upload_end_(bool successful) { | ||||
|   ESP_LOGD(TAG, "TFT upload done: %s", YESNO(successful)); | ||||
|   this->is_updating_ = false; | ||||
|   this->ignore_is_setup_ = false; | ||||
|  | ||||
|   uint32_t baud_rate = this->parent_->get_baud_rate(); | ||||
|   if (baud_rate != this->original_baud_rate_) { | ||||
|     ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_); | ||||
|     this->parent_->set_baud_rate(this->original_baud_rate_); | ||||
|     this->parent_->load_settings(); | ||||
|   } | ||||
|  | ||||
|   if (successful) { | ||||
|     ESP_LOGD(TAG, "Restart"); | ||||
| @@ -353,7 +344,18 @@ bool Nextion::upload_end_(bool successful) { | ||||
|     App.safe_reboot(); | ||||
|   } else { | ||||
|     ESP_LOGE(TAG, "TFT upload failed"); | ||||
|  | ||||
|     this->is_updating_ = false; | ||||
|     this->ignore_is_setup_ = false; | ||||
|  | ||||
|     uint32_t baud_rate = this->parent_->get_baud_rate(); | ||||
|     if (baud_rate != this->original_baud_rate_) { | ||||
|       ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_); | ||||
|       this->parent_->set_baud_rate(this->original_baud_rate_); | ||||
|       this->parent_->load_settings(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return successful; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -6,16 +6,8 @@ namespace template_ { | ||||
|  | ||||
| static const char *const TAG = "template.binary_sensor"; | ||||
|  | ||||
| void TemplateBinarySensor::setup() { | ||||
|   if (!this->publish_initial_state_) | ||||
|     return; | ||||
| void TemplateBinarySensor::setup() { this->loop(); } | ||||
|  | ||||
|   if (this->f_ != nullptr) { | ||||
|     this->publish_initial_state(this->f_().value_or(false)); | ||||
|   } else { | ||||
|     this->publish_initial_state(false); | ||||
|   } | ||||
| } | ||||
| void TemplateBinarySensor::loop() { | ||||
|   if (this->f_ == nullptr) | ||||
|     return; | ||||
|   | ||||
| @@ -555,7 +555,7 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { | ||||
| void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) { | ||||
|   if (this->events_.empty()) | ||||
|     return; | ||||
|   this->events_.deferrable_send_state(obj, "state", binary_sensor_state_json_generator); | ||||
|   | ||||
| @@ -269,7 +269,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; | ||||
|   void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override; | ||||
|  | ||||
|   /// Handle a binary sensor request under '/binary_sensor/<id>'. | ||||
|   void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); | ||||
|   | ||||
| @@ -117,7 +117,9 @@ void Application::loop() { | ||||
|   // Use the last component's end time instead of calling millis() again | ||||
|   auto elapsed = last_op_end_time - this->last_loop_; | ||||
|   if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) { | ||||
|     yield(); | ||||
|     // Even if we overran the loop interval, we still need to select() | ||||
|     // to know if any sockets have data ready | ||||
|     this->yield_with_select_(0); | ||||
|   } else { | ||||
|     uint32_t delay_time = this->loop_interval_ - elapsed; | ||||
|     uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time); | ||||
| @@ -126,7 +128,7 @@ void Application::loop() { | ||||
|     next_schedule = std::max(next_schedule, delay_time / 2); | ||||
|     delay_time = std::min(next_schedule, delay_time); | ||||
|  | ||||
|     this->delay_with_select_(delay_time); | ||||
|     this->yield_with_select_(delay_time); | ||||
|   } | ||||
|   this->last_loop_ = last_op_end_time; | ||||
|  | ||||
| @@ -215,7 +217,7 @@ void Application::teardown_components(uint32_t timeout_ms) { | ||||
|  | ||||
|     // Give some time for I/O operations if components are still pending | ||||
|     if (!pending_components.empty()) { | ||||
|       this->delay_with_select_(1); | ||||
|       this->yield_with_select_(1); | ||||
|     } | ||||
|  | ||||
|     // Update time for next iteration | ||||
| @@ -293,8 +295,6 @@ bool Application::is_socket_ready(int fd) const { | ||||
|   // This function is thread-safe for reading the result of select() | ||||
|   // However, it should only be called after select() has been executed in the main loop | ||||
|   // The read_fds_ is only modified by select() in the main loop | ||||
|   if (HighFrequencyLoopRequester::is_high_frequency()) | ||||
|     return true;  // fd sets via select are not updated in high frequency looping - so force true fallback behavior | ||||
|   if (fd < 0 || fd >= FD_SETSIZE) | ||||
|     return false; | ||||
|  | ||||
| @@ -302,7 +302,9 @@ bool Application::is_socket_ready(int fd) const { | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void Application::delay_with_select_(uint32_t delay_ms) { | ||||
| void Application::yield_with_select_(uint32_t delay_ms) { | ||||
|   // Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run | ||||
|   // since select() with 0 timeout only polls without yielding. | ||||
| #ifdef USE_SOCKET_SELECT_SUPPORT | ||||
|   if (!this->socket_fds_.empty()) { | ||||
|     // Update fd_set if socket list has changed | ||||
| @@ -340,6 +342,10 @@ void Application::delay_with_select_(uint32_t delay_ms) { | ||||
|       ESP_LOGW(TAG, "select() failed with errno %d", errno); | ||||
|       delay(delay_ms); | ||||
|     } | ||||
|     // When delay_ms is 0, we need to yield since select(0) doesn't yield | ||||
|     if (delay_ms == 0) { | ||||
|       yield(); | ||||
|     } | ||||
|   } else { | ||||
|     // No sockets registered, use regular delay | ||||
|     delay(delay_ms); | ||||
|   | ||||
| @@ -575,7 +575,7 @@ class Application { | ||||
|   void feed_wdt_arch_(); | ||||
|  | ||||
|   /// Perform a delay while also monitoring socket file descriptors for readiness | ||||
|   void delay_with_select_(uint32_t delay_ms); | ||||
|   void yield_with_select_(uint32_t delay_ms); | ||||
|  | ||||
|   std::vector<Component *> components_{}; | ||||
|   std::vector<Component *> looping_components_{}; | ||||
|   | ||||
| @@ -7,8 +7,10 @@ namespace esphome { | ||||
| void Controller::setup_controller(bool include_internal) { | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   for (auto *obj : App.get_binary_sensors()) { | ||||
|     if (include_internal || !obj->is_internal()) | ||||
|       obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); }); | ||||
|     if (include_internal || !obj->is_internal()) { | ||||
|       obj->add_full_state_callback( | ||||
|           [this, obj](optional<bool> previous, optional<bool> state) { this->on_binary_sensor_update(obj); }); | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   | ||||
| @@ -71,7 +71,7 @@ class Controller { | ||||
|  public: | ||||
|   void setup_controller(bool include_internal = false); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){}; | ||||
|   virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj){}; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   virtual void on_fan_update(fan::Fan *obj){}; | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| #include <string> | ||||
| #include <cstdint> | ||||
| #include "string_ref.h" | ||||
| #include "helpers.h" | ||||
| #include "log.h" | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| @@ -29,7 +31,7 @@ class EntityBase { | ||||
|   // Get the unique Object ID of this Entity | ||||
|   uint32_t get_object_id_hash(); | ||||
|  | ||||
|   // Get/set whether this Entity should be hidden from outside of ESPHome | ||||
|   // Get/set whether this Entity should be hidden outside ESPHome | ||||
|   bool is_internal() const; | ||||
|   void set_internal(bool internal); | ||||
|  | ||||
| @@ -56,11 +58,12 @@ class EntityBase { | ||||
|   StringRef name_; | ||||
|   const char *object_id_c_str_{nullptr}; | ||||
|   const char *icon_c_str_{nullptr}; | ||||
|   uint32_t object_id_hash_; | ||||
|   uint32_t object_id_hash_{}; | ||||
|   bool has_own_name_{false}; | ||||
|   bool internal_{false}; | ||||
|   bool disabled_by_default_{false}; | ||||
|   EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; | ||||
|   bool has_state_{}; | ||||
| }; | ||||
|  | ||||
| class EntityBase_DeviceClass {  // NOLINT(readability-identifier-naming) | ||||
| @@ -85,4 +88,58 @@ class EntityBase_UnitOfMeasurement {  // NOLINT(readability-identifier-naming) | ||||
|   const char *unit_of_measurement_{nullptr};  ///< Unit of measurement override | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * An entity that has a state. | ||||
|  * @tparam T The type of the state | ||||
|  */ | ||||
| template<typename T> class StatefulEntityBase : public EntityBase { | ||||
|  public: | ||||
|   virtual bool has_state() const { return this->state_.has_value(); } | ||||
|   virtual const T &get_state() const { return this->state_.value(); } | ||||
|   virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); } | ||||
|   void invalidate_state() { this->set_state_({}); } | ||||
|  | ||||
|   void add_full_state_callback(std::function<void(optional<T> previous, optional<T> current)> &&callback) { | ||||
|     if (this->full_state_callbacks_ == nullptr) | ||||
|       this->full_state_callbacks_ = new CallbackManager<void(optional<T> previous, optional<T> current)>();  // NOLINT | ||||
|     this->full_state_callbacks_->add(std::move(callback)); | ||||
|   } | ||||
|   void add_on_state_callback(std::function<void(T)> &&callback) { | ||||
|     if (this->state_callbacks_ == nullptr) | ||||
|       this->state_callbacks_ = new CallbackManager<void(T)>();  // NOLINT | ||||
|     this->state_callbacks_->add(std::move(callback)); | ||||
|   } | ||||
|  | ||||
|   void set_trigger_on_initial_state(bool trigger_on_initial_state) { | ||||
|     this->trigger_on_initial_state_ = trigger_on_initial_state; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   optional<T> state_{}; | ||||
|   /** | ||||
|    * Set a new state for this entity. This will trigger callbacks only if the new state is different from the previous. | ||||
|    * | ||||
|    * @param state The new state. | ||||
|    * @return True if the state was changed, false if it was the same as before. | ||||
|    */ | ||||
|   bool set_state_(const optional<T> &state) { | ||||
|     if (this->state_ != state) { | ||||
|       // call the full state callbacks with the previous and new state | ||||
|       if (this->full_state_callbacks_ != nullptr) | ||||
|         this->full_state_callbacks_->call(this->state_, state); | ||||
|       // trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or | ||||
|       // the previous state was valid | ||||
|       auto had_state = this->has_state(); | ||||
|       this->state_ = state; | ||||
|       if (this->state_callbacks_ != nullptr && state.has_value() && (this->trigger_on_initial_state_ || had_state)) | ||||
|         this->state_callbacks_->call(state.value()); | ||||
|       return true; | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|   bool trigger_on_initial_state_{true}; | ||||
|   // callbacks with full state and previous state | ||||
|   CallbackManager<void(optional<T> previous, optional<T> current)> *full_state_callbacks_{}; | ||||
|   CallbackManager<void(T)> *state_callbacks_{}; | ||||
| }; | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -165,6 +165,8 @@ int esp_idf_log_vprintf_(const char *format, va_list args);  // NOLINT | ||||
| #define YESNO(b) ((b) ? "YES" : "NO") | ||||
| #define ONOFF(b) ((b) ? "ON" : "OFF") | ||||
| #define TRUEFALSE(b) ((b) ? "TRUE" : "FALSE") | ||||
| // for use with optional values | ||||
| #define ONOFFMAYBE(b) (((b).has_value()) ? ONOFF((b).value()) : "UNKNOWN") | ||||
|  | ||||
| // Helper class that identifies strings that may be stored in flash storage (similar to Arduino's __FlashStringHelper) | ||||
| struct LogString; | ||||
|   | ||||
| @@ -52,6 +52,11 @@ template<typename T> class optional {  // NOLINT | ||||
|     reset(); | ||||
|     return *this; | ||||
|   } | ||||
|   bool operator==(optional<T> const &rhs) const { | ||||
|     if (has_value() && rhs.has_value()) | ||||
|       return value() == rhs.value(); | ||||
|     return !has_value() && !rhs.has_value(); | ||||
|   } | ||||
|  | ||||
|   template<class U> optional &operator=(optional<U> const &other) { | ||||
|     has_value_ = other.has_value(); | ||||
|   | ||||
| @@ -6,7 +6,7 @@ pre-commit | ||||
|  | ||||
| # Unit tests | ||||
| pytest==8.4.0 | ||||
| pytest-cov==6.1.1 | ||||
| pytest-cov==6.2.1 | ||||
| pytest-mock==3.14.1 | ||||
| pytest-asyncio==0.26.0 | ||||
| pytest-xdist==3.7.0 | ||||
|   | ||||
							
								
								
									
										15
									
								
								tests/components/binary_sensor/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/components/binary_sensor/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| binary_sensor: | ||||
|   - platform: template | ||||
|     trigger_on_initial_state: true | ||||
|     id: some_binary_sensor | ||||
|     name: "Random binary" | ||||
|     lambda: return (random_uint32() & 1) == 0; | ||||
|     on_state_change: | ||||
|       then: | ||||
|         - logger.log: | ||||
|             format: "Old state was %s" | ||||
|             args: ['x_previous.has_value() ? ONOFF(x_previous) : "Unknown"'] | ||||
|         - logger.log: | ||||
|             format: "New state is %s" | ||||
|             args: ['x.has_value() ? ONOFF(x) : "Unknown"'] | ||||
|         - binary_sensor.invalidate_state: some_binary_sensor | ||||
							
								
								
									
										2
									
								
								tests/components/binary_sensor/test.bk72xx-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/binary_sensor/test.bk72xx-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/binary_sensor/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/binary_sensor/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/binary_sensor/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/binary_sensor/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/binary_sensor/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/binary_sensor/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/binary_sensor/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/binary_sensor/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/binary_sensor/test.esp32-s3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/binary_sensor/test.esp32-s3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/binary_sensor/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/binary_sensor/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
							
								
								
									
										2
									
								
								tests/components/binary_sensor/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tests/components/binary_sensor/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| packages: | ||||
|   common: !include common.yaml | ||||
| @@ -63,7 +63,7 @@ binary_sensor: | ||||
|     id: lvgl_pressbutton | ||||
|     name: Pressbutton | ||||
|     widget: spin_up | ||||
|     publish_initial_state: true | ||||
|     trigger_on_initial_state: true | ||||
|   - platform: lvgl | ||||
|     name: ButtonMatrix button | ||||
|     widget: button_a | ||||
|   | ||||
		Reference in New Issue
	
	Block a user