1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-31 23:21:54 +00:00

Merge branch 'template_value_func_pointers' into integration

This commit is contained in:
J. Nick Koston
2025-10-26 12:19:08 -07:00
10 changed files with 38 additions and 131 deletions

View File

@@ -16,12 +16,7 @@ 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 ( from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
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
@@ -92,7 +87,6 @@ def validate_potentially_or_condition(value):
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action) IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action) WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
@@ -103,40 +97,9 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation") Automation = cg.esphome_ns.class_("Automation")
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", 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 = {}
@@ -277,9 +240,7 @@ async def lambda_condition_to_code(
args: TemplateArgsType, args: TemplateArgsType,
) -> MockObj: ) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=bool) lambda_ = await cg.process_lambda(config, args, return_type=bool)
return new_lambda_pvariable( return cg.new_Pvariable(condition_id, template_arg, lambda_)
condition_id, lambda_, StatelessLambdaCondition, template_arg
)
@register_condition( @register_condition(
@@ -445,7 +406,7 @@ async def lambda_action_to_code(
args: TemplateArgsType, args: TemplateArgsType,
) -> MockObj: ) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=cg.void) lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg) return cg.new_Pvariable(action_id, template_arg, lambda_)
@register_action( @register_action(

View File

@@ -155,7 +155,6 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component) AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
StatelessLambdaFilter = binary_sensor_ns.class_("StatelessLambdaFilter", Filter)
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component) SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
_LOGGER = getLogger(__name__) _LOGGER = getLogger(__name__)
@@ -300,7 +299,7 @@ async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool) config, [(bool, "x")], return_type=cg.optional.template(bool)
) )
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) return cg.new_Pvariable(filter_id, lambda_)
@register_filter( @register_filter(

View File

@@ -111,21 +111,6 @@ class LambdaFilter : public Filter {
std::function<optional<bool>(bool)> f_; std::function<optional<bool>(bool)> f_;
}; };
/** Optimized lambda filter 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 StatelessLambdaFilter : public Filter {
public:
explicit StatelessLambdaFilter(optional<bool> (*f)(bool)) : f_(f) {}
optional<bool> new_value(bool value) override { return this->f_(value); }
protected:
optional<bool> (*f_)(bool);
};
class SettleFilter : public Filter, public Component { class SettleFilter : public Filter, public Component {
public: public:
optional<bool> new_value(bool value) override; optional<bool> new_value(bool value) override;

View File

@@ -1,7 +1,7 @@
import re import re
from esphome import automation from esphome import automation
from esphome.automation import LambdaAction, StatelessLambdaAction from esphome.automation import LambdaAction
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
@@ -430,9 +430,7 @@ async def logger_log_action_to_code(config, action_id, template_arg, args):
text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_))) text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return automation.new_lambda_pvariable( return cg.new_Pvariable(action_id, template_arg, lambda_)
action_id, lambda_, StatelessLambdaAction, template_arg
)
@automation.register_action( @automation.register_action(
@@ -457,9 +455,7 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
text = str(cg.statement(logger.set_log_level(level))) text = str(cg.statement(logger.set_log_level(level)))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return automation.new_lambda_pvariable( return cg.new_Pvariable(action_id, template_arg, lambda_)
action_id, lambda_, StatelessLambdaAction, template_arg
)
FILTER_SOURCE_FILES = filter_source_files_from_platform( FILTER_SOURCE_FILES = filter_source_files_from_platform(

View File

@@ -261,7 +261,6 @@ ExponentialMovingAverageFilter = sensor_ns.class_(
) )
ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component) ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component)
LambdaFilter = sensor_ns.class_("LambdaFilter", Filter) LambdaFilter = sensor_ns.class_("LambdaFilter", Filter)
StatelessLambdaFilter = sensor_ns.class_("StatelessLambdaFilter", Filter)
OffsetFilter = sensor_ns.class_("OffsetFilter", Filter) OffsetFilter = sensor_ns.class_("OffsetFilter", Filter)
MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter)
ValueListFilter = sensor_ns.class_("ValueListFilter", Filter) ValueListFilter = sensor_ns.class_("ValueListFilter", Filter)
@@ -574,7 +573,7 @@ async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config, [(float, "x")], return_type=cg.optional.template(float) config, [(float, "x")], return_type=cg.optional.template(float)
) )
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) return cg.new_Pvariable(filter_id, lambda_)
DELTA_SCHEMA = cv.Schema( DELTA_SCHEMA = cv.Schema(

View File

@@ -296,21 +296,6 @@ class LambdaFilter : public Filter {
lambda_filter_t lambda_filter_; lambda_filter_t lambda_filter_;
}; };
/** Optimized lambda filter 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 StatelessLambdaFilter : public Filter {
public:
explicit StatelessLambdaFilter(optional<float> (*lambda_filter)(float)) : lambda_filter_(lambda_filter) {}
optional<float> new_value(float value) override { return this->lambda_filter_(value); }
protected:
optional<float> (*lambda_filter_)(float);
};
/// A simple filter that adds `offset` to each value it receives. /// A simple filter that adds `offset` to each value it receives.
class OffsetFilter : public Filter { class OffsetFilter : public Filter {
public: public:

View File

@@ -57,7 +57,6 @@ validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
# Filters # Filters
Filter = text_sensor_ns.class_("Filter") Filter = text_sensor_ns.class_("Filter")
LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter) LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter)
StatelessLambdaFilter = text_sensor_ns.class_("StatelessLambdaFilter", Filter)
ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter) ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter)
ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter) ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter)
AppendFilter = text_sensor_ns.class_("AppendFilter", Filter) AppendFilter = text_sensor_ns.class_("AppendFilter", Filter)
@@ -71,7 +70,7 @@ async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string) config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string)
) )
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) return cg.new_Pvariable(filter_id, lambda_)
@FILTER_REGISTRY.register("to_upper", ToUpperFilter, {}) @FILTER_REGISTRY.register("to_upper", ToUpperFilter, {})

View File

@@ -62,21 +62,6 @@ class LambdaFilter : public Filter {
lambda_filter_t lambda_filter_; lambda_filter_t lambda_filter_;
}; };
/** Optimized lambda filter 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 StatelessLambdaFilter : public Filter {
public:
explicit StatelessLambdaFilter(optional<std::string> (*lambda_filter)(std::string)) : lambda_filter_(lambda_filter) {}
optional<std::string> new_value(std::string value) override { return this->lambda_filter_(value); }
protected:
optional<std::string> (*lambda_filter_)(std::string);
};
/// A simple filter that converts all text to uppercase /// A simple filter that converts all text to uppercase
class ToUpperFilter : public Filter { class ToUpperFilter : public Filter {
public: public:

View File

@@ -27,11 +27,21 @@ template<typename T, typename... X> class TemplatableValue {
public: public:
TemplatableValue() : type_(NONE) {} TemplatableValue() : type_(NONE) {}
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0> TemplatableValue(F value) : type_(VALUE) { template<typename F>
requires(!std::invocable<F, X...>) TemplatableValue(F value) : type_(VALUE) {
new (&this->value_) T(std::move(value)); new (&this->value_) T(std::move(value));
} }
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) { // For stateless lambdas (convertible to function pointer): use function pointer
template<typename F>
requires std::invocable<F, X...> && std::convertible_to<F, T (*)(X...)> TemplatableValue(F f)
: type_(STATELESS_LAMBDA) {
this->stateless_f_ = f; // Implicit conversion to function pointer
}
// For stateful lambdas (not convertible to function pointer): use std::function
template<typename F>
requires std::invocable<F, X...> &&(!std::convertible_to<F, T (*)(X...)>) TemplatableValue(F f) : type_(LAMBDA) {
this->f_ = new std::function<T(X...)>(std::move(f)); this->f_ = new std::function<T(X...)>(std::move(f));
} }
@@ -41,6 +51,8 @@ template<typename T, typename... X> class TemplatableValue {
new (&this->value_) T(other.value_); new (&this->value_) T(other.value_);
} else if (type_ == LAMBDA) { } else if (type_ == LAMBDA) {
this->f_ = new std::function<T(X...)>(*other.f_); this->f_ = new std::function<T(X...)>(*other.f_);
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
} }
} }
@@ -51,6 +63,8 @@ template<typename T, typename... X> class TemplatableValue {
} else if (type_ == LAMBDA) { } else if (type_ == LAMBDA) {
this->f_ = other.f_; this->f_ = other.f_;
other.f_ = nullptr; other.f_ = nullptr;
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
} }
other.type_ = NONE; other.type_ = NONE;
} }
@@ -78,16 +92,23 @@ template<typename T, typename... X> class TemplatableValue {
} else if (type_ == LAMBDA) { } else if (type_ == LAMBDA) {
delete this->f_; delete this->f_;
} }
// STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty, not heap-allocated)
} }
bool has_value() { return this->type_ != NONE; } bool has_value() { return this->type_ != NONE; }
T value(X... x) { T value(X... x) {
if (this->type_ == LAMBDA) { switch (this->type_) {
return (*this->f_)(x...); case STATELESS_LAMBDA:
return this->stateless_f_(x...); // Direct function pointer call
case LAMBDA:
return (*this->f_)(x...); // std::function call
case VALUE:
return this->value_;
case NONE:
default:
return T{};
} }
// return value also when none
return this->type_ == VALUE ? this->value_ : T{};
} }
optional<T> optional_value(X... x) { optional<T> optional_value(X... x) {
@@ -109,11 +130,13 @@ template<typename T, typename... X> class TemplatableValue {
NONE, NONE,
VALUE, VALUE,
LAMBDA, LAMBDA,
STATELESS_LAMBDA,
} type_; } type_;
union { union {
T value_; T value_;
std::function<T(X...)> *f_; std::function<T(X...)> *f_;
T (*stateless_f_)(X...);
}; };
}; };

View File

@@ -79,18 +79,6 @@ 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) {}
@@ -202,19 +190,6 @@ 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) {}