mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	wip
This commit is contained in:
		| @@ -10,6 +10,9 @@ from .. import template_ns | ||||
| TemplateBinarySensor = template_ns.class_( | ||||
|     "TemplateBinarySensor", binary_sensor.BinarySensor, cg.Component | ||||
| ) | ||||
| StatelessTemplateBinarySensor = template_ns.class_( | ||||
|     "StatelessTemplateBinarySensor", binary_sensor.BinarySensor, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     binary_sensor.binary_sensor_schema(TemplateBinarySensor) | ||||
| @@ -26,15 +29,22 @@ CONFIG_SCHEMA = ( | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await binary_sensor.new_binary_sensor(config) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     # Check if we have a lambda first - determines which class to instantiate | ||||
|     if lamb := config.get(CONF_LAMBDA): | ||||
|         # Use new_lambda_pvariable to create either TemplateBinarySensor or StatelessTemplateBinarySensor | ||||
|         template_ = await cg.process_lambda( | ||||
|             lamb, [], return_type=cg.optional.template(bool) | ||||
|         ) | ||||
|         cg.add(var.set_template(template_)) | ||||
|     if condition := config.get(CONF_CONDITION): | ||||
|         var = automation.new_lambda_pvariable( | ||||
|             config[CONF_ID], template_, StatelessTemplateBinarySensor | ||||
|         ) | ||||
|         # Manually register as binary sensor since we didn't use new_binary_sensor | ||||
|         await binary_sensor.register_binary_sensor(var, config) | ||||
|         await cg.register_component(var, config) | ||||
|     elif condition := config.get(CONF_CONDITION): | ||||
|         # For conditions, create stateful version and set template | ||||
|         var = await binary_sensor.new_binary_sensor(config) | ||||
|         await cg.register_component(var, config) | ||||
|         condition = await automation.build_condition( | ||||
|             condition, cg.TemplateArguments(), [] | ||||
|         ) | ||||
| @@ -42,6 +52,10 @@ async def to_code(config): | ||||
|             f"return {condition.check()};", [], return_type=cg.optional.template(bool) | ||||
|         ) | ||||
|         cg.add(var.set_template(template_)) | ||||
|     else: | ||||
|         # No lambda or condition - just create the base template sensor | ||||
|         var = await binary_sensor.new_binary_sensor(config) | ||||
|         await cg.register_component(var, config) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|   | ||||
| @@ -6,18 +6,13 @@ namespace template_ { | ||||
|  | ||||
| static const char *const TAG = "template.binary_sensor"; | ||||
|  | ||||
| void TemplateBinarySensor::setup() { this->loop(); } | ||||
|  | ||||
| void TemplateBinarySensor::loop() { | ||||
|   if (this->f_ == nullptr) | ||||
|     return; | ||||
|  | ||||
|   auto s = this->f_(); | ||||
|   if (s.has_value()) { | ||||
|     this->publish_state(*s); | ||||
|   } | ||||
| // Template instantiations | ||||
| template<typename F> void TemplateBinarySensorBase<F>::dump_config() { | ||||
|   LOG_BINARY_SENSOR("", "Template Binary Sensor", this); | ||||
| } | ||||
| void TemplateBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Template Binary Sensor", this); } | ||||
|  | ||||
| template class TemplateBinarySensorBase<std::function<optional<bool>()>>; | ||||
| template class TemplateBinarySensorBase<optional<bool> (*)()>; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -6,18 +6,41 @@ | ||||
| namespace esphome { | ||||
| namespace template_ { | ||||
|  | ||||
| class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor { | ||||
| template<typename F> class TemplateBinarySensorBase : public Component, public binary_sensor::BinarySensor { | ||||
|  public: | ||||
|   void set_template(std::function<optional<bool>()> &&f) { this->f_ = f; } | ||||
|   void setup() override { this->loop(); } | ||||
|  | ||||
|   void loop() override { | ||||
|     if (this->f_ == nullptr) | ||||
|       return; | ||||
|     auto s = this->f_(); | ||||
|     if (s.has_value()) { | ||||
|       this->publish_state(*s); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|  protected: | ||||
|   std::function<optional<bool>()> f_{nullptr}; | ||||
|   F f_; | ||||
| }; | ||||
|  | ||||
| class TemplateBinarySensor : public TemplateBinarySensorBase<std::function<optional<bool>()>> { | ||||
|  public: | ||||
|   TemplateBinarySensor() { this->f_ = nullptr; } | ||||
|   void set_template(std::function<optional<bool>()> &&f) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| /** Optimized template binary sensor for stateless lambdas (no capture). | ||||
|  * | ||||
|  * Uses function pointer instead of std::function to reduce memory overhead. | ||||
|  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||
|  */ | ||||
| class StatelessTemplateBinarySensor : public TemplateBinarySensorBase<optional<bool> (*)()> { | ||||
|  public: | ||||
|   explicit StatelessTemplateBinarySensor(optional<bool> (*f)()) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| }  // namespace template_ | ||||
|   | ||||
| @@ -8,14 +8,8 @@ using namespace esphome::cover; | ||||
|  | ||||
| static const char *const TAG = "template.cover"; | ||||
|  | ||||
| TemplateCover::TemplateCover() | ||||
|     : open_trigger_(new Trigger<>()), | ||||
|       close_trigger_(new Trigger<>), | ||||
|       stop_trigger_(new Trigger<>()), | ||||
|       toggle_trigger_(new Trigger<>()), | ||||
|       position_trigger_(new Trigger<float>()), | ||||
|       tilt_trigger_(new Trigger<float>()) {} | ||||
| void TemplateCover::setup() { | ||||
| // Template instantiations | ||||
| template<typename StateF, typename TiltF> void TemplateCoverBase<StateF, TiltF>::setup() { | ||||
|   switch (this->restore_mode_) { | ||||
|     case COVER_NO_RESTORE: | ||||
|       break; | ||||
| @@ -34,43 +28,12 @@ void TemplateCover::setup() { | ||||
|     } | ||||
|   } | ||||
| } | ||||
| void TemplateCover::loop() { | ||||
|   bool changed = false; | ||||
|  | ||||
|   if (this->state_f_.has_value()) { | ||||
|     auto s = (*this->state_f_)(); | ||||
|     if (s.has_value()) { | ||||
|       auto pos = clamp(*s, 0.0f, 1.0f); | ||||
|       if (pos != this->position) { | ||||
|         this->position = pos; | ||||
|         changed = true; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   if (this->tilt_f_.has_value()) { | ||||
|     auto s = (*this->tilt_f_)(); | ||||
|     if (s.has_value()) { | ||||
|       auto tilt = clamp(*s, 0.0f, 1.0f); | ||||
|       if (tilt != this->tilt) { | ||||
|         this->tilt = tilt; | ||||
|         changed = true; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (changed) | ||||
|     this->publish_state(); | ||||
| template<typename StateF, typename TiltF> void TemplateCoverBase<StateF, TiltF>::dump_config() { | ||||
|   LOG_COVER("", "Template Cover", this); | ||||
| } | ||||
| void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } | ||||
| void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } | ||||
| void TemplateCover::set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; } | ||||
| float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
| Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; } | ||||
| Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; } | ||||
| Trigger<> *TemplateCover::get_stop_trigger() const { return this->stop_trigger_; } | ||||
| Trigger<> *TemplateCover::get_toggle_trigger() const { return this->toggle_trigger_; } | ||||
| void TemplateCover::dump_config() { LOG_COVER("", "Template Cover", this); } | ||||
| void TemplateCover::control(const CoverCall &call) { | ||||
|  | ||||
| template<typename StateF, typename TiltF> void TemplateCoverBase<StateF, TiltF>::control(const CoverCall &call) { | ||||
|   if (call.get_stop()) { | ||||
|     this->stop_prev_trigger_(); | ||||
|     this->stop_trigger_->trigger(); | ||||
|   | ||||
| @@ -13,31 +13,59 @@ enum TemplateCoverRestoreMode { | ||||
|   COVER_RESTORE_AND_CALL, | ||||
| }; | ||||
|  | ||||
| class TemplateCover : public cover::Cover, public Component { | ||||
| template<typename StateF, typename TiltF> class TemplateCoverBase : public cover::Cover, public Component { | ||||
|  public: | ||||
|   TemplateCover(); | ||||
|   TemplateCoverBase() | ||||
|       : open_trigger_(new Trigger<>()), | ||||
|         close_trigger_(new Trigger<>()), | ||||
|         stop_trigger_(new Trigger<>()), | ||||
|         toggle_trigger_(new Trigger<>()), | ||||
|         position_trigger_(new Trigger<float>()), | ||||
|         tilt_trigger_(new Trigger<float>()) {} | ||||
|  | ||||
|   void set_state_lambda(std::function<optional<float>()> &&f); | ||||
|   Trigger<> *get_open_trigger() const; | ||||
|   Trigger<> *get_close_trigger() const; | ||||
|   Trigger<> *get_stop_trigger() const; | ||||
|   Trigger<> *get_toggle_trigger() const; | ||||
|   Trigger<float> *get_position_trigger() const; | ||||
|   Trigger<float> *get_tilt_trigger() const; | ||||
|   void set_optimistic(bool optimistic); | ||||
|   void set_assumed_state(bool assumed_state); | ||||
|   void set_tilt_lambda(std::function<optional<float>()> &&tilt_f); | ||||
|   void set_has_stop(bool has_stop); | ||||
|   void set_has_position(bool has_position); | ||||
|   void set_has_tilt(bool has_tilt); | ||||
|   void set_has_toggle(bool has_toggle); | ||||
|   void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } | ||||
|   void loop() override { | ||||
|     bool changed = false; | ||||
|     if (this->state_f_.has_value()) { | ||||
|       auto s = (*this->state_f_)(); | ||||
|       if (s.has_value()) { | ||||
|         auto pos = clamp(*s, 0.0f, 1.0f); | ||||
|         if (pos != this->position) { | ||||
|           this->position = pos; | ||||
|           changed = true; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     if (this->tilt_f_.has_value()) { | ||||
|       auto s = (*this->tilt_f_)(); | ||||
|       if (s.has_value()) { | ||||
|         auto tilt = clamp(*s, 0.0f, 1.0f); | ||||
|         if (tilt != this->tilt) { | ||||
|           this->tilt = tilt; | ||||
|           changed = true; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     if (changed) | ||||
|       this->publish_state(); | ||||
|   } | ||||
|  | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|   Trigger<> *get_open_trigger() const { return this->open_trigger_; } | ||||
|   Trigger<> *get_close_trigger() const { return this->close_trigger_; } | ||||
|   Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } | ||||
|   Trigger<> *get_toggle_trigger() const { return this->toggle_trigger_; } | ||||
|   Trigger<float> *get_position_trigger() const { return this->position_trigger_; } | ||||
|   Trigger<float> *get_tilt_trigger() const { return this->tilt_trigger_; } | ||||
|   void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } | ||||
|   void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } | ||||
|   void set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } | ||||
|   void set_has_position(bool has_position) { this->has_position_ = has_position; } | ||||
|   void set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; } | ||||
|   void set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; } | ||||
|   void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } | ||||
|  | ||||
|  protected: | ||||
|   void control(const cover::CoverCall &call) override; | ||||
| @@ -45,8 +73,8 @@ class TemplateCover : public cover::Cover, public Component { | ||||
|   void stop_prev_trigger_(); | ||||
|  | ||||
|   TemplateCoverRestoreMode restore_mode_{COVER_RESTORE}; | ||||
|   optional<std::function<optional<float>()>> state_f_; | ||||
|   optional<std::function<optional<float>()>> tilt_f_; | ||||
|   optional<StateF> state_f_; | ||||
|   optional<TiltF> tilt_f_; | ||||
|   bool assumed_state_{false}; | ||||
|   bool optimistic_{false}; | ||||
|   Trigger<> *open_trigger_; | ||||
| @@ -62,5 +90,22 @@ class TemplateCover : public cover::Cover, public Component { | ||||
|   bool has_tilt_{false}; | ||||
| }; | ||||
|  | ||||
| class TemplateCover : public TemplateCoverBase<std::function<optional<float>()>, std::function<optional<float>()>> { | ||||
|  public: | ||||
|   void set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; } | ||||
|   void set_tilt_lambda(std::function<optional<float>()> &&tilt_f) { this->tilt_f_ = tilt_f; } | ||||
| }; | ||||
|  | ||||
| /** Optimized template cover for stateless lambdas (no capture). | ||||
|  * | ||||
|  * Uses function pointers instead of std::function to reduce memory overhead. | ||||
|  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function) per lambda. | ||||
|  */ | ||||
| class StatelessTemplateCover : public TemplateCoverBase<optional<float> (*)(), optional<float> (*)()> { | ||||
|  public: | ||||
|   explicit StatelessTemplateCover(optional<float> (*state_f)()) { this->state_f_ = state_f; } | ||||
|   void set_tilt_lambda(optional<float> (*tilt_f)()) { this->tilt_f_ = tilt_f; } | ||||
| }; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -19,6 +19,9 @@ from .. import template_ns | ||||
| TemplateNumber = template_ns.class_( | ||||
|     "TemplateNumber", number.Number, cg.PollingComponent | ||||
| ) | ||||
| StatelessTemplateNumber = template_ns.class_( | ||||
|     "StatelessTemplateNumber", number.Number, cg.PollingComponent | ||||
| ) | ||||
|  | ||||
|  | ||||
| def validate_min_max(config): | ||||
| @@ -66,23 +69,33 @@ CONFIG_SCHEMA = cv.All( | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await number.register_number( | ||||
|         var, | ||||
|         config, | ||||
|         min_value=config[CONF_MIN_VALUE], | ||||
|         max_value=config[CONF_MAX_VALUE], | ||||
|         step=config[CONF_STEP], | ||||
|     ) | ||||
|  | ||||
|     if CONF_LAMBDA in config: | ||||
|         # Use new_lambda_pvariable to create either TemplateNumber or StatelessTemplateNumber | ||||
|         template_ = await cg.process_lambda( | ||||
|             config[CONF_LAMBDA], [], return_type=cg.optional.template(float) | ||||
|         ) | ||||
|         cg.add(var.set_template(template_)) | ||||
|  | ||||
|         var = automation.new_lambda_pvariable( | ||||
|             config[CONF_ID], template_, StatelessTemplateNumber | ||||
|         ) | ||||
|         await cg.register_component(var, config) | ||||
|         await number.register_number( | ||||
|             var, | ||||
|             config, | ||||
|             min_value=config[CONF_MIN_VALUE], | ||||
|             max_value=config[CONF_MAX_VALUE], | ||||
|             step=config[CONF_STEP], | ||||
|         ) | ||||
|     else: | ||||
|         # No lambda - just create the base template number | ||||
|         var = cg.new_Pvariable(config[CONF_ID]) | ||||
|         await cg.register_component(var, config) | ||||
|         await number.register_number( | ||||
|             var, | ||||
|             config, | ||||
|             min_value=config[CONF_MIN_VALUE], | ||||
|             max_value=config[CONF_MAX_VALUE], | ||||
|             step=config[CONF_STEP], | ||||
|         ) | ||||
|         cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) | ||||
|         cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) | ||||
|         if CONF_RESTORE_VALUE in config: | ||||
|   | ||||
| @@ -6,7 +6,8 @@ namespace template_ { | ||||
|  | ||||
| static const char *const TAG = "template.number"; | ||||
|  | ||||
| void TemplateNumber::setup() { | ||||
| // Template instantiations | ||||
| template<typename F> void TemplateNumberBase<F>::setup() { | ||||
|   if (this->f_.has_value()) | ||||
|     return; | ||||
|  | ||||
| @@ -26,18 +27,7 @@ void TemplateNumber::setup() { | ||||
|   this->publish_state(value); | ||||
| } | ||||
|  | ||||
| void TemplateNumber::update() { | ||||
|   if (!this->f_.has_value()) | ||||
|     return; | ||||
|  | ||||
|   auto val = (*this->f_)(); | ||||
|   if (!val.has_value()) | ||||
|     return; | ||||
|  | ||||
|   this->publish_state(*val); | ||||
| } | ||||
|  | ||||
| void TemplateNumber::control(float value) { | ||||
| template<typename F> void TemplateNumberBase<F>::control(float value) { | ||||
|   this->set_trigger_->trigger(value); | ||||
|  | ||||
|   if (this->optimistic_) | ||||
| @@ -46,11 +36,15 @@ void TemplateNumber::control(float value) { | ||||
|   if (this->restore_value_) | ||||
|     this->pref_.save(&value); | ||||
| } | ||||
| void TemplateNumber::dump_config() { | ||||
|  | ||||
| template<typename F> void TemplateNumberBase<F>::dump_config() { | ||||
|   LOG_NUMBER("", "Template Number", this); | ||||
|   ESP_LOGCONFIG(TAG, "  Optimistic: %s", YESNO(this->optimistic_)); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| template class TemplateNumberBase<std::function<optional<float>()>>; | ||||
| template class TemplateNumberBase<optional<float> (*)()>; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -8,13 +8,22 @@ | ||||
| namespace esphome { | ||||
| namespace template_ { | ||||
|  | ||||
| class TemplateNumber : public number::Number, public PollingComponent { | ||||
| template<typename F> class TemplateNumberBase : public number::Number, public PollingComponent { | ||||
|  public: | ||||
|   void set_template(std::function<optional<float>()> &&f) { this->f_ = f; } | ||||
|   TemplateNumberBase() : set_trigger_(new Trigger<float>()) {} | ||||
|  | ||||
|   void setup() override; | ||||
|   void update() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void update() override { | ||||
|     if (!this->f_.has_value()) | ||||
|       return; | ||||
|     auto val = (*this->f_)(); | ||||
|     if (!val.has_value()) | ||||
|       return; | ||||
|     this->publish_state(*val); | ||||
|   } | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   Trigger<float> *get_set_trigger() const { return set_trigger_; } | ||||
| @@ -27,11 +36,26 @@ class TemplateNumber : public number::Number, public PollingComponent { | ||||
|   bool optimistic_{false}; | ||||
|   float initial_value_{NAN}; | ||||
|   bool restore_value_{false}; | ||||
|   Trigger<float> *set_trigger_ = new Trigger<float>(); | ||||
|   optional<std::function<optional<float>()>> f_; | ||||
|   Trigger<float> *set_trigger_; | ||||
|   optional<F> f_; | ||||
|  | ||||
|   ESPPreferenceObject pref_; | ||||
| }; | ||||
|  | ||||
| class TemplateNumber : public TemplateNumberBase<std::function<optional<float>()>> { | ||||
|  public: | ||||
|   void set_template(std::function<optional<float>()> &&f) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| /** Optimized template number for stateless lambdas (no capture). | ||||
|  * | ||||
|  * Uses function pointer instead of std::function to reduce memory overhead. | ||||
|  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||
|  */ | ||||
| class StatelessTemplateNumber : public TemplateNumberBase<optional<float> (*)()> { | ||||
|  public: | ||||
|   explicit StatelessTemplateNumber(optional<float> (*f)()) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -17,6 +17,9 @@ from .. import template_ns | ||||
| TemplateSelect = template_ns.class_( | ||||
|     "TemplateSelect", select.Select, cg.PollingComponent | ||||
| ) | ||||
| StatelessTemplateSelect = template_ns.class_( | ||||
|     "StatelessTemplateSelect", select.Select, cg.PollingComponent | ||||
| ) | ||||
|  | ||||
|  | ||||
| def validate(config): | ||||
| @@ -62,17 +65,22 @@ CONFIG_SCHEMA = cv.All( | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await select.register_select(var, config, options=config[CONF_OPTIONS]) | ||||
|  | ||||
|     if CONF_LAMBDA in config: | ||||
|         # Use new_lambda_pvariable to create either TemplateSelect or StatelessTemplateSelect | ||||
|         template_ = await cg.process_lambda( | ||||
|             config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) | ||||
|         ) | ||||
|         cg.add(var.set_template(template_)) | ||||
|  | ||||
|         var = automation.new_lambda_pvariable( | ||||
|             config[CONF_ID], template_, StatelessTemplateSelect | ||||
|         ) | ||||
|         await cg.register_component(var, config) | ||||
|         await select.register_select(var, config, options=config[CONF_OPTIONS]) | ||||
|     else: | ||||
|         # No lambda - just create the base template select | ||||
|         var = cg.new_Pvariable(config[CONF_ID]) | ||||
|         await cg.register_component(var, config) | ||||
|         await select.register_select(var, config, options=config[CONF_OPTIONS]) | ||||
|  | ||||
|         # Only set if non-default to avoid bloating setup() function | ||||
|         if config[CONF_OPTIMISTIC]: | ||||
|             cg.add(var.set_optimistic(True)) | ||||
|   | ||||
| @@ -6,7 +6,8 @@ namespace template_ { | ||||
|  | ||||
| static const char *const TAG = "template.select"; | ||||
|  | ||||
| void TemplateSelect::setup() { | ||||
| // Template instantiations | ||||
| template<typename F> void TemplateSelectBase<F>::setup() { | ||||
|   if (this->f_.has_value()) | ||||
|     return; | ||||
|  | ||||
| @@ -27,23 +28,7 @@ void TemplateSelect::setup() { | ||||
|   this->publish_state(this->at(index).value()); | ||||
| } | ||||
|  | ||||
| void TemplateSelect::update() { | ||||
|   if (!this->f_.has_value()) | ||||
|     return; | ||||
|  | ||||
|   auto val = (*this->f_)(); | ||||
|   if (!val.has_value()) | ||||
|     return; | ||||
|  | ||||
|   if (!this->has_option(*val)) { | ||||
|     ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str()); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->publish_state(*val); | ||||
| } | ||||
|  | ||||
| void TemplateSelect::control(const std::string &value) { | ||||
| template<typename F> void TemplateSelectBase<F>::control(const std::string &value) { | ||||
|   this->set_trigger_->trigger(value); | ||||
|  | ||||
|   if (this->optimistic_) | ||||
| @@ -55,7 +40,7 @@ void TemplateSelect::control(const std::string &value) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| void TemplateSelect::dump_config() { | ||||
| template<typename F> void TemplateSelectBase<F>::dump_config() { | ||||
|   LOG_SELECT("", "Template Select", this); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
|   if (this->f_.has_value()) | ||||
| @@ -68,5 +53,8 @@ void TemplateSelect::dump_config() { | ||||
|                 YESNO(this->restore_value_)); | ||||
| } | ||||
|  | ||||
| template class TemplateSelectBase<std::function<optional<std::string>()>>; | ||||
| template class TemplateSelectBase<optional<std::string> (*)()>; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -8,13 +8,26 @@ | ||||
| namespace esphome { | ||||
| namespace template_ { | ||||
|  | ||||
| class TemplateSelect : public select::Select, public PollingComponent { | ||||
| template<typename F> class TemplateSelectBase : public select::Select, public PollingComponent { | ||||
|  public: | ||||
|   void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; } | ||||
|   TemplateSelectBase() : set_trigger_(new Trigger<std::string>()) {} | ||||
|  | ||||
|   void setup() override; | ||||
|   void update() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void update() override { | ||||
|     if (!this->f_.has_value()) | ||||
|       return; | ||||
|     auto val = (*this->f_)(); | ||||
|     if (!val.has_value()) | ||||
|       return; | ||||
|     if (!this->has_option(*val)) { | ||||
|       ESP_LOGE("template.select", "Lambda returned an invalid option: %s", (*val).c_str()); | ||||
|       return; | ||||
|     } | ||||
|     this->publish_state(*val); | ||||
|   } | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; } | ||||
| @@ -27,11 +40,26 @@ class TemplateSelect : public select::Select, public PollingComponent { | ||||
|   bool optimistic_ = false; | ||||
|   size_t initial_option_index_{0}; | ||||
|   bool restore_value_ = false; | ||||
|   Trigger<std::string> *set_trigger_ = new Trigger<std::string>(); | ||||
|   optional<std::function<optional<std::string>()>> f_; | ||||
|   Trigger<std::string> *set_trigger_; | ||||
|   optional<F> f_; | ||||
|  | ||||
|   ESPPreferenceObject pref_; | ||||
| }; | ||||
|  | ||||
| class TemplateSelect : public TemplateSelectBase<std::function<optional<std::string>()>> { | ||||
|  public: | ||||
|   void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| /** Optimized template select for stateless lambdas (no capture). | ||||
|  * | ||||
|  * Uses function pointer instead of std::function to reduce memory overhead. | ||||
|  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||
|  */ | ||||
| class StatelessTemplateSelect : public TemplateSelectBase<optional<std::string> (*)()> { | ||||
|  public: | ||||
|   explicit StatelessTemplateSelect(optional<std::string> (*f)()) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -9,6 +9,9 @@ from .. import template_ns | ||||
| TemplateSensor = template_ns.class_( | ||||
|     "TemplateSensor", sensor.Sensor, cg.PollingComponent | ||||
| ) | ||||
| StatelessTemplateSensor = template_ns.class_( | ||||
|     "StatelessTemplateSensor", sensor.Sensor, cg.PollingComponent | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema( | ||||
| @@ -25,14 +28,21 @@ CONFIG_SCHEMA = ( | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await sensor.new_sensor(config) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     if CONF_LAMBDA in config: | ||||
|         # Use new_lambda_pvariable to create either TemplateSensor or StatelessTemplateSensor | ||||
|         template_ = await cg.process_lambda( | ||||
|             config[CONF_LAMBDA], [], return_type=cg.optional.template(float) | ||||
|         ) | ||||
|         cg.add(var.set_template(template_)) | ||||
|         var = automation.new_lambda_pvariable( | ||||
|             config[CONF_ID], template_, StatelessTemplateSensor | ||||
|         ) | ||||
|         # Manually register as sensor since we didn't use new_sensor | ||||
|         await sensor.register_sensor(var, config) | ||||
|         await cg.register_component(var, config) | ||||
|     else: | ||||
|         # No lambda - just create the base template sensor | ||||
|         var = await sensor.new_sensor(config) | ||||
|         await cg.register_component(var, config) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|   | ||||
| @@ -7,21 +7,14 @@ namespace template_ { | ||||
|  | ||||
| static const char *const TAG = "template.sensor"; | ||||
|  | ||||
| void TemplateSensor::update() { | ||||
|   if (!this->f_.has_value()) | ||||
|     return; | ||||
|  | ||||
|   auto val = (*this->f_)(); | ||||
|   if (val.has_value()) { | ||||
|     this->publish_state(*val); | ||||
|   } | ||||
| } | ||||
| float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
| void TemplateSensor::set_template(std::function<optional<float>()> &&f) { this->f_ = f; } | ||||
| void TemplateSensor::dump_config() { | ||||
| // Template instantiations | ||||
| template<typename F> void TemplateSensorBase<F>::dump_config() { | ||||
|   LOG_SENSOR("", "Template Sensor", this); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| template class TemplateSensorBase<std::function<optional<float>()>>; | ||||
| template class TemplateSensorBase<optional<float> (*)()>; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -6,18 +6,38 @@ | ||||
| namespace esphome { | ||||
| namespace template_ { | ||||
|  | ||||
| class TemplateSensor : public sensor::Sensor, public PollingComponent { | ||||
| template<typename F> class TemplateSensorBase : public sensor::Sensor, public PollingComponent { | ||||
|  public: | ||||
|   void set_template(std::function<optional<float>()> &&f); | ||||
|  | ||||
|   void update() override; | ||||
|   void update() override { | ||||
|     if (!this->f_.has_value()) | ||||
|       return; | ||||
|     auto val = (*this->f_)(); | ||||
|     if (val.has_value()) { | ||||
|       this->publish_state(*val); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|  protected: | ||||
|   optional<std::function<optional<float>()>> f_; | ||||
|   optional<F> f_; | ||||
| }; | ||||
|  | ||||
| class TemplateSensor : public TemplateSensorBase<std::function<optional<float>()>> { | ||||
|  public: | ||||
|   void set_template(std::function<optional<float>()> &&f) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| /** Optimized template sensor for stateless lambdas (no capture). | ||||
|  * | ||||
|  * Uses function pointer instead of std::function to reduce memory overhead. | ||||
|  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||
|  */ | ||||
| class StatelessTemplateSensor : public TemplateSensorBase<optional<float> (*)()> { | ||||
|  public: | ||||
|   explicit StatelessTemplateSensor(optional<float> (*f)()) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| }  // namespace template_ | ||||
|   | ||||
| @@ -16,6 +16,9 @@ from esphome.const import ( | ||||
| from .. import template_ns | ||||
|  | ||||
| TemplateSwitch = template_ns.class_("TemplateSwitch", switch.Switch, cg.Component) | ||||
| StatelessTemplateSwitch = template_ns.class_( | ||||
|     "StatelessTemplateSwitch", switch.Switch, cg.Component | ||||
| ) | ||||
|  | ||||
|  | ||||
| def validate(config): | ||||
| @@ -55,14 +58,22 @@ CONFIG_SCHEMA = cv.All( | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await switch.new_switch(config) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     if CONF_LAMBDA in config: | ||||
|         # Use new_lambda_pvariable to create either TemplateSwitch or StatelessTemplateSwitch | ||||
|         template_ = await cg.process_lambda( | ||||
|             config[CONF_LAMBDA], [], return_type=cg.optional.template(bool) | ||||
|         ) | ||||
|         cg.add(var.set_state_lambda(template_)) | ||||
|         var = automation.new_lambda_pvariable( | ||||
|             config[CONF_ID], template_, StatelessTemplateSwitch | ||||
|         ) | ||||
|         # Manually register as switch since we didn't use new_switch | ||||
|         await switch.register_switch(var, config) | ||||
|         await cg.register_component(var, config) | ||||
|     else: | ||||
|         # No lambda - just create the base template switch | ||||
|         var = await switch.new_switch(config) | ||||
|         await cg.register_component(var, config) | ||||
|  | ||||
|     if CONF_TURN_OFF_ACTION in config: | ||||
|         await automation.build_automation( | ||||
|             var.get_turn_off_trigger(), [], config[CONF_TURN_OFF_ACTION] | ||||
|   | ||||
| @@ -6,18 +6,8 @@ namespace template_ { | ||||
|  | ||||
| static const char *const TAG = "template.switch"; | ||||
|  | ||||
| TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {} | ||||
|  | ||||
| void TemplateSwitch::loop() { | ||||
|   if (!this->f_.has_value()) | ||||
|     return; | ||||
|   auto s = (*this->f_)(); | ||||
|   if (!s.has_value()) | ||||
|     return; | ||||
|  | ||||
|   this->publish_state(*s); | ||||
| } | ||||
| void TemplateSwitch::write_state(bool state) { | ||||
| // Template instantiations | ||||
| template<typename F> void TemplateSwitchBase<F>::write_state(bool state) { | ||||
|   if (this->prev_trigger_ != nullptr) { | ||||
|     this->prev_trigger_->stop_action(); | ||||
|   } | ||||
| @@ -33,13 +23,8 @@ void TemplateSwitch::write_state(bool state) { | ||||
|   if (this->optimistic_) | ||||
|     this->publish_state(state); | ||||
| } | ||||
| void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } | ||||
| bool TemplateSwitch::assumed_state() { return this->assumed_state_; } | ||||
| void TemplateSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; } | ||||
| float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; } | ||||
| Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; } | ||||
| Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; } | ||||
| void TemplateSwitch::setup() { | ||||
|  | ||||
| template<typename F> void TemplateSwitchBase<F>::setup() { | ||||
|   optional<bool> initial_state = this->get_initial_state_with_restore_mode(); | ||||
|  | ||||
|   if (initial_state.has_value()) { | ||||
| @@ -52,11 +37,14 @@ void TemplateSwitch::setup() { | ||||
|     } | ||||
|   } | ||||
| } | ||||
| void TemplateSwitch::dump_config() { | ||||
|  | ||||
| template<typename F> void TemplateSwitchBase<F>::dump_config() { | ||||
|   LOG_SWITCH("", "Template Switch", this); | ||||
|   ESP_LOGCONFIG(TAG, "  Optimistic: %s", YESNO(this->optimistic_)); | ||||
| } | ||||
| void TemplateSwitch::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } | ||||
|  | ||||
| template class TemplateSwitchBase<std::function<optional<bool>()>>; | ||||
| template class TemplateSwitchBase<optional<bool> (*)()>; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -7,28 +7,35 @@ | ||||
| namespace esphome { | ||||
| namespace template_ { | ||||
|  | ||||
| class TemplateSwitch : public switch_::Switch, public Component { | ||||
| template<typename F> class TemplateSwitchBase : public switch_::Switch, public Component { | ||||
|  public: | ||||
|   TemplateSwitch(); | ||||
|   TemplateSwitchBase() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {} | ||||
|  | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void set_state_lambda(std::function<optional<bool>()> &&f); | ||||
|   Trigger<> *get_turn_on_trigger() const; | ||||
|   Trigger<> *get_turn_off_trigger() const; | ||||
|   void set_optimistic(bool optimistic); | ||||
|   void set_assumed_state(bool assumed_state); | ||||
|   void loop() override; | ||||
|   void loop() override { | ||||
|     if (!this->f_.has_value()) | ||||
|       return; | ||||
|     auto s = (*this->f_)(); | ||||
|     if (!s.has_value()) | ||||
|       return; | ||||
|     this->publish_state(*s); | ||||
|   } | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|   Trigger<> *get_turn_on_trigger() const { return this->turn_on_trigger_; } | ||||
|   Trigger<> *get_turn_off_trigger() const { return this->turn_off_trigger_; } | ||||
|   void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } | ||||
|   void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE - 2.0f; } | ||||
|  | ||||
|  protected: | ||||
|   bool assumed_state() override; | ||||
|   bool assumed_state() override { return this->assumed_state_; } | ||||
|  | ||||
|   void write_state(bool state) override; | ||||
|  | ||||
|   optional<std::function<optional<bool>()>> f_; | ||||
|   optional<F> f_; | ||||
|   bool optimistic_{false}; | ||||
|   bool assumed_state_{false}; | ||||
|   Trigger<> *turn_on_trigger_; | ||||
| @@ -36,5 +43,20 @@ class TemplateSwitch : public switch_::Switch, public Component { | ||||
|   Trigger<> *prev_trigger_{nullptr}; | ||||
| }; | ||||
|  | ||||
| class TemplateSwitch : public TemplateSwitchBase<std::function<optional<bool>()>> { | ||||
|  public: | ||||
|   void set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| /** Optimized template switch for stateless lambdas (no capture). | ||||
|  * | ||||
|  * Uses function pointer instead of std::function to reduce memory overhead. | ||||
|  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||
|  */ | ||||
| class StatelessTemplateSwitch : public TemplateSwitchBase<optional<bool> (*)()> { | ||||
|  public: | ||||
|   explicit StatelessTemplateSwitch(optional<bool> (*f)()) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import esphome.codegen as cg | ||||
| from esphome.components import text | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_INITIAL_VALUE, | ||||
|     CONF_LAMBDA, | ||||
|     CONF_MAX_LENGTH, | ||||
| @@ -16,6 +17,9 @@ from esphome.const import ( | ||||
| from .. import template_ns | ||||
|  | ||||
| TemplateText = template_ns.class_("TemplateText", text.Text, cg.PollingComponent) | ||||
| StatelessTemplateText = template_ns.class_( | ||||
|     "StatelessTemplateText", text.Text, cg.PollingComponent | ||||
| ) | ||||
|  | ||||
| TextSaverBase = template_ns.class_("TemplateTextSaverBase") | ||||
| TextSaverTemplate = template_ns.class_("TextSaver", TextSaverBase) | ||||
| @@ -65,21 +69,31 @@ CONFIG_SCHEMA = cv.All( | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await text.new_text( | ||||
|         config, | ||||
|         min_length=config[CONF_MIN_LENGTH], | ||||
|         max_length=config[CONF_MAX_LENGTH], | ||||
|         pattern=config.get(CONF_PATTERN), | ||||
|     ) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     if CONF_LAMBDA in config: | ||||
|         # Use new_lambda_pvariable to create either TemplateText or StatelessTemplateText | ||||
|         template_ = await cg.process_lambda( | ||||
|             config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) | ||||
|         ) | ||||
|         cg.add(var.set_template(template_)) | ||||
|  | ||||
|         var = automation.new_lambda_pvariable( | ||||
|             config[CONF_ID], template_, StatelessTemplateText | ||||
|         ) | ||||
|         await cg.register_component(var, config) | ||||
|         await text.register_text( | ||||
|             var, | ||||
|             config, | ||||
|             min_length=config[CONF_MIN_LENGTH], | ||||
|             max_length=config[CONF_MAX_LENGTH], | ||||
|             pattern=config.get(CONF_PATTERN), | ||||
|         ) | ||||
|     else: | ||||
|         # No lambda - just create the base template text | ||||
|         var = await text.new_text( | ||||
|             config, | ||||
|             min_length=config[CONF_MIN_LENGTH], | ||||
|             max_length=config[CONF_MAX_LENGTH], | ||||
|             pattern=config.get(CONF_PATTERN), | ||||
|         ) | ||||
|         await cg.register_component(var, config) | ||||
|         cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) | ||||
|         if initial_value_config := config.get(CONF_INITIAL_VALUE): | ||||
|             cg.add(var.set_initial_value(initial_value_config)) | ||||
|   | ||||
| @@ -6,7 +6,8 @@ namespace template_ { | ||||
|  | ||||
| static const char *const TAG = "template.text"; | ||||
|  | ||||
| void TemplateText::setup() { | ||||
| // Template instantiations | ||||
| template<typename F> void TemplateTextBase<F>::setup() { | ||||
|   if (!(this->f_ == nullptr)) { | ||||
|     if (this->f_.has_value()) | ||||
|       return; | ||||
| @@ -25,21 +26,7 @@ void TemplateText::setup() { | ||||
|     this->publish_state(value); | ||||
| } | ||||
|  | ||||
| void TemplateText::update() { | ||||
|   if (this->f_ == nullptr) | ||||
|     return; | ||||
|  | ||||
|   if (!this->f_.has_value()) | ||||
|     return; | ||||
|  | ||||
|   auto val = (*this->f_)(); | ||||
|   if (!val.has_value()) | ||||
|     return; | ||||
|  | ||||
|   this->publish_state(*val); | ||||
| } | ||||
|  | ||||
| void TemplateText::control(const std::string &value) { | ||||
| template<typename F> void TemplateTextBase<F>::control(const std::string &value) { | ||||
|   this->set_trigger_->trigger(value); | ||||
|  | ||||
|   if (this->optimistic_) | ||||
| @@ -51,11 +38,15 @@ void TemplateText::control(const std::string &value) { | ||||
|     } | ||||
|   } | ||||
| } | ||||
| void TemplateText::dump_config() { | ||||
|  | ||||
| template<typename F> void TemplateTextBase<F>::dump_config() { | ||||
|   LOG_TEXT("", "Template Text Input", this); | ||||
|   ESP_LOGCONFIG(TAG, "  Optimistic: %s", YESNO(this->optimistic_)); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| template class TemplateTextBase<std::function<optional<std::string>()>>; | ||||
| template class TemplateTextBase<optional<std::string> (*)()>; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -59,13 +59,24 @@ template<uint8_t SZ> class TextSaver : public TemplateTextSaverBase { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class TemplateText : public text::Text, public PollingComponent { | ||||
| template<typename F> class TemplateTextBase : public text::Text, public PollingComponent { | ||||
|  public: | ||||
|   void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; } | ||||
|   TemplateTextBase() : set_trigger_(new Trigger<std::string>()) {} | ||||
|  | ||||
|   void setup() override; | ||||
|   void update() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void update() override { | ||||
|     if (this->f_ == nullptr) | ||||
|       return; | ||||
|     if (!this->f_.has_value()) | ||||
|       return; | ||||
|     auto val = (*this->f_)(); | ||||
|     if (!val.has_value()) | ||||
|       return; | ||||
|     this->publish_state(*val); | ||||
|   } | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; } | ||||
| @@ -77,11 +88,26 @@ class TemplateText : public text::Text, public PollingComponent { | ||||
|   void control(const std::string &value) override; | ||||
|   bool optimistic_ = false; | ||||
|   std::string initial_value_; | ||||
|   Trigger<std::string> *set_trigger_ = new Trigger<std::string>(); | ||||
|   optional<std::function<optional<std::string>()>> f_{nullptr}; | ||||
|   Trigger<std::string> *set_trigger_; | ||||
|   optional<F> f_{nullptr}; | ||||
|  | ||||
|   TemplateTextSaverBase *pref_ = nullptr; | ||||
| }; | ||||
|  | ||||
| class TemplateText : public TemplateTextBase<std::function<optional<std::string>()>> { | ||||
|  public: | ||||
|   void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| /** Optimized template text for stateless lambdas (no capture). | ||||
|  * | ||||
|  * Uses function pointer instead of std::function to reduce memory overhead. | ||||
|  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||
|  */ | ||||
| class StatelessTemplateText : public TemplateTextBase<optional<std::string> (*)()> { | ||||
|  public: | ||||
|   explicit StatelessTemplateText(optional<std::string> (*f)()) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -10,6 +10,9 @@ from .. import template_ns | ||||
| TemplateTextSensor = template_ns.class_( | ||||
|     "TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent | ||||
| ) | ||||
| StatelessTemplateTextSensor = template_ns.class_( | ||||
|     "StatelessTemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     text_sensor.text_sensor_schema() | ||||
| @@ -24,14 +27,21 @@ CONFIG_SCHEMA = ( | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await text_sensor.new_text_sensor(config) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     if CONF_LAMBDA in config: | ||||
|         # Use new_lambda_pvariable to create either TemplateTextSensor or StatelessTemplateTextSensor | ||||
|         template_ = await cg.process_lambda( | ||||
|             config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) | ||||
|         ) | ||||
|         cg.add(var.set_template(template_)) | ||||
|         var = automation.new_lambda_pvariable( | ||||
|             config[CONF_ID], template_, StatelessTemplateTextSensor | ||||
|         ) | ||||
|         # Manually register as text sensor since we didn't use new_text_sensor | ||||
|         await text_sensor.register_text_sensor(var, config) | ||||
|         await cg.register_component(var, config) | ||||
|     else: | ||||
|         # No lambda - just create the base template text sensor | ||||
|         var = await text_sensor.new_text_sensor(config) | ||||
|         await cg.register_component(var, config) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|   | ||||
| @@ -6,18 +6,11 @@ namespace template_ { | ||||
|  | ||||
| static const char *const TAG = "template.text_sensor"; | ||||
|  | ||||
| void TemplateTextSensor::update() { | ||||
|   if (!this->f_.has_value()) | ||||
|     return; | ||||
| // Template instantiations | ||||
| template<typename F> void TemplateTextSensorBase<F>::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); } | ||||
|  | ||||
|   auto val = (*this->f_)(); | ||||
|   if (val.has_value()) { | ||||
|     this->publish_state(*val); | ||||
|   } | ||||
| } | ||||
| float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
| void TemplateTextSensor::set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; } | ||||
| void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); } | ||||
| template class TemplateTextSensorBase<std::function<optional<std::string>()>>; | ||||
| template class TemplateTextSensorBase<optional<std::string> (*)()>; | ||||
|  | ||||
| }  // namespace template_ | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -7,18 +7,38 @@ | ||||
| namespace esphome { | ||||
| namespace template_ { | ||||
|  | ||||
| class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent { | ||||
| template<typename F> class TemplateTextSensorBase : public text_sensor::TextSensor, public PollingComponent { | ||||
|  public: | ||||
|   void set_template(std::function<optional<std::string>()> &&f); | ||||
|   void update() override { | ||||
|     if (!this->f_.has_value()) | ||||
|       return; | ||||
|     auto val = (*this->f_)(); | ||||
|     if (val.has_value()) { | ||||
|       this->publish_state(*val); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void update() override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   optional<std::function<optional<std::string>()>> f_{}; | ||||
|   optional<F> f_; | ||||
| }; | ||||
|  | ||||
| class TemplateTextSensor : public TemplateTextSensorBase<std::function<optional<std::string>()>> { | ||||
|  public: | ||||
|   void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| /** Optimized template text sensor for stateless lambdas (no capture). | ||||
|  * | ||||
|  * Uses function pointer instead of std::function to reduce memory overhead. | ||||
|  * Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||
|  */ | ||||
| class StatelessTemplateTextSensor : public TemplateTextSensorBase<optional<std::string> (*)()> { | ||||
|  public: | ||||
|   explicit StatelessTemplateTextSensor(optional<std::string> (*f)()) { this->f_ = f; } | ||||
| }; | ||||
|  | ||||
| }  // namespace template_ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user