Compare commits

..

7 Commits

Author SHA1 Message Date
J. Nick Koston
89bd9b610e modify in validation instead to avoid copy 2026-02-02 02:48:00 +01:00
J. Nick Koston
9dbcf1447b integration test 2026-02-02 02:45:43 +01:00
J. Nick Koston
6c853cae57 use pattern from sensor filters 2026-02-02 02:40:45 +01:00
J. Nick Koston
48e6efb6aa use pattern from sensor filters 2026-02-02 02:40:30 +01:00
J. Nick Koston
cfc3b3336f fix 2026-02-02 02:35:51 +01:00
J. Nick Koston
9ca394d1e5 not as bad as I was thinking it would be 2026-02-02 02:31:29 +01:00
J. Nick Koston
e62a87afe1 [template] Conditionally compile select set_trigger
Only allocate the set_trigger when set_action is configured.
This saves ~20-24 bytes of heap per template select that doesn't
use set_action.
2026-02-01 20:39:52 +01:00
35 changed files with 94 additions and 372 deletions

View File

@@ -79,19 +79,16 @@ async def to_code(config):
var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]
)
if CONF_STOP_ACTION in config:
cg.add_define("USE_TEMPLATE_COVER_STOP_TRIGGER")
await automation.build_automation(
var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
)
cg.add(var.set_has_stop(True))
if CONF_TOGGLE_ACTION in config:
cg.add_define("USE_TEMPLATE_COVER_TOGGLE_TRIGGER")
await automation.build_automation(
var.get_toggle_trigger(), [], config[CONF_TOGGLE_ACTION]
)
cg.add(var.set_has_toggle(True))
if CONF_TILT_ACTION in config:
cg.add_define("USE_TEMPLATE_COVER_TILT_TRIGGER")
await automation.build_automation(
var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION]
)
@@ -102,7 +99,6 @@ async def to_code(config):
)
cg.add(var.set_tilt_lambda(tilt_template_))
if CONF_POSITION_ACTION in config:
cg.add_define("USE_TEMPLATE_COVER_POSITION_TRIGGER")
await automation.build_automation(
var.get_position_trigger(), [(float, "pos")], config[CONF_POSITION_ACTION]
)

View File

