mirror of
https://github.com/esphome/esphome.git
synced 2026-02-03 11:39:39 -07:00
Compare commits
11 Commits
json_web_s
...
template_l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be71aa4f1d | ||
|
|
581732e0ca | ||
|
|
61e2b2b6eb | ||
|
|
e202b933d9 | ||
|
|
5344318c0d | ||
|
|
ef6a7ed79e | ||
|
|
2faca19947 | ||
|
|
a5fc52ccbb | ||
|
|
66fd919e07 | ||
|
|
50b23aa988 | ||
|
|
afd0c85e48 |
@@ -16,7 +16,12 @@ from esphome.const import (
|
|||||||
CONF_UPDATE_INTERVAL,
|
CONF_UPDATE_INTERVAL,
|
||||||
)
|
)
|
||||||
from esphome.core import ID
|
from esphome.core import ID
|
||||||
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
|
from esphome.cpp_generator import (
|
||||||
|
LambdaExpression,
|
||||||
|
MockObj,
|
||||||
|
MockObjClass,
|
||||||
|
TemplateArgsType,
|
||||||
|
)
|
||||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
@@ -100,6 +105,36 @@ LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
|||||||
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
||||||
|
|
||||||
|
|
||||||
|
def new_lambda_pvariable(
|
||||||
|
id_obj: ID,
|
||||||
|
lambda_expr: LambdaExpression,
|
||||||
|
stateless_class: MockObjClass,
|
||||||
|
template_arg: cg.TemplateArguments | None = None,
|
||||||
|
) -> MockObj:
|
||||||
|
"""Create Pvariable for lambda, using stateless class if applicable.
|
||||||
|
|
||||||
|
Combines ID selection and Pvariable creation in one call. For stateless
|
||||||
|
lambdas (empty capture), uses function pointer instead of std::function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
id_obj: The ID object (action_id, condition_id, or filter_id)
|
||||||
|
lambda_expr: The lambda expression object
|
||||||
|
stateless_class: The stateless class to use for stateless lambdas
|
||||||
|
template_arg: Optional template arguments (for actions/conditions)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The created Pvariable
|
||||||
|
"""
|
||||||
|
# For stateless lambdas, use function pointer instead of std::function
|
||||||
|
if lambda_expr.capture == "":
|
||||||
|
id_obj = id_obj.copy()
|
||||||
|
id_obj.type = stateless_class
|
||||||
|
|
||||||
|
if template_arg is not None:
|
||||||
|
return cg.new_Pvariable(id_obj, template_arg, lambda_expr)
|
||||||
|
return cg.new_Pvariable(id_obj, lambda_expr)
|
||||||
|
|
||||||
|
|
||||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
if extra_schema is None:
|
if extra_schema is None:
|
||||||
extra_schema = {}
|
extra_schema = {}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ from .. import template_ns
|
|||||||
TemplateBinarySensor = template_ns.class_(
|
TemplateBinarySensor = template_ns.class_(
|
||||||
"TemplateBinarySensor", binary_sensor.BinarySensor, cg.Component
|
"TemplateBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||||
)
|
)
|
||||||
|
StatelessTemplateBinarySensor = template_ns.class_(
|
||||||
|
"StatelessTemplateBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
binary_sensor.binary_sensor_schema(TemplateBinarySensor)
|
binary_sensor.binary_sensor_schema(TemplateBinarySensor)
|
||||||
@@ -26,15 +29,22 @@ CONFIG_SCHEMA = (
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = await binary_sensor.new_binary_sensor(config)
|
# Check if we have a lambda first - determines which class to instantiate
|
||||||
await cg.register_component(var, config)
|
|
||||||
|
|
||||||
if lamb := config.get(CONF_LAMBDA):
|
if lamb := config.get(CONF_LAMBDA):
|
||||||
|
# Use new_lambda_pvariable to create either TemplateBinarySensor or StatelessTemplateBinarySensor
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
lamb, [], return_type=cg.optional.template(bool)
|
lamb, [], return_type=cg.optional.template(bool)
|
||||||
)
|
)
|
||||||
cg.add(var.set_template(template_))
|
var = automation.new_lambda_pvariable(
|
||||||
if condition := config.get(CONF_CONDITION):
|
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 = await automation.build_condition(
|
||||||
condition, cg.TemplateArguments(), []
|
condition, cg.TemplateArguments(), []
|
||||||
)
|
)
|
||||||
@@ -42,6 +52,10 @@ async def to_code(config):
|
|||||||
f"return {condition.check()};", [], return_type=cg.optional.template(bool)
|
f"return {condition.check()};", [], return_type=cg.optional.template(bool)
|
||||||
)
|
)
|
||||||
cg.add(var.set_template(template_))
|
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(
|
@automation.register_action(
|
||||||
|
|||||||
@@ -6,18 +6,13 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.binary_sensor";
|
static const char *const TAG = "template.binary_sensor";
|
||||||
|
|
||||||
void TemplateBinarySensor::setup() { this->loop(); }
|
// Template instantiations
|
||||||
|
template<typename F> void TemplateBinarySensorBase<F>::dump_config() {
|
||||||
void TemplateBinarySensor::loop() {
|
LOG_BINARY_SENSOR("", "Template Binary Sensor", this);
|
||||||
if (this->f_ == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto s = this->f_();
|
|
||||||
if (s.has_value()) {
|
|
||||||
this->publish_state(*s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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 template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -6,18 +6,41 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor {
|
template<typename F> class TemplateBinarySensorBase : public Component, public binary_sensor::BinarySensor {
|
||||||
public:
|
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;
|
void dump_config() override;
|
||||||
|
|
||||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
protected:
|
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_
|
} // namespace template_
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ from esphome.const import (
|
|||||||
from .. import template_ns
|
from .. import template_ns
|
||||||
|
|
||||||
TemplateCover = template_ns.class_("TemplateCover", cover.Cover, cg.Component)
|
TemplateCover = template_ns.class_("TemplateCover", cover.Cover, cg.Component)
|
||||||
|
StatelessTemplateCover = template_ns.class_(
|
||||||
|
"StatelessTemplateCover", cover.Cover, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
TemplateCoverRestoreMode = template_ns.enum("TemplateCoverRestoreMode")
|
TemplateCoverRestoreMode = template_ns.enum("TemplateCoverRestoreMode")
|
||||||
RESTORE_MODES = {
|
RESTORE_MODES = {
|
||||||
@@ -63,13 +66,22 @@ CONFIG_SCHEMA = (
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = await cover.new_cover(config)
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
if CONF_LAMBDA in config:
|
if CONF_LAMBDA in config:
|
||||||
|
# Use new_lambda_pvariable to create either TemplateCover or StatelessTemplateCover
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(float)
|
config[CONF_LAMBDA], [], return_type=cg.optional.template(float)
|
||||||
)
|
)
|
||||||
cg.add(var.set_state_lambda(template_))
|
var = automation.new_lambda_pvariable(
|
||||||
|
config[CONF_ID], template_, StatelessTemplateCover
|
||||||
|
)
|
||||||
|
# Manually register as cover since we didn't use new_cover
|
||||||
|
await cover.register_cover(var, config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
else:
|
||||||
|
# No state lambda - just create the base template cover
|
||||||
|
var = await cover.new_cover(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
if CONF_OPEN_ACTION in config:
|
if CONF_OPEN_ACTION in config:
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
|
var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
|
||||||
|
|||||||
@@ -8,14 +8,8 @@ using namespace esphome::cover;
|
|||||||
|
|
||||||
static const char *const TAG = "template.cover";
|
static const char *const TAG = "template.cover";
|
||||||
|
|
||||||
TemplateCover::TemplateCover()
|
// Template instantiations
|
||||||
: open_trigger_(new Trigger<>()),
|
template<typename StateF, typename TiltF> void TemplateCoverBase<StateF, TiltF>::setup() {
|
||||||
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_) {
|
switch (this->restore_mode_) {
|
||||||
case COVER_NO_RESTORE:
|
case COVER_NO_RESTORE:
|
||||||
break;
|
break;
|
||||||
@@ -34,43 +28,12 @@ void TemplateCover::setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void TemplateCover::loop() {
|
|
||||||
bool changed = false;
|
|
||||||
|
|
||||||
if (this->state_f_.has_value()) {
|
template<typename StateF, typename TiltF> void TemplateCoverBase<StateF, TiltF>::dump_config() {
|
||||||
auto s = (*this->state_f_)();
|
LOG_COVER("", "Template Cover", this);
|
||||||
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 TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
|
||||||
void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
template<typename StateF, typename TiltF> void TemplateCoverBase<StateF, TiltF>::control(const CoverCall &call) {
|
||||||
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) {
|
|
||||||
if (call.get_stop()) {
|
if (call.get_stop()) {
|
||||||
this->stop_prev_trigger_();
|
this->stop_prev_trigger_();
|
||||||
this->stop_trigger_->trigger();
|
this->stop_trigger_->trigger();
|
||||||
@@ -113,7 +76,8 @@ void TemplateCover::control(const CoverCall &call) {
|
|||||||
|
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
CoverTraits TemplateCover::get_traits() {
|
|
||||||
|
template<typename StateF, typename TiltF> CoverTraits TemplateCoverBase<StateF, TiltF>::get_traits() {
|
||||||
auto traits = CoverTraits();
|
auto traits = CoverTraits();
|
||||||
traits.set_is_assumed_state(this->assumed_state_);
|
traits.set_is_assumed_state(this->assumed_state_);
|
||||||
traits.set_supports_stop(this->has_stop_);
|
traits.set_supports_stop(this->has_stop_);
|
||||||
@@ -122,19 +86,16 @@ CoverTraits TemplateCover::get_traits() {
|
|||||||
traits.set_supports_tilt(this->has_tilt_);
|
traits.set_supports_tilt(this->has_tilt_);
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
|
|
||||||
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
|
template<typename StateF, typename TiltF> void TemplateCoverBase<StateF, TiltF>::stop_prev_trigger_() {
|
||||||
void TemplateCover::set_tilt_lambda(std::function<optional<float>()> &&tilt_f) { this->tilt_f_ = tilt_f; }
|
|
||||||
void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
|
|
||||||
void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
|
|
||||||
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }
|
|
||||||
void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; }
|
|
||||||
void TemplateCover::stop_prev_trigger_() {
|
|
||||||
if (this->prev_command_trigger_ != nullptr) {
|
if (this->prev_command_trigger_ != nullptr) {
|
||||||
this->prev_command_trigger_->stop_action();
|
this->prev_command_trigger_->stop_action();
|
||||||
this->prev_command_trigger_ = nullptr;
|
this->prev_command_trigger_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateCoverBase<std::function<optional<float>()>, std::function<optional<float>()>>;
|
||||||
|
template class TemplateCoverBase<optional<float> (*)(), optional<float> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -13,31 +13,59 @@ enum TemplateCoverRestoreMode {
|
|||||||
COVER_RESTORE_AND_CALL,
|
COVER_RESTORE_AND_CALL,
|
||||||
};
|
};
|
||||||
|
|
||||||
class TemplateCover : public cover::Cover, public Component {
|
template<typename StateF, typename TiltF> class TemplateCoverBase : public cover::Cover, public Component {
|
||||||
public:
|
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);
|
void loop() override {
|
||||||
Trigger<> *get_open_trigger() const;
|
bool changed = false;
|
||||||
Trigger<> *get_close_trigger() const;
|
if (this->state_f_.has_value()) {
|
||||||
Trigger<> *get_stop_trigger() const;
|
auto s = (*this->state_f_)();
|
||||||
Trigger<> *get_toggle_trigger() const;
|
if (s.has_value()) {
|
||||||
Trigger<float> *get_position_trigger() const;
|
auto pos = clamp(*s, 0.0f, 1.0f);
|
||||||
Trigger<float> *get_tilt_trigger() const;
|
if (pos != this->position) {
|
||||||
void set_optimistic(bool optimistic);
|
this->position = pos;
|
||||||
void set_assumed_state(bool assumed_state);
|
changed = true;
|
||||||
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);
|
if (this->tilt_f_.has_value()) {
|
||||||
void set_has_toggle(bool has_toggle);
|
auto s = (*this->tilt_f_)();
|
||||||
void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; }
|
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 setup() override;
|
||||||
void loop() override;
|
|
||||||
void dump_config() 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:
|
protected:
|
||||||
void control(const cover::CoverCall &call) override;
|
void control(const cover::CoverCall &call) override;
|
||||||
@@ -45,8 +73,8 @@ class TemplateCover : public cover::Cover, public Component {
|
|||||||
void stop_prev_trigger_();
|
void stop_prev_trigger_();
|
||||||
|
|
||||||
TemplateCoverRestoreMode restore_mode_{COVER_RESTORE};
|
TemplateCoverRestoreMode restore_mode_{COVER_RESTORE};
|
||||||
optional<std::function<optional<float>()>> state_f_;
|
optional<StateF> state_f_;
|
||||||
optional<std::function<optional<float>()>> tilt_f_;
|
optional<TiltF> tilt_f_;
|
||||||
bool assumed_state_{false};
|
bool assumed_state_{false};
|
||||||
bool optimistic_{false};
|
bool optimistic_{false};
|
||||||
Trigger<> *open_trigger_;
|
Trigger<> *open_trigger_;
|
||||||
@@ -62,5 +90,22 @@ class TemplateCover : public cover::Cover, public Component {
|
|||||||
bool has_tilt_{false};
|
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 template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import esphome.config_validation as cv
|
|||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DAY,
|
CONF_DAY,
|
||||||
CONF_HOUR,
|
CONF_HOUR,
|
||||||
|
CONF_ID,
|
||||||
CONF_INITIAL_VALUE,
|
CONF_INITIAL_VALUE,
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
CONF_MINUTE,
|
CONF_MINUTE,
|
||||||
@@ -25,14 +26,23 @@ CODEOWNERS = ["@rfdarter"]
|
|||||||
TemplateDate = template_ns.class_(
|
TemplateDate = template_ns.class_(
|
||||||
"TemplateDate", datetime.DateEntity, cg.PollingComponent
|
"TemplateDate", datetime.DateEntity, cg.PollingComponent
|
||||||
)
|
)
|
||||||
|
StatelessTemplateDate = template_ns.class_(
|
||||||
|
"StatelessTemplateDate", datetime.DateEntity, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
TemplateTime = template_ns.class_(
|
TemplateTime = template_ns.class_(
|
||||||
"TemplateTime", datetime.TimeEntity, cg.PollingComponent
|
"TemplateTime", datetime.TimeEntity, cg.PollingComponent
|
||||||
)
|
)
|
||||||
|
StatelessTemplateTime = template_ns.class_(
|
||||||
|
"StatelessTemplateTime", datetime.TimeEntity, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
TemplateDateTime = template_ns.class_(
|
TemplateDateTime = template_ns.class_(
|
||||||
"TemplateDateTime", datetime.DateTimeEntity, cg.PollingComponent
|
"TemplateDateTime", datetime.DateTimeEntity, cg.PollingComponent
|
||||||
)
|
)
|
||||||
|
StatelessTemplateDateTime = template_ns.class_(
|
||||||
|
"StatelessTemplateDateTime", datetime.DateTimeEntity, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate(config):
|
def validate(config):
|
||||||
@@ -99,15 +109,30 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = await datetime.new_datetime(config)
|
|
||||||
|
|
||||||
if CONF_LAMBDA in config:
|
if CONF_LAMBDA in config:
|
||||||
|
# Use new_lambda_pvariable to create either Template* or StatelessTemplate*
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.ESPTime)
|
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.ESPTime)
|
||||||
)
|
)
|
||||||
cg.add(var.set_template(template_))
|
# Determine the appropriate stateless class based on type
|
||||||
|
if config[CONF_TYPE] == "DATE":
|
||||||
|
stateless_class = StatelessTemplateDate
|
||||||
|
elif config[CONF_TYPE] == "TIME":
|
||||||
|
stateless_class = StatelessTemplateTime
|
||||||
|
else: # DATETIME
|
||||||
|
stateless_class = StatelessTemplateDateTime
|
||||||
|
|
||||||
|
var = automation.new_lambda_pvariable(
|
||||||
|
config[CONF_ID], template_, stateless_class
|
||||||
|
)
|
||||||
|
# Manually register as datetime since we didn't use new_datetime
|
||||||
|
await datetime.register_datetime(var, config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
else:
|
else:
|
||||||
|
# No lambda - just create the base template datetime
|
||||||
|
var = await datetime.new_datetime(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||||
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
|
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
|
||||||
|
|
||||||
@@ -146,5 +171,3 @@ async def to_code(config):
|
|||||||
[(cg.ESPTime, "x")],
|
[(cg.ESPTime, "x")],
|
||||||
config[CONF_SET_ACTION],
|
config[CONF_SET_ACTION],
|
||||||
)
|
)
|
||||||
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.date";
|
static const char *const TAG = "template.date";
|
||||||
|
|
||||||
void TemplateDate::setup() {
|
// Template instantiations
|
||||||
|
template<typename F> void TemplateDateBase<F>::setup() {
|
||||||
if (this->f_.has_value())
|
if (this->f_.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -36,21 +37,7 @@ void TemplateDate::setup() {
|
|||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateDate::update() {
|
template<typename F> void TemplateDateBase<F>::control(const datetime::DateCall &call) {
|
||||||
if (!this->f_.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto val = (*this->f_)();
|
|
||||||
if (!val.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->year_ = val->year;
|
|
||||||
this->month_ = val->month;
|
|
||||||
this->day_ = val->day_of_month;
|
|
||||||
this->publish_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemplateDate::control(const datetime::DateCall &call) {
|
|
||||||
bool has_year = call.get_year().has_value();
|
bool has_year = call.get_year().has_value();
|
||||||
bool has_month = call.get_month().has_value();
|
bool has_month = call.get_month().has_value();
|
||||||
bool has_day = call.get_day().has_value();
|
bool has_day = call.get_day().has_value();
|
||||||
@@ -99,12 +86,15 @@ void TemplateDate::control(const datetime::DateCall &call) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateDate::dump_config() {
|
template<typename F> void TemplateDateBase<F>::dump_config() {
|
||||||
LOG_DATETIME_DATE("", "Template Date", this);
|
LOG_DATETIME_DATE("", "Template Date", this);
|
||||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateDateBase<std::function<optional<ESPTime>()>>;
|
||||||
|
template class TemplateDateBase<optional<ESPTime> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,23 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateDate : public datetime::DateEntity, public PollingComponent {
|
template<typename F> class TemplateDateBase : public datetime::DateEntity, public PollingComponent {
|
||||||
public:
|
public:
|
||||||
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
void update() override {
|
||||||
|
if (!this->f_.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto val = (*this->f_)();
|
||||||
|
if (!val.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->year_ = val->year;
|
||||||
|
this->month_ = val->month;
|
||||||
|
this->day_ = val->day_of_month;
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void update() override;
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
@@ -35,11 +46,26 @@ class TemplateDate : public datetime::DateEntity, public PollingComponent {
|
|||||||
ESPTime initial_value_{};
|
ESPTime initial_value_{};
|
||||||
bool restore_value_{false};
|
bool restore_value_{false};
|
||||||
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||||
optional<std::function<optional<ESPTime>()>> f_;
|
optional<F> f_;
|
||||||
|
|
||||||
ESPPreferenceObject pref_;
|
ESPPreferenceObject pref_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TemplateDate : public TemplateDateBase<std::function<optional<ESPTime>()>> {
|
||||||
|
public:
|
||||||
|
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Optimized template date 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 StatelessTemplateDate : public TemplateDateBase<optional<ESPTime> (*)()> {
|
||||||
|
public:
|
||||||
|
explicit StatelessTemplateDate(optional<ESPTime> (*f)()) { this->f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.datetime";
|
static const char *const TAG = "template.datetime";
|
||||||
|
|
||||||
void TemplateDateTime::setup() {
|
// Template instantiations
|
||||||
|
template<typename F> void TemplateDateTimeBase<F>::setup() {
|
||||||
if (this->f_.has_value())
|
if (this->f_.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -39,24 +40,7 @@ void TemplateDateTime::setup() {
|
|||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateDateTime::update() {
|
template<typename F> void TemplateDateTimeBase<F>::control(const datetime::DateTimeCall &call) {
|
||||||
if (!this->f_.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto val = (*this->f_)();
|
|
||||||
if (!val.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->year_ = val->year;
|
|
||||||
this->month_ = val->month;
|
|
||||||
this->day_ = val->day_of_month;
|
|
||||||
this->hour_ = val->hour;
|
|
||||||
this->minute_ = val->minute;
|
|
||||||
this->second_ = val->second;
|
|
||||||
this->publish_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemplateDateTime::control(const datetime::DateTimeCall &call) {
|
|
||||||
bool has_year = call.get_year().has_value();
|
bool has_year = call.get_year().has_value();
|
||||||
bool has_month = call.get_month().has_value();
|
bool has_month = call.get_month().has_value();
|
||||||
bool has_day = call.get_day().has_value();
|
bool has_day = call.get_day().has_value();
|
||||||
@@ -138,12 +122,15 @@ void TemplateDateTime::control(const datetime::DateTimeCall &call) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateDateTime::dump_config() {
|
template<typename F> void TemplateDateTimeBase<F>::dump_config() {
|
||||||
LOG_DATETIME_DATETIME("", "Template DateTime", this);
|
LOG_DATETIME_DATETIME("", "Template DateTime", this);
|
||||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateDateTimeBase<std::function<optional<ESPTime>()>>;
|
||||||
|
template class TemplateDateTimeBase<optional<ESPTime> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,26 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent {
|
template<typename F> class TemplateDateTimeBase : public datetime::DateTimeEntity, public PollingComponent {
|
||||||
public:
|
public:
|
||||||
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
void update() override {
|
||||||
|
if (!this->f_.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto val = (*this->f_)();
|
||||||
|
if (!val.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->year_ = val->year;
|
||||||
|
this->month_ = val->month;
|
||||||
|
this->day_ = val->day_of_month;
|
||||||
|
this->hour_ = val->hour;
|
||||||
|
this->minute_ = val->minute;
|
||||||
|
this->second_ = val->second;
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void update() override;
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
@@ -35,11 +49,26 @@ class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponen
|
|||||||
ESPTime initial_value_{};
|
ESPTime initial_value_{};
|
||||||
bool restore_value_{false};
|
bool restore_value_{false};
|
||||||
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||||
optional<std::function<optional<ESPTime>()>> f_;
|
optional<F> f_;
|
||||||
|
|
||||||
ESPPreferenceObject pref_;
|
ESPPreferenceObject pref_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TemplateDateTime : public TemplateDateTimeBase<std::function<optional<ESPTime>()>> {
|
||||||
|
public:
|
||||||
|
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Optimized template datetime 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 StatelessTemplateDateTime : public TemplateDateTimeBase<optional<ESPTime> (*)()> {
|
||||||
|
public:
|
||||||
|
explicit StatelessTemplateDateTime(optional<ESPTime> (*f)()) { this->f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.time";
|
static const char *const TAG = "template.time";
|
||||||
|
|
||||||
void TemplateTime::setup() {
|
// Template instantiations
|
||||||
|
template<typename F> void TemplateTimeBase<F>::setup() {
|
||||||
if (this->f_.has_value())
|
if (this->f_.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -36,21 +37,7 @@ void TemplateTime::setup() {
|
|||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateTime::update() {
|
template<typename F> void TemplateTimeBase<F>::control(const datetime::TimeCall &call) {
|
||||||
if (!this->f_.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto val = (*this->f_)();
|
|
||||||
if (!val.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->hour_ = val->hour;
|
|
||||||
this->minute_ = val->minute;
|
|
||||||
this->second_ = val->second;
|
|
||||||
this->publish_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemplateTime::control(const datetime::TimeCall &call) {
|
|
||||||
bool has_hour = call.get_hour().has_value();
|
bool has_hour = call.get_hour().has_value();
|
||||||
bool has_minute = call.get_minute().has_value();
|
bool has_minute = call.get_minute().has_value();
|
||||||
bool has_second = call.get_second().has_value();
|
bool has_second = call.get_second().has_value();
|
||||||
@@ -99,12 +86,15 @@ void TemplateTime::control(const datetime::TimeCall &call) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateTime::dump_config() {
|
template<typename F> void TemplateTimeBase<F>::dump_config() {
|
||||||
LOG_DATETIME_TIME("", "Template Time", this);
|
LOG_DATETIME_TIME("", "Template Time", this);
|
||||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateTimeBase<std::function<optional<ESPTime>()>>;
|
||||||
|
template class TemplateTimeBase<optional<ESPTime> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,23 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateTime : public datetime::TimeEntity, public PollingComponent {
|
template<typename F> class TemplateTimeBase : public datetime::TimeEntity, public PollingComponent {
|
||||||
public:
|
public:
|
||||||
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
void update() override {
|
||||||
|
if (!this->f_.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto val = (*this->f_)();
|
||||||
|
if (!val.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->hour_ = val->hour;
|
||||||
|
this->minute_ = val->minute;
|
||||||
|
this->second_ = val->second;
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void update() override;
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
@@ -35,11 +46,26 @@ class TemplateTime : public datetime::TimeEntity, public PollingComponent {
|
|||||||
ESPTime initial_value_{};
|
ESPTime initial_value_{};
|
||||||
bool restore_value_{false};
|
bool restore_value_{false};
|
||||||
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||||
optional<std::function<optional<ESPTime>()>> f_;
|
optional<F> f_;
|
||||||
|
|
||||||
ESPPreferenceObject pref_;
|
ESPPreferenceObject pref_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TemplateTime : public TemplateTimeBase<std::function<optional<ESPTime>()>> {
|
||||||
|
public:
|
||||||
|
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Optimized template time 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 StatelessTemplateTime : public TemplateTimeBase<optional<ESPTime> (*)()> {
|
||||||
|
public:
|
||||||
|
explicit StatelessTemplateTime(optional<ESPTime> (*f)()) { this->f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
#include <vector>
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/fan/fan.h"
|
#include "esphome/components/fan/fan.h"
|
||||||
@@ -16,7 +16,7 @@ class TemplateFan : public Component, public fan::Fan {
|
|||||||
void set_has_direction(bool has_direction) { this->has_direction_ = has_direction; }
|
void set_has_direction(bool has_direction) { this->has_direction_ = has_direction; }
|
||||||
void set_has_oscillating(bool has_oscillating) { this->has_oscillating_ = has_oscillating; }
|
void set_has_oscillating(bool has_oscillating) { this->has_oscillating_ = has_oscillating; }
|
||||||
void set_speed_count(int count) { this->speed_count_ = count; }
|
void set_speed_count(int count) { this->speed_count_ = count; }
|
||||||
void set_preset_modes(const std::set<std::string> &presets) { this->preset_modes_ = presets; }
|
void set_preset_modes(const std::initializer_list<std::string> &presets) { this->preset_modes_ = presets; }
|
||||||
fan::FanTraits get_traits() override { return this->traits_; }
|
fan::FanTraits get_traits() override { return this->traits_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -26,7 +26,7 @@ class TemplateFan : public Component, public fan::Fan {
|
|||||||
bool has_direction_{false};
|
bool has_direction_{false};
|
||||||
int speed_count_{0};
|
int speed_count_{0};
|
||||||
fan::FanTraits traits_;
|
fan::FanTraits traits_;
|
||||||
std::set<std::string> preset_modes_{};
|
std::vector<std::string> preset_modes_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ from esphome.const import (
|
|||||||
from .. import template_ns
|
from .. import template_ns
|
||||||
|
|
||||||
TemplateLock = template_ns.class_("TemplateLock", lock.Lock, cg.Component)
|
TemplateLock = template_ns.class_("TemplateLock", lock.Lock, cg.Component)
|
||||||
|
StatelessTemplateLock = template_ns.class_(
|
||||||
|
"StatelessTemplateLock", lock.Lock, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
TemplateLockPublishAction = template_ns.class_(
|
TemplateLockPublishAction = template_ns.class_(
|
||||||
"TemplateLockPublishAction",
|
"TemplateLockPublishAction",
|
||||||
@@ -55,14 +58,22 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = await lock.new_lock(config)
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
|
|
||||||
if CONF_LAMBDA in config:
|
if CONF_LAMBDA in config:
|
||||||
|
# Use new_lambda_pvariable to create either TemplateLock or StatelessTemplateLock
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(lock.LockState)
|
config[CONF_LAMBDA], [], return_type=cg.optional.template(lock.LockState)
|
||||||
)
|
)
|
||||||
cg.add(var.set_state_lambda(template_))
|
var = automation.new_lambda_pvariable(
|
||||||
|
config[CONF_ID], template_, StatelessTemplateLock
|
||||||
|
)
|
||||||
|
# Manually register as lock since we didn't use new_lock
|
||||||
|
await lock.register_lock(var, config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
else:
|
||||||
|
# No lambda - just create the base template lock
|
||||||
|
var = await lock.new_lock(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
if CONF_UNLOCK_ACTION in config:
|
if CONF_UNLOCK_ACTION in config:
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
var.get_unlock_trigger(), [], config[CONF_UNLOCK_ACTION]
|
var.get_unlock_trigger(), [], config[CONF_UNLOCK_ACTION]
|
||||||
|
|||||||
@@ -8,19 +8,8 @@ using namespace esphome::lock;
|
|||||||
|
|
||||||
static const char *const TAG = "template.lock";
|
static const char *const TAG = "template.lock";
|
||||||
|
|
||||||
TemplateLock::TemplateLock()
|
// Template instantiations
|
||||||
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
|
template<typename F> void TemplateLockBase<F>::control(const lock::LockCall &call) {
|
||||||
|
|
||||||
void TemplateLock::loop() {
|
|
||||||
if (!this->f_.has_value())
|
|
||||||
return;
|
|
||||||
auto val = (*this->f_)();
|
|
||||||
if (!val.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->publish_state(*val);
|
|
||||||
}
|
|
||||||
void TemplateLock::control(const lock::LockCall &call) {
|
|
||||||
if (this->prev_trigger_ != nullptr) {
|
if (this->prev_trigger_ != nullptr) {
|
||||||
this->prev_trigger_->stop_action();
|
this->prev_trigger_->stop_action();
|
||||||
}
|
}
|
||||||
@@ -37,23 +26,22 @@ void TemplateLock::control(const lock::LockCall &call) {
|
|||||||
if (this->optimistic_)
|
if (this->optimistic_)
|
||||||
this->publish_state(state);
|
this->publish_state(state);
|
||||||
}
|
}
|
||||||
void TemplateLock::open_latch() {
|
|
||||||
|
template<typename F> void TemplateLockBase<F>::open_latch() {
|
||||||
if (this->prev_trigger_ != nullptr) {
|
if (this->prev_trigger_ != nullptr) {
|
||||||
this->prev_trigger_->stop_action();
|
this->prev_trigger_->stop_action();
|
||||||
}
|
}
|
||||||
this->prev_trigger_ = this->open_trigger_;
|
this->prev_trigger_ = this->open_trigger_;
|
||||||
this->open_trigger_->trigger();
|
this->open_trigger_->trigger();
|
||||||
}
|
}
|
||||||
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
|
||||||
void TemplateLock::set_state_lambda(std::function<optional<lock::LockState>()> &&f) { this->f_ = f; }
|
template<typename F> void TemplateLockBase<F>::dump_config() {
|
||||||
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
|
|
||||||
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
|
|
||||||
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }
|
|
||||||
Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; }
|
|
||||||
void TemplateLock::dump_config() {
|
|
||||||
LOG_LOCK("", "Template Lock", this);
|
LOG_LOCK("", "Template Lock", this);
|
||||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateLockBase<std::function<optional<lock::LockState>()>>;
|
||||||
|
template class TemplateLockBase<optional<lock::LockState> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -7,26 +7,35 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateLock : public lock::Lock, public Component {
|
template<typename F> class TemplateLockBase : public lock::Lock, public Component {
|
||||||
public:
|
public:
|
||||||
TemplateLock();
|
TemplateLockBase()
|
||||||
|
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
if (!this->f_.has_value())
|
||||||
|
return;
|
||||||
|
auto val = (*this->f_)();
|
||||||
|
if (!val.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->publish_state(*val);
|
||||||
|
}
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
void set_state_lambda(std::function<optional<lock::LockState>()> &&f);
|
Trigger<> *get_lock_trigger() const { return this->lock_trigger_; }
|
||||||
Trigger<> *get_lock_trigger() const;
|
Trigger<> *get_unlock_trigger() const { return this->unlock_trigger_; }
|
||||||
Trigger<> *get_unlock_trigger() const;
|
Trigger<> *get_open_trigger() const { return this->open_trigger_; }
|
||||||
Trigger<> *get_open_trigger() const;
|
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||||
void set_optimistic(bool optimistic);
|
|
||||||
void loop() override;
|
|
||||||
|
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const lock::LockCall &call) override;
|
void control(const lock::LockCall &call) override;
|
||||||
void open_latch() override;
|
void open_latch() override;
|
||||||
|
|
||||||
optional<std::function<optional<lock::LockState>()>> f_;
|
optional<F> f_;
|
||||||
bool optimistic_{false};
|
bool optimistic_{false};
|
||||||
Trigger<> *lock_trigger_;
|
Trigger<> *lock_trigger_;
|
||||||
Trigger<> *unlock_trigger_;
|
Trigger<> *unlock_trigger_;
|
||||||
@@ -34,5 +43,20 @@ class TemplateLock : public lock::Lock, public Component {
|
|||||||
Trigger<> *prev_trigger_{nullptr};
|
Trigger<> *prev_trigger_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TemplateLock : public TemplateLockBase<std::function<optional<lock::LockState>()>> {
|
||||||
|
public:
|
||||||
|
void set_state_lambda(std::function<optional<lock::LockState>()> &&f) { this->f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Optimized template lock 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 StatelessTemplateLock : public TemplateLockBase<optional<lock::LockState> (*)()> {
|
||||||
|
public:
|
||||||
|
explicit StatelessTemplateLock(optional<lock::LockState> (*f)()) { this->f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ from .. import template_ns
|
|||||||
TemplateNumber = template_ns.class_(
|
TemplateNumber = template_ns.class_(
|
||||||
"TemplateNumber", number.Number, cg.PollingComponent
|
"TemplateNumber", number.Number, cg.PollingComponent
|
||||||
)
|
)
|
||||||
|
StatelessTemplateNumber = template_ns.class_(
|
||||||
|
"StatelessTemplateNumber", number.Number, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_min_max(config):
|
def validate_min_max(config):
|
||||||
@@ -66,23 +69,33 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
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:
|
if CONF_LAMBDA in config:
|
||||||
|
# Use new_lambda_pvariable to create either TemplateNumber or StatelessTemplateNumber
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(float)
|
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:
|
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_optimistic(config[CONF_OPTIMISTIC]))
|
||||||
cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
|
cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
|
||||||
if CONF_RESTORE_VALUE in config:
|
if CONF_RESTORE_VALUE in config:
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.number";
|
static const char *const TAG = "template.number";
|
||||||
|
|
||||||
void TemplateNumber::setup() {
|
// Template instantiations
|
||||||
|
template<typename F> void TemplateNumberBase<F>::setup() {
|
||||||
if (this->f_.has_value())
|
if (this->f_.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -26,18 +27,7 @@ void TemplateNumber::setup() {
|
|||||||
this->publish_state(value);
|
this->publish_state(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateNumber::update() {
|
template<typename F> void TemplateNumberBase<F>::control(float value) {
|
||||||
if (!this->f_.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto val = (*this->f_)();
|
|
||||||
if (!val.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->publish_state(*val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemplateNumber::control(float value) {
|
|
||||||
this->set_trigger_->trigger(value);
|
this->set_trigger_->trigger(value);
|
||||||
|
|
||||||
if (this->optimistic_)
|
if (this->optimistic_)
|
||||||
@@ -46,11 +36,15 @@ void TemplateNumber::control(float value) {
|
|||||||
if (this->restore_value_)
|
if (this->restore_value_)
|
||||||
this->pref_.save(&value);
|
this->pref_.save(&value);
|
||||||
}
|
}
|
||||||
void TemplateNumber::dump_config() {
|
|
||||||
|
template<typename F> void TemplateNumberBase<F>::dump_config() {
|
||||||
LOG_NUMBER("", "Template Number", this);
|
LOG_NUMBER("", "Template Number", this);
|
||||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateNumberBase<std::function<optional<float>()>>;
|
||||||
|
template class TemplateNumberBase<optional<float> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -8,13 +8,22 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateNumber : public number::Number, public PollingComponent {
|
template<typename F> class TemplateNumberBase : public number::Number, public PollingComponent {
|
||||||
public:
|
public:
|
||||||
void set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
|
TemplateNumberBase() : set_trigger_(new Trigger<float>()) {}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void update() override;
|
|
||||||
void dump_config() 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; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
Trigger<float> *get_set_trigger() const { return set_trigger_; }
|
Trigger<float> *get_set_trigger() const { return set_trigger_; }
|
||||||
@@ -27,11 +36,26 @@ class TemplateNumber : public number::Number, public PollingComponent {
|
|||||||
bool optimistic_{false};
|
bool optimistic_{false};
|
||||||
float initial_value_{NAN};
|
float initial_value_{NAN};
|
||||||
bool restore_value_{false};
|
bool restore_value_{false};
|
||||||
Trigger<float> *set_trigger_ = new Trigger<float>();
|
Trigger<float> *set_trigger_;
|
||||||
optional<std::function<optional<float>()>> f_;
|
optional<F> f_;
|
||||||
|
|
||||||
ESPPreferenceObject pref_;
|
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 template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ from .. import template_ns
|
|||||||
TemplateSelect = template_ns.class_(
|
TemplateSelect = template_ns.class_(
|
||||||
"TemplateSelect", select.Select, cg.PollingComponent
|
"TemplateSelect", select.Select, cg.PollingComponent
|
||||||
)
|
)
|
||||||
|
StatelessTemplateSelect = template_ns.class_(
|
||||||
|
"StatelessTemplateSelect", select.Select, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate(config):
|
def validate(config):
|
||||||
@@ -62,22 +65,34 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
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:
|
if CONF_LAMBDA in config:
|
||||||
|
# Use new_lambda_pvariable to create either TemplateSelect or StatelessTemplateSelect
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
|
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:
|
else:
|
||||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
# No lambda - just create the base template select
|
||||||
cg.add(var.set_initial_option(config[CONF_INITIAL_OPTION]))
|
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_RESTORE_VALUE in config:
|
# Only set if non-default to avoid bloating setup() function
|
||||||
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
|
if config[CONF_OPTIMISTIC]:
|
||||||
|
cg.add(var.set_optimistic(True))
|
||||||
|
initial_option_index = config[CONF_OPTIONS].index(config[CONF_INITIAL_OPTION])
|
||||||
|
# Only set if non-zero to avoid bloating setup() function
|
||||||
|
# (initial_option_index_ is zero-initialized in the header)
|
||||||
|
if initial_option_index != 0:
|
||||||
|
cg.add(var.set_initial_option_index(initial_option_index))
|
||||||
|
|
||||||
|
# Only set if True (default is False)
|
||||||
|
if config.get(CONF_RESTORE_VALUE):
|
||||||
|
cg.add(var.set_restore_value(True))
|
||||||
|
|
||||||
if CONF_SET_ACTION in config:
|
if CONF_SET_ACTION in config:
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
|
|||||||
@@ -6,49 +6,29 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.select";
|
static const char *const TAG = "template.select";
|
||||||
|
|
||||||
void TemplateSelect::setup() {
|
// Template instantiations
|
||||||
|
template<typename F> void TemplateSelectBase<F>::setup() {
|
||||||
if (this->f_.has_value())
|
if (this->f_.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string value;
|
size_t index = this->initial_option_index_;
|
||||||
if (!this->restore_value_) {
|
if (this->restore_value_) {
|
||||||
value = this->initial_option_;
|
|
||||||
ESP_LOGD(TAG, "State from initial: %s", value.c_str());
|
|
||||||
} else {
|
|
||||||
size_t index;
|
|
||||||
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash());
|
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash());
|
||||||
if (!this->pref_.load(&index)) {
|
size_t restored_index;
|
||||||
value = this->initial_option_;
|
if (this->pref_.load(&restored_index) && this->has_index(restored_index)) {
|
||||||
ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str());
|
index = restored_index;
|
||||||
} else if (!this->has_index(index)) {
|
ESP_LOGD(TAG, "State from restore: %s", this->at(index).value().c_str());
|
||||||
value = this->initial_option_;
|
|
||||||
ESP_LOGD(TAG, "State from initial (restored index %d out of bounds): %s", index, value.c_str());
|
|
||||||
} else {
|
} else {
|
||||||
value = this->at(index).value();
|
ESP_LOGD(TAG, "State from initial (could not load or invalid stored index): %s", this->at(index).value().c_str());
|
||||||
ESP_LOGD(TAG, "State from restore: %s", value.c_str());
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "State from initial: %s", this->at(index).value().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
this->publish_state(value);
|
this->publish_state(this->at(index).value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateSelect::update() {
|
template<typename F> void TemplateSelectBase<F>::control(const std::string &value) {
|
||||||
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) {
|
|
||||||
this->set_trigger_->trigger(value);
|
this->set_trigger_->trigger(value);
|
||||||
|
|
||||||
if (this->optimistic_)
|
if (this->optimistic_)
|
||||||
@@ -60,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_SELECT("", "Template Select", this);
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
if (this->f_.has_value())
|
if (this->f_.has_value())
|
||||||
@@ -69,8 +49,12 @@ void TemplateSelect::dump_config() {
|
|||||||
" Optimistic: %s\n"
|
" Optimistic: %s\n"
|
||||||
" Initial Option: %s\n"
|
" Initial Option: %s\n"
|
||||||
" Restore Value: %s",
|
" Restore Value: %s",
|
||||||
YESNO(this->optimistic_), this->initial_option_.c_str(), YESNO(this->restore_value_));
|
YESNO(this->optimistic_), this->at(this->initial_option_index_).value().c_str(),
|
||||||
|
YESNO(this->restore_value_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateSelectBase<std::function<optional<std::string>()>>;
|
||||||
|
template class TemplateSelectBase<optional<std::string> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -8,30 +8,58 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateSelect : public select::Select, public PollingComponent {
|
template<typename F> class TemplateSelectBase : public select::Select, public PollingComponent {
|
||||||
public:
|
public:
|
||||||
void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
|
TemplateSelectBase() : set_trigger_(new Trigger<std::string>()) {}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void update() override;
|
|
||||||
void dump_config() 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; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
|
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
|
||||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||||
void set_initial_option(const std::string &initial_option) { this->initial_option_ = initial_option; }
|
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; }
|
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(const std::string &value) override;
|
||||||
bool optimistic_ = false;
|
bool optimistic_ = false;
|
||||||
std::string initial_option_;
|
size_t initial_option_index_{0};
|
||||||
bool restore_value_ = false;
|
bool restore_value_ = false;
|
||||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
Trigger<std::string> *set_trigger_;
|
||||||
optional<std::function<optional<std::string>()>> f_;
|
optional<F> f_;
|
||||||
|
|
||||||
ESPPreferenceObject pref_;
|
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 template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ from .. import template_ns
|
|||||||
TemplateSensor = template_ns.class_(
|
TemplateSensor = template_ns.class_(
|
||||||
"TemplateSensor", sensor.Sensor, cg.PollingComponent
|
"TemplateSensor", sensor.Sensor, cg.PollingComponent
|
||||||
)
|
)
|
||||||
|
StatelessTemplateSensor = template_ns.class_(
|
||||||
|
"StatelessTemplateSensor", sensor.Sensor, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
sensor.sensor_schema(
|
sensor.sensor_schema(
|
||||||
@@ -25,14 +28,21 @@ CONFIG_SCHEMA = (
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = await sensor.new_sensor(config)
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
|
|
||||||
if CONF_LAMBDA in config:
|
if CONF_LAMBDA in config:
|
||||||
|
# Use new_lambda_pvariable to create either TemplateSensor or StatelessTemplateSensor
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(float)
|
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(
|
@automation.register_action(
|
||||||
|
|||||||
@@ -7,21 +7,14 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.sensor";
|
static const char *const TAG = "template.sensor";
|
||||||
|
|
||||||
void TemplateSensor::update() {
|
// Template instantiations
|
||||||
if (!this->f_.has_value())
|
template<typename F> void TemplateSensorBase<F>::dump_config() {
|
||||||
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() {
|
|
||||||
LOG_SENSOR("", "Template Sensor", this);
|
LOG_SENSOR("", "Template Sensor", this);
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateSensorBase<std::function<optional<float>()>>;
|
||||||
|
template class TemplateSensorBase<optional<float> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -6,18 +6,38 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateSensor : public sensor::Sensor, public PollingComponent {
|
template<typename F> class TemplateSensorBase : public sensor::Sensor, public PollingComponent {
|
||||||
public:
|
public:
|
||||||
void set_template(std::function<optional<float>()> &&f);
|
void update() override {
|
||||||
|
if (!this->f_.has_value())
|
||||||
void update() override;
|
return;
|
||||||
|
auto val = (*this->f_)();
|
||||||
|
if (val.has_value()) {
|
||||||
|
this->publish_state(*val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
protected:
|
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_
|
} // namespace template_
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ from esphome.const import (
|
|||||||
from .. import template_ns
|
from .. import template_ns
|
||||||
|
|
||||||
TemplateSwitch = template_ns.class_("TemplateSwitch", switch.Switch, cg.Component)
|
TemplateSwitch = template_ns.class_("TemplateSwitch", switch.Switch, cg.Component)
|
||||||
|
StatelessTemplateSwitch = template_ns.class_(
|
||||||
|
"StatelessTemplateSwitch", switch.Switch, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate(config):
|
def validate(config):
|
||||||
@@ -55,14 +58,22 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = await switch.new_switch(config)
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
|
|
||||||
if CONF_LAMBDA in config:
|
if CONF_LAMBDA in config:
|
||||||
|
# Use new_lambda_pvariable to create either TemplateSwitch or StatelessTemplateSwitch
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(bool)
|
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:
|
if CONF_TURN_OFF_ACTION in config:
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
var.get_turn_off_trigger(), [], config[CONF_TURN_OFF_ACTION]
|
var.get_turn_off_trigger(), [], config[CONF_TURN_OFF_ACTION]
|
||||||
|
|||||||
@@ -6,18 +6,8 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.switch";
|
static const char *const TAG = "template.switch";
|
||||||
|
|
||||||
TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
|
// Template instantiations
|
||||||
|
template<typename F> void TemplateSwitchBase<F>::write_state(bool state) {
|
||||||
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) {
|
|
||||||
if (this->prev_trigger_ != nullptr) {
|
if (this->prev_trigger_ != nullptr) {
|
||||||
this->prev_trigger_->stop_action();
|
this->prev_trigger_->stop_action();
|
||||||
}
|
}
|
||||||
@@ -33,13 +23,8 @@ void TemplateSwitch::write_state(bool state) {
|
|||||||
if (this->optimistic_)
|
if (this->optimistic_)
|
||||||
this->publish_state(state);
|
this->publish_state(state);
|
||||||
}
|
}
|
||||||
void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
|
||||||
bool TemplateSwitch::assumed_state() { return this->assumed_state_; }
|
template<typename F> void TemplateSwitchBase<F>::setup() {
|
||||||
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() {
|
|
||||||
optional<bool> initial_state = this->get_initial_state_with_restore_mode();
|
optional<bool> initial_state = this->get_initial_state_with_restore_mode();
|
||||||
|
|
||||||
if (initial_state.has_value()) {
|
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);
|
LOG_SWITCH("", "Template Switch", this);
|
||||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
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 template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -7,28 +7,35 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateSwitch : public switch_::Switch, public Component {
|
template<typename F> class TemplateSwitchBase : public switch_::Switch, public Component {
|
||||||
public:
|
public:
|
||||||
TemplateSwitch();
|
TemplateSwitchBase() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
void set_state_lambda(std::function<optional<bool>()> &&f);
|
void loop() override {
|
||||||
Trigger<> *get_turn_on_trigger() const;
|
if (!this->f_.has_value())
|
||||||
Trigger<> *get_turn_off_trigger() const;
|
return;
|
||||||
void set_optimistic(bool optimistic);
|
auto s = (*this->f_)();
|
||||||
void set_assumed_state(bool assumed_state);
|
if (!s.has_value())
|
||||||
void loop() override;
|
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:
|
protected:
|
||||||
bool assumed_state() override;
|
bool assumed_state() override { return this->assumed_state_; }
|
||||||
|
|
||||||
void write_state(bool state) override;
|
void write_state(bool state) override;
|
||||||
|
|
||||||
optional<std::function<optional<bool>()>> f_;
|
optional<F> f_;
|
||||||
bool optimistic_{false};
|
bool optimistic_{false};
|
||||||
bool assumed_state_{false};
|
bool assumed_state_{false};
|
||||||
Trigger<> *turn_on_trigger_;
|
Trigger<> *turn_on_trigger_;
|
||||||
@@ -36,5 +43,20 @@ class TemplateSwitch : public switch_::Switch, public Component {
|
|||||||
Trigger<> *prev_trigger_{nullptr};
|
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 template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import esphome.codegen as cg
|
|||||||
from esphome.components import text
|
from esphome.components import text
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
CONF_INITIAL_VALUE,
|
CONF_INITIAL_VALUE,
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
CONF_MAX_LENGTH,
|
CONF_MAX_LENGTH,
|
||||||
@@ -16,6 +17,9 @@ from esphome.const import (
|
|||||||
from .. import template_ns
|
from .. import template_ns
|
||||||
|
|
||||||
TemplateText = template_ns.class_("TemplateText", text.Text, cg.PollingComponent)
|
TemplateText = template_ns.class_("TemplateText", text.Text, cg.PollingComponent)
|
||||||
|
StatelessTemplateText = template_ns.class_(
|
||||||
|
"StatelessTemplateText", text.Text, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
TextSaverBase = template_ns.class_("TemplateTextSaverBase")
|
TextSaverBase = template_ns.class_("TemplateTextSaverBase")
|
||||||
TextSaverTemplate = template_ns.class_("TextSaver", TextSaverBase)
|
TextSaverTemplate = template_ns.class_("TextSaver", TextSaverBase)
|
||||||
@@ -65,21 +69,31 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
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:
|
if CONF_LAMBDA in config:
|
||||||
|
# Use new_lambda_pvariable to create either TemplateText or StatelessTemplateText
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
|
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:
|
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]))
|
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||||
if initial_value_config := config.get(CONF_INITIAL_VALUE):
|
if initial_value_config := config.get(CONF_INITIAL_VALUE):
|
||||||
cg.add(var.set_initial_value(initial_value_config))
|
cg.add(var.set_initial_value(initial_value_config))
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.text";
|
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_ == nullptr)) {
|
||||||
if (this->f_.has_value())
|
if (this->f_.has_value())
|
||||||
return;
|
return;
|
||||||
@@ -25,21 +26,7 @@ void TemplateText::setup() {
|
|||||||
this->publish_state(value);
|
this->publish_state(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateText::update() {
|
template<typename F> void TemplateTextBase<F>::control(const std::string &value) {
|
||||||
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) {
|
|
||||||
this->set_trigger_->trigger(value);
|
this->set_trigger_->trigger(value);
|
||||||
|
|
||||||
if (this->optimistic_)
|
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);
|
LOG_TEXT("", "Template Text Input", this);
|
||||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateTextBase<std::function<optional<std::string>()>>;
|
||||||
|
template class TemplateTextBase<optional<std::string> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // 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:
|
public:
|
||||||
void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
|
TemplateTextBase() : set_trigger_(new Trigger<std::string>()) {}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void update() override;
|
|
||||||
void dump_config() 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; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
|
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;
|
void control(const std::string &value) override;
|
||||||
bool optimistic_ = false;
|
bool optimistic_ = false;
|
||||||
std::string initial_value_;
|
std::string initial_value_;
|
||||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
Trigger<std::string> *set_trigger_;
|
||||||
optional<std::function<optional<std::string>()>> f_{nullptr};
|
optional<F> f_{nullptr};
|
||||||
|
|
||||||
TemplateTextSaverBase *pref_ = 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 template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ from .. import template_ns
|
|||||||
TemplateTextSensor = template_ns.class_(
|
TemplateTextSensor = template_ns.class_(
|
||||||
"TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent
|
"TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent
|
||||||
)
|
)
|
||||||
|
StatelessTemplateTextSensor = template_ns.class_(
|
||||||
|
"StatelessTemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
text_sensor.text_sensor_schema()
|
text_sensor.text_sensor_schema()
|
||||||
@@ -24,14 +27,21 @@ CONFIG_SCHEMA = (
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = await text_sensor.new_text_sensor(config)
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
|
|
||||||
if CONF_LAMBDA in config:
|
if CONF_LAMBDA in config:
|
||||||
|
# Use new_lambda_pvariable to create either TemplateTextSensor or StatelessTemplateTextSensor
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
|
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(
|
@automation.register_action(
|
||||||
|
|||||||
@@ -6,18 +6,11 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.text_sensor";
|
static const char *const TAG = "template.text_sensor";
|
||||||
|
|
||||||
void TemplateTextSensor::update() {
|
// Template instantiations
|
||||||
if (!this->f_.has_value())
|
template<typename F> void TemplateTextSensorBase<F>::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); }
|
||||||
return;
|
|
||||||
|
|
||||||
auto val = (*this->f_)();
|
template class TemplateTextSensorBase<std::function<optional<std::string>()>>;
|
||||||
if (val.has_value()) {
|
template class TemplateTextSensorBase<optional<std::string> (*)()>;
|
||||||
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); }
|
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -7,18 +7,38 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
template<typename F> class TemplateTextSensorBase : public text_sensor::TextSensor, public PollingComponent {
|
||||||
public:
|
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 { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
float get_setup_priority() const override;
|
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
protected:
|
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_
|
} // namespace template_
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ from esphome.const import (
|
|||||||
from .. import template_ns
|
from .. import template_ns
|
||||||
|
|
||||||
TemplateValve = template_ns.class_("TemplateValve", valve.Valve, cg.Component)
|
TemplateValve = template_ns.class_("TemplateValve", valve.Valve, cg.Component)
|
||||||
|
StatelessTemplateValve = template_ns.class_(
|
||||||
|
"StatelessTemplateValve", valve.Valve, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
TemplateValvePublishAction = template_ns.class_(
|
TemplateValvePublishAction = template_ns.class_(
|
||||||
"TemplateValvePublishAction", automation.Action, cg.Parented.template(TemplateValve)
|
"TemplateValvePublishAction", automation.Action, cg.Parented.template(TemplateValve)
|
||||||
@@ -62,13 +65,22 @@ CONFIG_SCHEMA = (
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = await valve.new_valve(config)
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
if lambda_config := config.get(CONF_LAMBDA):
|
if lambda_config := config.get(CONF_LAMBDA):
|
||||||
|
# Use new_lambda_pvariable to create either TemplateValve or StatelessTemplateValve
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
lambda_config, [], return_type=cg.optional.template(float)
|
lambda_config, [], return_type=cg.optional.template(float)
|
||||||
)
|
)
|
||||||
cg.add(var.set_state_lambda(template_))
|
var = automation.new_lambda_pvariable(
|
||||||
|
config[CONF_ID], template_, StatelessTemplateValve
|
||||||
|
)
|
||||||
|
# Manually register as valve since we didn't use new_valve
|
||||||
|
await valve.register_valve(var, config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
else:
|
||||||
|
# No lambda - just create the base template valve
|
||||||
|
var = await valve.new_valve(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
if open_action_config := config.get(CONF_OPEN_ACTION):
|
if open_action_config := config.get(CONF_OPEN_ACTION):
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
var.get_open_trigger(), [], open_action_config
|
var.get_open_trigger(), [], open_action_config
|
||||||
|
|||||||
@@ -8,14 +8,8 @@ using namespace esphome::valve;
|
|||||||
|
|
||||||
static const char *const TAG = "template.valve";
|
static const char *const TAG = "template.valve";
|
||||||
|
|
||||||
TemplateValve::TemplateValve()
|
// Template instantiations
|
||||||
: open_trigger_(new Trigger<>()),
|
template<typename F> void TemplateValveBase<F>::setup() {
|
||||||
close_trigger_(new Trigger<>),
|
|
||||||
stop_trigger_(new Trigger<>()),
|
|
||||||
toggle_trigger_(new Trigger<>()),
|
|
||||||
position_trigger_(new Trigger<float>()) {}
|
|
||||||
|
|
||||||
void TemplateValve::setup() {
|
|
||||||
switch (this->restore_mode_) {
|
switch (this->restore_mode_) {
|
||||||
case VALVE_NO_RESTORE:
|
case VALVE_NO_RESTORE:
|
||||||
break;
|
break;
|
||||||
@@ -35,35 +29,7 @@ void TemplateValve::setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateValve::loop() {
|
template<typename F> void TemplateValveBase<F>::dump_config() {
|
||||||
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 (changed)
|
|
||||||
this->publish_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
|
||||||
void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
|
||||||
void TemplateValve::set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; }
|
|
||||||
float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; }
|
|
||||||
|
|
||||||
Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; }
|
|
||||||
Trigger<> *TemplateValve::get_close_trigger() const { return this->close_trigger_; }
|
|
||||||
Trigger<> *TemplateValve::get_stop_trigger() const { return this->stop_trigger_; }
|
|
||||||
Trigger<> *TemplateValve::get_toggle_trigger() const { return this->toggle_trigger_; }
|
|
||||||
|
|
||||||
void TemplateValve::dump_config() {
|
|
||||||
LOG_VALVE("", "Template Valve", this);
|
LOG_VALVE("", "Template Valve", this);
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
" Has position: %s\n"
|
" Has position: %s\n"
|
||||||
@@ -71,7 +37,7 @@ void TemplateValve::dump_config() {
|
|||||||
YESNO(this->has_position_), YESNO(this->optimistic_));
|
YESNO(this->has_position_), YESNO(this->optimistic_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateValve::control(const ValveCall &call) {
|
template<typename F> void TemplateValveBase<F>::control(const ValveCall &call) {
|
||||||
if (call.get_stop()) {
|
if (call.get_stop()) {
|
||||||
this->stop_prev_trigger_();
|
this->stop_prev_trigger_();
|
||||||
this->stop_trigger_->trigger();
|
this->stop_trigger_->trigger();
|
||||||
@@ -106,7 +72,7 @@ void TemplateValve::control(const ValveCall &call) {
|
|||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
ValveTraits TemplateValve::get_traits() {
|
template<typename F> valve::ValveTraits TemplateValveBase<F>::get_traits() {
|
||||||
auto traits = ValveTraits();
|
auto traits = ValveTraits();
|
||||||
traits.set_is_assumed_state(this->assumed_state_);
|
traits.set_is_assumed_state(this->assumed_state_);
|
||||||
traits.set_supports_stop(this->has_stop_);
|
traits.set_supports_stop(this->has_stop_);
|
||||||
@@ -115,18 +81,15 @@ ValveTraits TemplateValve::get_traits() {
|
|||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
Trigger<float> *TemplateValve::get_position_trigger() const { return this->position_trigger_; }
|
template<typename F> void TemplateValveBase<F>::stop_prev_trigger_() {
|
||||||
|
|
||||||
void TemplateValve::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
|
|
||||||
void TemplateValve::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
|
|
||||||
void TemplateValve::set_has_position(bool has_position) { this->has_position_ = has_position; }
|
|
||||||
|
|
||||||
void TemplateValve::stop_prev_trigger_() {
|
|
||||||
if (this->prev_command_trigger_ != nullptr) {
|
if (this->prev_command_trigger_ != nullptr) {
|
||||||
this->prev_command_trigger_->stop_action();
|
this->prev_command_trigger_->stop_action();
|
||||||
this->prev_command_trigger_ = nullptr;
|
this->prev_command_trigger_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template class TemplateValveBase<std::function<optional<float>()>>;
|
||||||
|
template class TemplateValveBase<optional<float> (*)()>;
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -13,28 +13,48 @@ enum TemplateValveRestoreMode {
|
|||||||
VALVE_RESTORE_AND_CALL,
|
VALVE_RESTORE_AND_CALL,
|
||||||
};
|
};
|
||||||
|
|
||||||
class TemplateValve : public valve::Valve, public Component {
|
template<typename F> class TemplateValveBase : public valve::Valve, public Component {
|
||||||
public:
|
public:
|
||||||
TemplateValve();
|
TemplateValveBase()
|
||||||
|
: open_trigger_(new Trigger<>()),
|
||||||
|
close_trigger_(new Trigger<>()),
|
||||||
|
stop_trigger_(new Trigger<>()),
|
||||||
|
toggle_trigger_(new Trigger<>()),
|
||||||
|
position_trigger_(new Trigger<float>()) {}
|
||||||
|
|
||||||
void set_state_lambda(std::function<optional<float>()> &&f);
|
void loop() override {
|
||||||
Trigger<> *get_open_trigger() const;
|
bool changed = false;
|
||||||
Trigger<> *get_close_trigger() const;
|
|
||||||
Trigger<> *get_stop_trigger() const;
|
if (this->state_f_.has_value()) {
|
||||||
Trigger<> *get_toggle_trigger() const;
|
auto s = (*this->state_f_)();
|
||||||
Trigger<float> *get_position_trigger() const;
|
if (s.has_value()) {
|
||||||
void set_optimistic(bool optimistic);
|
auto pos = clamp(*s, 0.0f, 1.0f);
|
||||||
void set_assumed_state(bool assumed_state);
|
if (pos != this->position) {
|
||||||
void set_has_stop(bool has_stop);
|
this->position = pos;
|
||||||
void set_has_position(bool has_position);
|
changed = true;
|
||||||
void set_has_toggle(bool has_toggle);
|
}
|
||||||
void set_restore_mode(TemplateValveRestoreMode restore_mode) { restore_mode_ = restore_mode; }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void loop() override;
|
|
||||||
void dump_config() 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_; }
|
||||||
|
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_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
|
||||||
|
void set_restore_mode(TemplateValveRestoreMode restore_mode) { restore_mode_ = restore_mode; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(const valve::ValveCall &call) override;
|
void control(const valve::ValveCall &call) override;
|
||||||
@@ -42,7 +62,7 @@ class TemplateValve : public valve::Valve, public Component {
|
|||||||
void stop_prev_trigger_();
|
void stop_prev_trigger_();
|
||||||
|
|
||||||
TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE};
|
TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE};
|
||||||
optional<std::function<optional<float>()>> state_f_;
|
optional<F> state_f_;
|
||||||
bool assumed_state_{false};
|
bool assumed_state_{false};
|
||||||
bool optimistic_{false};
|
bool optimistic_{false};
|
||||||
Trigger<> *open_trigger_;
|
Trigger<> *open_trigger_;
|
||||||
@@ -56,5 +76,20 @@ class TemplateValve : public valve::Valve, public Component {
|
|||||||
bool has_position_{false};
|
bool has_position_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TemplateValve : public TemplateValveBase<std::function<optional<float>()>> {
|
||||||
|
public:
|
||||||
|
void set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Optimized template valve 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 StatelessTemplateValve : public TemplateValveBase<optional<float> (*)()> {
|
||||||
|
public:
|
||||||
|
explicit StatelessTemplateValve(optional<float> (*f)()) { this->state_f_ = f; }
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -79,6 +79,18 @@ template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
|
|||||||
std::function<bool(Ts...)> f_;
|
std::function<bool(Ts...)> f_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Optimized lambda condition 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).
|
||||||
|
template<typename... Ts> class StatelessLambdaCondition : public Condition<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {}
|
||||||
|
bool check(Ts... x) override { return this->f_(x...); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool (*f_)(Ts...);
|
||||||
|
};
|
||||||
|
|
||||||
template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
|
template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
|
||||||
public:
|
public:
|
||||||
explicit ForCondition(Condition<> *condition) : condition_(condition) {}
|
explicit ForCondition(Condition<> *condition) : condition_(condition) {}
|
||||||
@@ -190,6 +202,19 @@ template<typename... Ts> class LambdaAction : public Action<Ts...> {
|
|||||||
std::function<void(Ts...)> f_;
|
std::function<void(Ts...)> f_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Optimized lambda action 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).
|
||||||
|
template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {}
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->f_(x...); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void (*f_)(Ts...);
|
||||||
|
};
|
||||||
|
|
||||||
template<typename... Ts> class IfAction : public Action<Ts...> {
|
template<typename... Ts> class IfAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}
|
explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}
|
||||||
|
|||||||
@@ -198,6 +198,8 @@ class LambdaExpression(Expression):
|
|||||||
self.return_type = safe_exp(return_type) if return_type is not None else None
|
self.return_type = safe_exp(return_type) if return_type is not None else None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
# Stateless lambdas (empty capture) implicitly convert to function pointers
|
||||||
|
# when assigned to function pointer types - no unary + needed
|
||||||
cpp = f"[{self.capture}]({self.parameters})"
|
cpp = f"[{self.capture}]({self.parameters})"
|
||||||
if self.return_type is not None:
|
if self.return_type is not None:
|
||||||
cpp += f" -> {self.return_type}"
|
cpp += f" -> {self.return_type}"
|
||||||
@@ -700,6 +702,12 @@ async def process_lambda(
|
|||||||
parts[i * 3 + 1] = var
|
parts[i * 3 + 1] = var
|
||||||
parts[i * 3 + 2] = ""
|
parts[i * 3 + 2] = ""
|
||||||
|
|
||||||
|
# All id() references are global variables in generated C++ code.
|
||||||
|
# Global variables should not be captured - they're accessible everywhere.
|
||||||
|
# Use empty capture instead of capture-by-value.
|
||||||
|
if capture == "=":
|
||||||
|
capture = ""
|
||||||
|
|
||||||
if isinstance(value, ESPHomeDataBase) and value.esp_range is not None:
|
if isinstance(value, ESPHomeDataBase) and value.esp_range is not None:
|
||||||
location = value.esp_range.start_mark
|
location = value.esp_range.start_mark
|
||||||
location.line += value.content_offset
|
location.line += value.content_offset
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ def test_text_config_value_mode_set(generate_main):
|
|||||||
|
|
||||||
def test_text_config_lamda_is_set(generate_main):
|
def test_text_config_lamda_is_set(generate_main):
|
||||||
"""
|
"""
|
||||||
Test if lambda is set for lambda mode
|
Test if lambda is set for lambda mode (optimized with stateless lambda)
|
||||||
"""
|
"""
|
||||||
# Given
|
# Given
|
||||||
|
|
||||||
@@ -66,5 +66,5 @@ def test_text_config_lamda_is_set(generate_main):
|
|||||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||||
|
|
||||||
# Then
|
# Then
|
||||||
assert "it_4->set_template([=]() -> esphome::optional<std::string> {" in main_cpp
|
assert "it_4->set_template([]() -> esphome::optional<std::string> {" in main_cpp
|
||||||
assert 'return std::string{"Hello"};' in main_cpp
|
assert 'return std::string{"Hello"};' in main_cpp
|
||||||
|
|||||||
@@ -173,6 +173,61 @@ class TestLambdaExpression:
|
|||||||
"}"
|
"}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_str__stateless_no_return(self):
|
||||||
|
"""Test stateless lambda (empty capture) generates correctly"""
|
||||||
|
target = cg.LambdaExpression(
|
||||||
|
('ESP_LOGD("main", "Test message");',),
|
||||||
|
(), # No parameters
|
||||||
|
"", # Empty capture (stateless)
|
||||||
|
)
|
||||||
|
|
||||||
|
actual = str(target)
|
||||||
|
|
||||||
|
assert actual == ('[]() {\n ESP_LOGD("main", "Test message");\n}')
|
||||||
|
|
||||||
|
def test_str__stateless_with_return(self):
|
||||||
|
"""Test stateless lambda with return type generates correctly"""
|
||||||
|
target = cg.LambdaExpression(
|
||||||
|
("return global_value > 0;",),
|
||||||
|
(), # No parameters
|
||||||
|
"", # Empty capture (stateless)
|
||||||
|
bool, # Return type
|
||||||
|
)
|
||||||
|
|
||||||
|
actual = str(target)
|
||||||
|
|
||||||
|
assert actual == ("[]() -> bool {\n return global_value > 0;\n}")
|
||||||
|
|
||||||
|
def test_str__stateless_with_params(self):
|
||||||
|
"""Test stateless lambda with parameters generates correctly"""
|
||||||
|
target = cg.LambdaExpression(
|
||||||
|
("return foo + bar;",),
|
||||||
|
((int, "foo"), (float, "bar")),
|
||||||
|
"", # Empty capture (stateless)
|
||||||
|
float,
|
||||||
|
)
|
||||||
|
|
||||||
|
actual = str(target)
|
||||||
|
|
||||||
|
assert actual == (
|
||||||
|
"[](int32_t foo, float bar) -> float {\n return foo + bar;\n}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_str__with_capture(self):
|
||||||
|
"""Test lambda with capture generates correctly"""
|
||||||
|
target = cg.LambdaExpression(
|
||||||
|
("return captured_var + x;",),
|
||||||
|
((int, "x"),),
|
||||||
|
"captured_var", # Has capture (not stateless)
|
||||||
|
int,
|
||||||
|
)
|
||||||
|
|
||||||
|
actual = str(target)
|
||||||
|
|
||||||
|
assert actual == (
|
||||||
|
"[captured_var](int32_t x) -> int32_t {\n return captured_var + x;\n}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestLiterals:
|
class TestLiterals:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|||||||
Reference in New Issue
Block a user