@@ -9,25 +9,11 @@ static const char *const TAG = "template.cover";
TemplateCover::TemplateCover()
: open_trigger_(new Trigger<>()),
close_trigger_(new Trigger<>())
#ifdef USE_TEMPLATE_COVER_STOP_TRIGGER
,
stop_trigger_(new Trigger<>())
#endif
#ifdef USE_TEMPLATE_COVER_TOGGLE_TRIGGER
,
toggle_trigger_(new Trigger<>())
#endif
#ifdef USE_TEMPLATE_COVER_POSITION_TRIGGER
,
position_trigger_(new Trigger<float>())
#endif
#ifdef USE_TEMPLATE_COVER_TILT_TRIGGER
,
tilt_trigger_(new Trigger<float>())
#endif
{
}
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() {
switch (this->restore_mode_) {
case COVER_NO_RESTORE:
@@ -78,30 +64,22 @@ void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_
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_; }
#ifdef USE_TEMPLATE_COVER_STOP_TRIGGER
Trigger<> *TemplateCover::get_stop_trigger() const { return this->stop_trigger_; }
#endif
#ifdef USE_TEMPLATE_COVER_TOGGLE_TRIGGER
Trigger<> *TemplateCover::get_toggle_trigger() const { return this->toggle_trigger_; }
#endif
void TemplateCover::dump_config() { LOG_COVER("", "Template Cover", this); }
void TemplateCover::control(const CoverCall &call) {
#ifdef USE_TEMPLATE_COVER_STOP_TRIGGER
if (call.get_stop()) {
this->stop_prev_trigger_();
this->stop_trigger_->trigger();
this->prev_command_trigger_ = this->stop_trigger_;
this->publish_state();
}
#endif
#ifdef USE_TEMPLATE_COVER_TOGGLE_TRIGGER
if (call.get_toggle().has_value()) {
this->stop_prev_trigger_();
this->toggle_trigger_->trigger();
this->prev_command_trigger_ = this->toggle_trigger_;
this->publish_state();
}
#endif
if (call.get_position().has_value()) {
auto pos = *call.get_position();
this->stop_prev_trigger_();
@@ -112,19 +90,15 @@ void TemplateCover::control(const CoverCall &call) {
} else if (pos == COVER_CLOSED) {
this->close_trigger_->trigger();
this->prev_command_trigger_ = this->close_trigger_;
}
#ifdef USE_TEMPLATE_COVER_POSITION_TRIGGER
else {
} else {
this->position_trigger_->trigger(pos);
}
#endif
if (this->optimistic_) {
this->position = pos;
}
}
#ifdef USE_TEMPLATE_COVER_TILT_TRIGGER
if (call.get_tilt().has_value()) {
auto tilt = *call.get_tilt();
this->tilt_trigger_->trigger(tilt);
@@ -133,53 +107,24 @@ void TemplateCover::control(const CoverCall &call) {
this->tilt = tilt;
}
}
#endif
this->publish_state();
}
CoverTraits TemplateCover::get_traits() {
auto traits = CoverTraits();
traits.set_is_assumed_state(this->assumed_state_);
#ifdef USE_TEMPLATE_COVER_STOP_TRIGGER
traits.set_supports_stop(this->has_stop_);
#else
traits.set_supports_stop(false);
#endif
#ifdef USE_TEMPLATE_COVER_TOGGLE_TRIGGER
traits.set_supports_toggle(this->has_toggle_);
#else
traits.set_supports_toggle(false);
#endif
#ifdef USE_TEMPLATE_COVER_POSITION_TRIGGER
traits.set_supports_position(this->has_position_);
#else
traits.set_supports_position(false);
#endif
#ifdef USE_TEMPLATE_COVER_TILT_TRIGGER
traits.set_supports_tilt(this->has_tilt_);
#else
traits.set_supports_tilt(false);
#endif
return traits;
}
#ifdef USE_TEMPLATE_COVER_POSITION_TRIGGER
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
#endif
#ifdef USE_TEMPLATE_COVER_TILT_TRIGGER
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
#endif
#ifdef USE_TEMPLATE_COVER_STOP_TRIGGER
void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
#endif
#ifdef USE_TEMPLATE_COVER_TOGGLE_TRIGGER
void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
#endif
#ifdef USE_TEMPLATE_COVER_POSITION_TRIGGER
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }
#endif
#ifdef USE_TEMPLATE_COVER_TILT_TRIGGER
void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; }
#endif
void TemplateCover::stop_prev_trigger_() {
if (this->prev_command_trigger_ != nullptr) {
this->prev_command_trigger_->stop_action();

View File

@@ -21,32 +21,16 @@ class TemplateCover final : public cover::Cover, public Component {
template<typename F> void set_tilt_lambda(F &&f) { this->tilt_f_.set(std::forward<F>(f)); }
Trigger<> *get_open_trigger() const;
Trigger<> *get_close_trigger() const;
#ifdef USE_TEMPLATE_COVER_STOP_TRIGGER
Trigger<> *get_stop_trigger() const;
#endif
#ifdef USE_TEMPLATE_COVER_TOGGLE_TRIGGER
Trigger<> *get_toggle_trigger() const;
#endif
#ifdef USE_TEMPLATE_COVER_POSITION_TRIGGER
Trigger<float> *get_position_trigger() const;
#endif
#ifdef USE_TEMPLATE_COVER_TILT_TRIGGER
Trigger<float> *get_tilt_trigger() const;
#endif
void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state);
#ifdef USE_TEMPLATE_COVER_STOP_TRIGGER
void set_has_stop(bool has_stop);
#endif
#ifdef USE_TEMPLATE_COVER_POSITION_TRIGGER
void set_has_position(bool has_position);
#endif
#ifdef USE_TEMPLATE_COVER_TILT_TRIGGER
void set_has_tilt(bool has_tilt);
#endif
#ifdef USE_TEMPLATE_COVER_TOGGLE_TRIGGER
void set_has_toggle(bool has_toggle);
#endif
void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; }
void setup() override;
@@ -60,39 +44,22 @@ class TemplateCover final : public cover::Cover, public Component {
cover::CoverTraits get_traits() override;
void stop_prev_trigger_();
// Ordered to minimize padding on 32-bit: 4-byte members first, then smaller
TemplateCoverRestoreMode restore_mode_{COVER_RESTORE};
TemplateLambda<float> state_f_;
TemplateLambda<float> tilt_f_;
Trigger<> *open_trigger_;
Trigger<> *close_trigger_;
Trigger<> *prev_command_trigger_{nullptr};
#ifdef USE_TEMPLATE_COVER_STOP_TRIGGER
Trigger<> *stop_trigger_;
#endif
#ifdef USE_TEMPLATE_COVER_TOGGLE_TRIGGER
Trigger<> *toggle_trigger_;
#endif
#ifdef USE_TEMPLATE_COVER_POSITION_TRIGGER
Trigger<float> *position_trigger_;
#endif
#ifdef USE_TEMPLATE_COVER_TILT_TRIGGER
Trigger<float> *tilt_trigger_;
#endif
bool assumed_state_{false};
bool optimistic_{false};
#ifdef USE_TEMPLATE_COVER_STOP_TRIGGER
Trigger<> *open_trigger_;
Trigger<> *close_trigger_;
bool has_stop_{false};
#endif
#ifdef USE_TEMPLATE_COVER_TOGGLE_TRIGGER
bool has_toggle_{false};
#endif
#ifdef USE_TEMPLATE_COVER_POSITION_TRIGGER
Trigger<> *stop_trigger_;
Trigger<> *toggle_trigger_;
Trigger<> *prev_command_trigger_{nullptr};
Trigger<float> *position_trigger_;
bool has_position_{false};
#endif
#ifdef USE_TEMPLATE_COVER_TILT_TRIGGER
Trigger<float> *tilt_trigger_;
bool has_tilt_{false};
#endif
};
} // namespace esphome::template_

View File

@@ -141,12 +141,6 @@ async def to_code(config):
cg.add(var.set_initial_value(datetime_struct))
if CONF_SET_ACTION in config:
if config[CONF_TYPE] == "DATE":
cg.add_define("USE_TEMPLATE_DATE_SET_TRIGGER")
elif config[CONF_TYPE] == "TIME":
cg.add_define("USE_TEMPLATE_TIME_SET_TRIGGER")
elif config[CONF_TYPE] == "DATETIME":
cg.add_define("USE_TEMPLATE_DATETIME_SET_TRIGGER")
await automation.build_automation(
var.get_set_trigger(),
[(cg.ESPTime, "x")],

View File

@@ -62,9 +62,7 @@ void TemplateDate::control(const datetime::DateCall &call) {
if (has_day)
value.day_of_month = *call.get_day();
#ifdef USE_TEMPLATE_DATE_SET_TRIGGER
this->set_trigger_->trigger(value);
#endif
if (this->optimistic_) {
if (has_year)

View File

@@ -22,9 +22,7 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
#ifdef USE_TEMPLATE_DATE_SET_TRIGGER
Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; }
#endif
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
@@ -36,9 +34,7 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent
bool optimistic_{false};
ESPTime initial_value_{};
bool restore_value_{false};
#ifdef USE_TEMPLATE_DATE_SET_TRIGGER
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
#endif
TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_;

View File

@@ -80,9 +80,7 @@ void TemplateDateTime::control(const datetime::DateTimeCall &call) {
if (has_second)
value.second = *call.get_second();
#ifdef USE_TEMPLATE_DATETIME_SET_TRIGGER
this->set_trigger_->trigger(value);
#endif
if (this->optimistic_) {
if (has_year)

View File

@@ -22,9 +22,7 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
#ifdef USE_TEMPLATE_DATETIME_SET_TRIGGER
Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; }
#endif
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
@@ -36,9 +34,7 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo
bool optimistic_{false};
ESPTime initial_value_{};
bool restore_value_{false};
#ifdef USE_TEMPLATE_DATETIME_SET_TRIGGER
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
#endif
TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_;

View File

@@ -62,9 +62,7 @@ void TemplateTime::control(const datetime::TimeCall &call) {
if (has_second)
value.second = *call.get_second();
#ifdef USE_TEMPLATE_TIME_SET_TRIGGER
this->set_trigger_->trigger(value);
#endif
if (this->optimistic_) {
if (has_hour)

View File

@@ -22,9 +22,7 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
#ifdef USE_TEMPLATE_TIME_SET_TRIGGER
Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; }
#endif
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
@@ -36,9 +34,7 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent
bool optimistic_{false};
ESPTime initial_value_{};
bool restore_value_{false};
#ifdef USE_TEMPLATE_TIME_SET_TRIGGER
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
#endif
TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_;

View File

@@ -64,17 +64,14 @@ async def to_code(config):
)
cg.add(var.set_state_lambda(template_))
if CONF_UNLOCK_ACTION in config:
cg.add_define("USE_TEMPLATE_LOCK_UNLOCK_TRIGGER")
await automation.build_automation(
var.get_unlock_trigger(), [], config[CONF_UNLOCK_ACTION]
)
if CONF_LOCK_ACTION in config:
cg.add_define("USE_TEMPLATE_LOCK_LOCK_TRIGGER")
await automation.build_automation(
var.get_lock_trigger(), [], config[CONF_LOCK_ACTION]
)
if CONF_OPEN_ACTION in config:
cg.add_define("USE_TEMPLATE_LOCK_OPEN_TRIGGER")
await automation.build_automation(
var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
)

View File

@@ -8,27 +8,7 @@ using namespace esphome::lock;
static const char *const TAG = "template.lock";
TemplateLock::TemplateLock()
#ifdef USE_TEMPLATE_LOCK_LOCK_TRIGGER
: lock_trigger_(new Trigger<>())
#ifdef USE_TEMPLATE_LOCK_UNLOCK_TRIGGER
,
unlock_trigger_(new Trigger<>())
#endif
#ifdef USE_TEMPLATE_LOCK_OPEN_TRIGGER
,
open_trigger_(new Trigger<>())
#endif
#elif defined(USE_TEMPLATE_LOCK_UNLOCK_TRIGGER)
: unlock_trigger_(new Trigger<>())
#ifdef USE_TEMPLATE_LOCK_OPEN_TRIGGER
,
open_trigger_(new Trigger<>())
#endif
#elif defined(USE_TEMPLATE_LOCK_OPEN_TRIGGER)
: open_trigger_(new Trigger<>())
#endif
{
}
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
void TemplateLock::setup() {
if (!this->f_.has_value())
@@ -42,55 +22,34 @@ void TemplateLock::loop() {
}
}
void TemplateLock::control(const lock::LockCall &call) {
#if defined(USE_TEMPLATE_LOCK_LOCK_TRIGGER) || defined(USE_TEMPLATE_LOCK_UNLOCK_TRIGGER) || \
defined(USE_TEMPLATE_LOCK_OPEN_TRIGGER)
if (this->prev_trigger_ != nullptr) {
this->prev_trigger_->stop_action();
}
#endif
auto state = *call.get_state();
#ifdef USE_TEMPLATE_LOCK_LOCK_TRIGGER
if (state == LOCK_STATE_LOCKED) {
this->prev_trigger_ = this->lock_trigger_;
this->lock_trigger_->trigger();
}
#endif
#ifdef USE_TEMPLATE_LOCK_UNLOCK_TRIGGER
#ifdef USE_TEMPLATE_LOCK_LOCK_TRIGGER
else
#endif
if (state == LOCK_STATE_UNLOCKED) {
} else if (state == LOCK_STATE_UNLOCKED) {
this->prev_trigger_ = this->unlock_trigger_;
this->unlock_trigger_->trigger();
}
#endif
if (this->optimistic_)
this->publish_state(state);
}
void TemplateLock::open_latch() {
#ifdef USE_TEMPLATE_LOCK_OPEN_TRIGGER
#if defined(USE_TEMPLATE_LOCK_LOCK_TRIGGER) || defined(USE_TEMPLATE_LOCK_UNLOCK_TRIGGER)
if (this->prev_trigger_ != nullptr) {
this->prev_trigger_->stop_action();
}
#endif
this->prev_trigger_ = this->open_trigger_;
this->open_trigger_->trigger();
#endif
}
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
#ifdef USE_TEMPLATE_LOCK_LOCK_TRIGGER
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
#endif
#ifdef USE_TEMPLATE_LOCK_UNLOCK_TRIGGER
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }
#endif
#ifdef USE_TEMPLATE_LOCK_OPEN_TRIGGER
Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; }
#endif
void TemplateLock::dump_config() {
LOG_LOCK("", "Template Lock", this);
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));

View File

@@ -15,15 +15,9 @@ class TemplateLock final : public lock::Lock, public Component {
void dump_config() override;
template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); }
#ifdef USE_TEMPLATE_LOCK_LOCK_TRIGGER
Trigger<> *get_lock_trigger() const;
#endif
#ifdef USE_TEMPLATE_LOCK_UNLOCK_TRIGGER
Trigger<> *get_unlock_trigger() const;
#endif
#ifdef USE_TEMPLATE_LOCK_OPEN_TRIGGER
Trigger<> *get_open_trigger() const;
#endif
void set_optimistic(bool optimistic);
void loop() override;
@@ -35,19 +29,10 @@ class TemplateLock final : public lock::Lock, public Component {
TemplateLambda<lock::LockState> f_;
bool optimistic_{false};
#ifdef USE_TEMPLATE_LOCK_LOCK_TRIGGER
Trigger<> *lock_trigger_;
#endif
#ifdef USE_TEMPLATE_LOCK_UNLOCK_TRIGGER
Trigger<> *unlock_trigger_;
#endif
#ifdef USE_TEMPLATE_LOCK_OPEN_TRIGGER
Trigger<> *open_trigger_;
#endif
#if defined(USE_TEMPLATE_LOCK_LOCK_TRIGGER) || defined(USE_TEMPLATE_LOCK_UNLOCK_TRIGGER) || \
defined(USE_TEMPLATE_LOCK_OPEN_TRIGGER)
Trigger<> *prev_trigger_{nullptr};
#endif
};
} // namespace esphome::template_

View File

@@ -89,7 +89,6 @@ async def to_code(config):
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
if CONF_SET_ACTION in config:
cg.add_define("USE_TEMPLATE_NUMBER_SET_TRIGGER")
await automation.build_automation(
var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION]
)

View File

@@ -36,9 +36,7 @@ void TemplateNumber::update() {
}
void TemplateNumber::control(float value) {
#ifdef USE_TEMPLATE_NUMBER_SET_TRIGGER
this->set_trigger_->trigger(value);
#endif
if (this->optimistic_)
this->publish_state(value);

View File

@@ -17,9 +17,7 @@ class TemplateNumber final : public number::Number, public PollingComponent {
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
#ifdef USE_TEMPLATE_NUMBER_SET_TRIGGER
Trigger<float> *get_set_trigger() const { return set_trigger_; }
#endif
void set_optimistic(bool optimistic) { optimistic_ = optimistic; }
void set_initial_value(float initial_value) { initial_value_ = initial_value; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
@@ -29,9 +27,7 @@ class TemplateNumber final : public number::Number, public PollingComponent {
bool optimistic_{false};
float initial_value_{NAN};
bool restore_value_{false};
#ifdef USE_TEMPLATE_NUMBER_SET_TRIGGER
Trigger<float> *set_trigger_ = new Trigger<float>();
#endif
TemplateLambda<float> f_;
ESPPreferenceObject pref_;

View File

@@ -17,6 +17,9 @@ from .. import template_ns
TemplateSelect = template_ns.class_(
"TemplateSelect", select.Select, cg.PollingComponent
)
TemplateSelectWithSetAction = template_ns.class_(
"TemplateSelectWithSetAction", TemplateSelect
)
def validate(config):
@@ -39,6 +42,11 @@ def validate(config):
raise cv.Invalid(
"Either optimistic mode must be enabled, or set_action must be set, to handle the option being set."
)
# Use subclass with trigger only when set_action is configured
if CONF_SET_ACTION in config:
config[CONF_ID].type = TemplateSelectWithSetAction
return config
@@ -87,7 +95,6 @@ async def to_code(config):
cg.add(var.set_restore_value(True))
if CONF_SET_ACTION in config:
cg.add_define("USE_TEMPLATE_SELECT_SET_TRIGGER")
await automation.build_automation(
var.get_set_trigger(), [(cg.StringRef, "x")], config[CONF_SET_ACTION]
)

View File

@@ -41,10 +41,6 @@ void TemplateSelect::update() {
}
void TemplateSelect::control(size_t index) {
#ifdef USE_TEMPLATE_SELECT_SET_TRIGGER
this->set_trigger_->trigger(StringRef(this->option_at(index)));
#endif
if (this->optimistic_)
this->publish_state(index);
@@ -52,6 +48,11 @@ void TemplateSelect::control(size_t index) {
this->pref_.save(&index);
}
void TemplateSelectWithSetAction::control(size_t index) {
this->set_trigger_.trigger(StringRef(this->option_at(index)));
TemplateSelect::control(index);
}
void TemplateSelect::dump_config() {
LOG_SELECT("", "Template Select", this);
LOG_UPDATE_INTERVAL(this);

View File

@@ -9,7 +9,8 @@
namespace esphome::template_ {
class TemplateSelect final : public select::Select, public PollingComponent {
/// Base template select class - used when no set_action is configured
class TemplateSelect : public select::Select, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
@@ -18,9 +19,6 @@ class TemplateSelect final : public select::Select, public PollingComponent {
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
#ifdef USE_TEMPLATE_SELECT_SET_TRIGGER
Trigger<StringRef> *get_set_trigger() const { return this->set_trigger_; }
#endif
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
@@ -30,12 +28,19 @@ class TemplateSelect final : public select::Select, public PollingComponent {
bool optimistic_ = false;
size_t initial_option_index_{0};
bool restore_value_ = false;
#ifdef USE_TEMPLATE_SELECT_SET_TRIGGER
Trigger<StringRef> *set_trigger_ = new Trigger<StringRef>();
#endif
TemplateLambda<std::string> f_;
ESPPreferenceObject pref_;
};
/// Template select with set_action trigger - only instantiated when set_action is configured
class TemplateSelectWithSetAction final : public TemplateSelect {
public:
Trigger<StringRef> *get_set_trigger() { return &this->set_trigger_; }
protected:
void control(size_t index) override;
Trigger<StringRef> set_trigger_;
};
} // namespace esphome::template_

View File

@@ -60,12 +60,10 @@ async def to_code(config):
)
cg.add(var.set_state_lambda(template_))
if CONF_TURN_OFF_ACTION in config:
cg.add_define("USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER")
await automation.build_automation(
var.get_turn_off_trigger(), [], config[CONF_TURN_OFF_ACTION]
)
if CONF_TURN_ON_ACTION in config:
cg.add_define("USE_TEMPLATE_SWITCH_TURN_ON_TRIGGER")
await automation.build_automation(
var.get_turn_on_trigger(), [], config[CONF_TURN_ON_ACTION]
)

View File

@@ -5,18 +5,7 @@ namespace esphome::template_ {
static const char *const TAG = "template.switch";
TemplateSwitch::TemplateSwitch()
#ifdef USE_TEMPLATE_SWITCH_TURN_ON_TRIGGER
: turn_on_trigger_(new Trigger<>())
#ifdef USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER
,
turn_off_trigger_(new Trigger<>())
#endif
#elif defined(USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER)
: turn_off_trigger_(new Trigger<>())
#endif
{
}
TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
void TemplateSwitch::loop() {
auto s = this->f_();
@@ -25,22 +14,16 @@ void TemplateSwitch::loop() {
}
}
void TemplateSwitch::write_state(bool state) {
#if defined(USE_TEMPLATE_SWITCH_TURN_ON_TRIGGER) || defined(USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER)
if (this->prev_trigger_ != nullptr) {
this->prev_trigger_->stop_action();
}
#endif
if (state) {
#ifdef USE_TEMPLATE_SWITCH_TURN_ON_TRIGGER
this->prev_trigger_ = this->turn_on_trigger_;
this->turn_on_trigger_->trigger();
#endif
} else {
#ifdef USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER
this->prev_trigger_ = this->turn_off_trigger_;
this->turn_off_trigger_->trigger();
#endif
}
if (this->optimistic_)
@@ -49,12 +32,8 @@ void TemplateSwitch::write_state(bool state) {
void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
bool TemplateSwitch::assumed_state() { return this->assumed_state_; }
float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; }
#ifdef USE_TEMPLATE_SWITCH_TURN_ON_TRIGGER
Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
#endif
#ifdef USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER
Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
#endif
void TemplateSwitch::setup() {
if (!this->f_.has_value())
this->disable_loop();

View File

@@ -15,12 +15,8 @@ class TemplateSwitch final : public switch_::Switch, public Component {
void dump_config() override;
template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); }
#ifdef USE_TEMPLATE_SWITCH_TURN_ON_TRIGGER
Trigger<> *get_turn_on_trigger() const;
#endif
#ifdef USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER
Trigger<> *get_turn_off_trigger() const;
#endif
void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state);
void loop() override;
@@ -35,15 +31,9 @@ class TemplateSwitch final : public switch_::Switch, public Component {
TemplateLambda<bool> f_;
bool optimistic_{false};
bool assumed_state_{false};
#ifdef USE_TEMPLATE_SWITCH_TURN_ON_TRIGGER
Trigger<> *turn_on_trigger_;
#endif
#ifdef USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER
Trigger<> *turn_off_trigger_;
#endif
#if defined(USE_TEMPLATE_SWITCH_TURN_ON_TRIGGER) || defined(USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER)
Trigger<> *prev_trigger_{nullptr};
#endif
};
} // namespace esphome::template_

View File

@@ -89,7 +89,6 @@ async def to_code(config):
cg.add(var.set_value_saver(saver))
if CONF_SET_ACTION in config:
cg.add_define("USE_TEMPLATE_TEXT_SET_TRIGGER")
await automation.build_automation(
var.get_set_trigger(), [(cg.std_string, "x")], config[CONF_SET_ACTION]
)

View File

@@ -47,9 +47,7 @@ void TemplateText::update() {
}
void TemplateText::control(const std::string &value) {
#ifdef USE_TEMPLATE_TEXT_SET_TRIGGER
this->set_trigger_->trigger(value);
#endif
if (this->optimistic_)
this->publish_state(value);

View File

@@ -68,9 +68,7 @@ class TemplateText final : public text::Text, public PollingComponent {
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
#ifdef USE_TEMPLATE_TEXT_SET_TRIGGER
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
#endif
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(const char *initial_value) { this->initial_value_ = initial_value; }
/// Prevent accidental use of std::string which would dangle
@@ -81,9 +79,7 @@ class TemplateText final : public text::Text, public PollingComponent {
void control(const std::string &value) override;
bool optimistic_ = false;
const char *initial_value_{nullptr};
#ifdef USE_TEMPLATE_TEXT_SET_TRIGGER
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
#endif
TemplateLambda<std::string> f_{};
TemplateTextSaverBase *pref_ = nullptr;

View File

@@ -78,19 +78,16 @@ async def to_code(config):
var.get_close_trigger(), [], close_action_config
)
if stop_action_config := config.get(CONF_STOP_ACTION):
cg.add_define("USE_TEMPLATE_VALVE_STOP_TRIGGER")
await automation.build_automation(
var.get_stop_trigger(), [], stop_action_config
)
cg.add(var.set_has_stop(True))
if toggle_action_config := config.get(CONF_TOGGLE_ACTION):
cg.add_define("USE_TEMPLATE_VALVE_TOGGLE_TRIGGER")
await automation.build_automation(
var.get_toggle_trigger(), [], toggle_action_config
)
cg.add(var.set_has_toggle(True))
if position_action_config := config.get(CONF_POSITION_ACTION):
cg.add_define("USE_TEMPLATE_VALVE_POSITION_TRIGGER")
await automation.build_automation(
var.get_position_trigger(), [(float, "pos")], position_action_config
)

View File

@@ -9,21 +9,10 @@ static const char *const TAG = "template.valve";
TemplateValve::TemplateValve()
: open_trigger_(new Trigger<>()),
close_trigger_(new Trigger<>())
#ifdef USE_TEMPLATE_VALVE_STOP_TRIGGER
,
stop_trigger_(new Trigger<>())
#endif
#ifdef USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
,
toggle_trigger_(new Trigger<>())
#endif
#ifdef USE_TEMPLATE_VALVE_POSITION_TRIGGER
,
position_trigger_(new Trigger<float>())
#endif
{
}
close_trigger_(new Trigger<>),
stop_trigger_(new Trigger<>()),
toggle_trigger_(new Trigger<>()),
position_trigger_(new Trigger<float>()) {}
void TemplateValve::setup() {
switch (this->restore_mode_) {
@@ -69,12 +58,8 @@ float TemplateValve::get_setup_priority() const { return setup_priority::HARDWAR
Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; }
Trigger<> *TemplateValve::get_close_trigger() const { return this->close_trigger_; }
#ifdef USE_TEMPLATE_VALVE_STOP_TRIGGER
Trigger<> *TemplateValve::get_stop_trigger() const { return this->stop_trigger_; }
#endif
#ifdef USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
Trigger<> *TemplateValve::get_toggle_trigger() const { return this->toggle_trigger_; }
#endif
void TemplateValve::dump_config() {
LOG_VALVE("", "Template Valve", this);
@@ -85,22 +70,18 @@ void TemplateValve::dump_config() {
}
void TemplateValve::control(const ValveCall &call) {
#ifdef USE_TEMPLATE_VALVE_STOP_TRIGGER
if (call.get_stop()) {
this->stop_prev_trigger_();
this->stop_trigger_->trigger();
this->prev_command_trigger_ = this->stop_trigger_;
this->publish_state();
}
#endif
#ifdef USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
if (call.get_toggle().has_value()) {
this->stop_prev_trigger_();
this->toggle_trigger_->trigger();
this->prev_command_trigger_ = this->toggle_trigger_;
this->publish_state();
}
#endif
if (call.get_position().has_value()) {
auto pos = *call.get_position();
this->stop_prev_trigger_();
@@ -111,12 +92,9 @@ void TemplateValve::control(const ValveCall &call) {
} else if (pos == VALVE_CLOSED) {
this->close_trigger_->trigger();
this->prev_command_trigger_ = this->close_trigger_;
}
#ifdef USE_TEMPLATE_VALVE_POSITION_TRIGGER
else {
} else {
this->position_trigger_->trigger(pos);
}
#endif
if (this->optimistic_) {
this->position = pos;
@@ -129,37 +107,17 @@ void TemplateValve::control(const ValveCall &call) {
ValveTraits TemplateValve::get_traits() {
auto traits = ValveTraits();
traits.set_is_assumed_state(this->assumed_state_);
#ifdef USE_TEMPLATE_VALVE_STOP_TRIGGER
traits.set_supports_stop(this->has_stop_);
#else
traits.set_supports_stop(false);
#endif
#ifdef USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
traits.set_supports_toggle(this->has_toggle_);
#else
traits.set_supports_toggle(false);
#endif
#ifdef USE_TEMPLATE_VALVE_POSITION_TRIGGER
traits.set_supports_position(this->has_position_);
#else
traits.set_supports_position(false);
#endif
return traits;
}
#ifdef USE_TEMPLATE_VALVE_POSITION_TRIGGER
Trigger<float> *TemplateValve::get_position_trigger() const { return this->position_trigger_; }
#endif
#ifdef USE_TEMPLATE_VALVE_STOP_TRIGGER
void TemplateValve::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
#endif
#ifdef USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
void TemplateValve::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
#endif
#ifdef USE_TEMPLATE_VALVE_POSITION_TRIGGER
void TemplateValve::set_has_position(bool has_position) { this->has_position_ = has_position; }
#endif
void TemplateValve::stop_prev_trigger_() {
if (this->prev_command_trigger_ != nullptr) {

View File

@@ -20,26 +20,14 @@ class TemplateValve final : public valve::Valve, public Component {
template<typename F> void set_state_lambda(F &&f) { this->state_f_.set(std::forward<F>(f)); }
Trigger<> *get_open_trigger() const;
Trigger<> *get_close_trigger() const;
#ifdef USE_TEMPLATE_VALVE_STOP_TRIGGER
Trigger<> *get_stop_trigger() const;
#endif
#ifdef USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
Trigger<> *get_toggle_trigger() const;
#endif
#ifdef USE_TEMPLATE_VALVE_POSITION_TRIGGER
Trigger<float> *get_position_trigger() const;
#endif
void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state);
#ifdef USE_TEMPLATE_VALVE_STOP_TRIGGER
void set_has_stop(bool has_stop);
#endif
#ifdef USE_TEMPLATE_VALVE_POSITION_TRIGGER
void set_has_position(bool has_position);
#endif
#ifdef USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
void set_has_toggle(bool has_toggle);
#endif
void set_restore_mode(TemplateValveRestoreMode restore_mode) { restore_mode_ = restore_mode; }
void setup() override;
@@ -53,32 +41,19 @@ class TemplateValve final : public valve::Valve, public Component {
valve::ValveTraits get_traits() override;
void stop_prev_trigger_();
// Ordered to minimize padding on 32-bit: 4-byte members first, then smaller
TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE};
TemplateLambda<float> state_f_;
Trigger<> *open_trigger_;
Trigger<> *close_trigger_;
Trigger<> *prev_command_trigger_{nullptr};
#ifdef USE_TEMPLATE_VALVE_STOP_TRIGGER
Trigger<> *stop_trigger_;
#endif
#ifdef USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
Trigger<> *toggle_trigger_;
#endif
#ifdef USE_TEMPLATE_VALVE_POSITION_TRIGGER
Trigger<float> *position_trigger_;
#endif
bool assumed_state_{false};
bool optimistic_{false};
#ifdef USE_TEMPLATE_VALVE_STOP_TRIGGER
Trigger<> *open_trigger_;
Trigger<> *close_trigger_;
bool has_stop_{false};
#endif
#ifdef USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
bool has_toggle_{false};
#endif
#ifdef USE_TEMPLATE_VALVE_POSITION_TRIGGER
Trigger<> *stop_trigger_;
Trigger<> *toggle_trigger_;
Trigger<> *prev_command_trigger_{nullptr};
Trigger<float> *position_trigger_;
bool has_position_{false};
#endif
};
} // namespace esphome::template_

View File

@@ -64,7 +64,6 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if CONF_SET_ACTION in config:
cg.add_define("USE_TEMPLATE_WATER_HEATER_SET_TRIGGER")
await automation.build_automation(
var.get_set_trigger(), [], config[CONF_SET_ACTION]
)

View File

@@ -5,12 +5,7 @@ namespace esphome::template_ {
static const char *const TAG = "template.water_heater";
TemplateWaterHeater::TemplateWaterHeater()
#ifdef USE_TEMPLATE_WATER_HEATER_SET_TRIGGER
: set_trigger_(new Trigger<>())
#endif
{
}
TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {}
void TemplateWaterHeater::setup() {
if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE ||
@@ -83,9 +78,7 @@ void TemplateWaterHeater::control(const water_heater::WaterHeaterCall &call) {
}
}
#ifdef USE_TEMPLATE_WATER_HEATER_SET_TRIGGER
this->set_trigger_->trigger();
#endif
if (this->optimistic_) {
this->publish_state();

View File

@@ -28,9 +28,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
this->supported_modes_ = modes;
}
#ifdef USE_TEMPLATE_WATER_HEATER_SET_TRIGGER
Trigger<> *get_set_trigger() const { return this->set_trigger_; }
#endif
void setup() override;
void loop() override;
@@ -44,9 +42,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
water_heater::WaterHeaterTraits traits() override;
// Ordered to minimize padding on 32-bit: 4-byte members first, then smaller
#ifdef USE_TEMPLATE_WATER_HEATER_SET_TRIGGER
Trigger<> *set_trigger_;
#endif
TemplateLambda<float> current_temperature_f_;
TemplateLambda<water_heater::WaterHeaterMode> mode_f_;
TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE};

View File

@@ -232,25 +232,6 @@
#define ESPHOME_WIFI_CONNECT_STATE_LISTENERS 2
#define ESPHOME_WIFI_POWER_SAVE_LISTENERS 2
#define USE_WIFI_RUNTIME_POWER_SAVE
#define USE_TEMPLATE_NUMBER_SET_TRIGGER
#define USE_TEMPLATE_SELECT_SET_TRIGGER
#define USE_TEMPLATE_TEXT_SET_TRIGGER
#define USE_TEMPLATE_SWITCH_TURN_ON_TRIGGER
#define USE_TEMPLATE_SWITCH_TURN_OFF_TRIGGER
#define USE_TEMPLATE_DATE_SET_TRIGGER
#define USE_TEMPLATE_TIME_SET_TRIGGER
#define USE_TEMPLATE_DATETIME_SET_TRIGGER
#define USE_TEMPLATE_WATER_HEATER_SET_TRIGGER
#define USE_TEMPLATE_LOCK_LOCK_TRIGGER
#define USE_TEMPLATE_LOCK_UNLOCK_TRIGGER
#define USE_TEMPLATE_LOCK_OPEN_TRIGGER
#define USE_TEMPLATE_COVER_STOP_TRIGGER
#define USE_TEMPLATE_COVER_TOGGLE_TRIGGER
#define USE_TEMPLATE_COVER_POSITION_TRIGGER
#define USE_TEMPLATE_COVER_TILT_TRIGGER
#define USE_TEMPLATE_VALVE_STOP_TRIGGER
#define USE_TEMPLATE_VALVE_TOGGLE_TRIGGER
#define USE_TEMPLATE_VALVE_POSITION_TRIGGER
#define USB_HOST_MAX_REQUESTS 16
#ifdef USE_ARDUINO

View File

@@ -296,6 +296,16 @@ select:
// Migration guide: Store in std::string
std::string stored_option(id(template_select).current_option());
ESP_LOGI("test", "Stored: %s", stored_option.c_str());
- platform: template
id: template_select_with_action
name: "Template select with action"
options:
- option_a
- option_b
set_action:
- logger.log:
format: "Selected: %s"
args: ["x.c_str()"]
lock:
- platform: template

View File

@@ -56,7 +56,21 @@ select:
std::string prefix = x.substr(0, 6);
ESP_LOGI("test", "Substr prefix: %s", prefix.c_str());
# Second select with numeric options to test ADL functions
# Second select with set_action trigger (uses TemplateSelectWithSetAction subclass)
- platform: template
name: "Action Select"
id: action_select
options:
- "Action A"
- "Action B"
set_action:
then:
# Test: set_action trigger receives StringRef
- logger.log:
format: "set_action triggered: %s"
args: ['x.c_str()']
# Third select with numeric options to test ADL functions
- platform: template
name: "Baud Rate"
id: baud_select

View File

@@ -28,6 +28,8 @@ async def test_select_stringref_trigger(
find_substr_future = loop.create_future()
find_char_future = loop.create_future()
substr_future = loop.create_future()
# set_action trigger (TemplateSelectWithSetAction subclass)
set_action_future = loop.create_future()
# ADL functions
stoi_future = loop.create_future()
stol_future = loop.create_future()
@@ -43,6 +45,8 @@ async def test_select_stringref_trigger(
find_substr_pattern = re.compile(r"Found 'Option' in value")
find_char_pattern = re.compile(r"Space at position: 6") # space at index 6
substr_pattern = re.compile(r"Substr prefix: Option")
# set_action trigger pattern (TemplateSelectWithSetAction subclass)
set_action_pattern = re.compile(r"set_action triggered: Action B")
# ADL function patterns (115200 from baud rate select)
stoi_pattern = re.compile(r"stoi result: 115200")
stol_pattern = re.compile(r"stol result: 115200")
@@ -67,6 +71,9 @@ async def test_select_stringref_trigger(
find_char_future.set_result(True)
if not substr_future.done() and substr_pattern.search(line):
substr_future.set_result(True)
# set_action trigger
if not set_action_future.done() and set_action_pattern.search(line):
set_action_future.set_result(True)
# ADL functions
if not stoi_future.done() and stoi_pattern.search(line):
stoi_future.set_result(True)
@@ -89,22 +96,21 @@ async def test_select_stringref_trigger(
# List entities to find our select
entities, _ = await client.list_entities_services()
select_entity = next(
(e for e in entities if hasattr(e, "options") and e.name == "Test Select"),
None,
)
select_entity = next((e for e in entities if e.name == "Test Select"), None)
assert select_entity is not None, "Test Select entity not found"
baud_entity = next(
(e for e in entities if hasattr(e, "options") and e.name == "Baud Rate"),
None,
)
baud_entity = next((e for e in entities if e.name == "Baud Rate"), None)
assert baud_entity is not None, "Baud Rate entity not found"
action_entity = next((e for e in entities if e.name == "Action Select"), None)
assert action_entity is not None, "Action Select entity not found"
# Change select to Option B - this should trigger on_value with StringRef
client.select_command(select_entity.key, "Option B")
# Change baud to 115200 - this tests ADL functions (stoi, stol, stof, stod)
client.select_command(baud_entity.key, "115200")
# Change action select - tests set_action trigger (TemplateSelectWithSetAction)
client.select_command(action_entity.key, "Action B")
# Wait for all log messages confirming StringRef operations work
try:
@@ -118,6 +124,7 @@ async def test_select_stringref_trigger(
find_substr_future,
find_char_future,
substr_future,
set_action_future,
stoi_future,
stol_future,
stof_future,
@@ -135,6 +142,7 @@ async def test_select_stringref_trigger(
"find_substr": find_substr_future.done(),
"find_char": find_char_future.done(),
"substr": substr_future.done(),
"set_action": set_action_future.done(),
"stoi": stoi_future.done(),
"stol": stol_future.done(),
"stof": stof_future.done(),