mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 13:27:14 +00:00
[core] Add TemplatableFn for 4-byte function-pointer templatable storage (#15545)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -19,8 +19,8 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
sensor::Sensor *sensor_{nullptr};
|
sensor::Sensor *sensor_{nullptr};
|
||||||
TemplatableValue<float> upper_threshold_{};
|
TemplatableFn<float> upper_threshold_{};
|
||||||
TemplatableValue<float> lower_threshold_{};
|
TemplatableFn<float> lower_threshold_{};
|
||||||
bool raw_state_{false}; // Pre-filter state for hysteresis logic
|
bool raw_state_{false}; // Pre-filter state for hysteresis logic
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIServer *parent_;
|
APIServer *parent_;
|
||||||
TemplatableValue<bool, Ts...> success_{true};
|
TemplatableFn<bool, Ts...> success_{[](Ts...) -> bool { return true; }};
|
||||||
TemplatableValue<std::string, Ts...> error_message_{""};
|
TemplatableValue<std::string, Ts...> error_message_{""};
|
||||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||||
std::function<void(Ts..., JsonObject)> json_builder_;
|
std::function<void(Ts..., JsonObject)> json_builder_;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class TimeoutFilter : public Filter, public Component {
|
|||||||
template<typename T> void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; }
|
template<typename T> void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TemplatableValue<uint32_t> timeout_delay_{};
|
TemplatableFn<uint32_t> timeout_delay_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class DelayedOnOffFilter final : public Filter, public Component {
|
class DelayedOnOffFilter final : public Filter, public Component {
|
||||||
@@ -49,8 +49,8 @@ class DelayedOnOffFilter final : public Filter, public Component {
|
|||||||
template<typename T> void set_off_delay(T delay) { this->off_delay_ = delay; }
|
template<typename T> void set_off_delay(T delay) { this->off_delay_ = delay; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TemplatableValue<uint32_t> on_delay_{};
|
TemplatableFn<uint32_t> on_delay_{};
|
||||||
TemplatableValue<uint32_t> off_delay_{};
|
TemplatableFn<uint32_t> off_delay_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class DelayedOnFilter : public Filter, public Component {
|
class DelayedOnFilter : public Filter, public Component {
|
||||||
@@ -62,7 +62,7 @@ class DelayedOnFilter : public Filter, public Component {
|
|||||||
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
|
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TemplatableValue<uint32_t> delay_{};
|
TemplatableFn<uint32_t> delay_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class DelayedOffFilter : public Filter, public Component {
|
class DelayedOffFilter : public Filter, public Component {
|
||||||
@@ -74,7 +74,7 @@ class DelayedOffFilter : public Filter, public Component {
|
|||||||
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
|
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TemplatableValue<uint32_t> delay_{};
|
TemplatableFn<uint32_t> delay_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class InvertFilter : public Filter {
|
class InvertFilter : public Filter {
|
||||||
@@ -155,7 +155,7 @@ class SettleFilter : public Filter, public Component {
|
|||||||
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
|
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TemplatableValue<uint32_t> delay_{};
|
TemplatableFn<uint32_t> delay_{};
|
||||||
bool steady_{true};
|
bool steady_{true};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -423,11 +423,10 @@ def _register_setter_actions():
|
|||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
await cg.register_parented(var, config[CONF_ID])
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
data = config[CONF_VALUE]
|
data = config[CONF_VALUE]
|
||||||
if cg.is_template(data):
|
if _map and not cg.is_template(data):
|
||||||
templ_ = await cg.templatable(data, args, _type)
|
data = _map[data]
|
||||||
cg.add(getattr(var, _setter)(templ_))
|
templ_ = await cg.templatable(data, args, _type)
|
||||||
else:
|
cg.add(getattr(var, _setter)(templ_))
|
||||||
cg.add(getattr(var, _setter)(_map[data] if _map else data))
|
|
||||||
return var
|
return var
|
||||||
|
|
||||||
automation.register_action(
|
automation.register_action(
|
||||||
|
|||||||
@@ -204,7 +204,8 @@ async def datetime_date_set_to_code(config, action_id, template_arg, args):
|
|||||||
("month", date_config[CONF_MONTH]),
|
("month", date_config[CONF_MONTH]),
|
||||||
("year", date_config[CONF_YEAR]),
|
("year", date_config[CONF_YEAR]),
|
||||||
)
|
)
|
||||||
cg.add(action_var.set_date(date_struct))
|
template_ = await cg.templatable(date_struct, args, cg.ESPTime)
|
||||||
|
cg.add(action_var.set_date(template_))
|
||||||
return action_var
|
return action_var
|
||||||
|
|
||||||
|
|
||||||
@@ -236,7 +237,8 @@ async def datetime_time_set_to_code(config, action_id, template_arg, args):
|
|||||||
("minute", time_config[CONF_MINUTE]),
|
("minute", time_config[CONF_MINUTE]),
|
||||||
("hour", time_config[CONF_HOUR]),
|
("hour", time_config[CONF_HOUR]),
|
||||||
)
|
)
|
||||||
cg.add(action_var.set_time(time_struct))
|
template_ = await cg.templatable(time_struct, args, cg.ESPTime)
|
||||||
|
cg.add(action_var.set_time(template_))
|
||||||
return action_var
|
return action_var
|
||||||
|
|
||||||
|
|
||||||
@@ -271,5 +273,6 @@ async def datetime_datetime_set_to_code(config, action_id, template_arg, args):
|
|||||||
("month", datetime_config[CONF_MONTH]),
|
("month", datetime_config[CONF_MONTH]),
|
||||||
("year", datetime_config[CONF_YEAR]),
|
("year", datetime_config[CONF_YEAR]),
|
||||||
)
|
)
|
||||||
cg.add(action_var.set_datetime(datetime_struct))
|
template_ = await cg.templatable(datetime_struct, args, cg.ESPTime)
|
||||||
|
cg.add(action_var.set_datetime(template_))
|
||||||
return action_var
|
return action_var
|
||||||
|
|||||||
@@ -207,7 +207,8 @@ async def display_page_show_to_code(config, action_id, template_arg, args):
|
|||||||
cg.add(var.set_page(template_))
|
cg.add(var.set_page(template_))
|
||||||
else:
|
else:
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
cg.add(var.set_page(paren))
|
template_ = await cg.templatable(paren, args, DisplayPagePtr)
|
||||||
|
cg.add(var.set_page(template_))
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -378,7 +378,8 @@ async def esp32_ble_tracker_start_scan_action_to_code(
|
|||||||
):
|
):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
cg.add(var.set_continuous(config[CONF_CONTINUOUS]))
|
template_ = await cg.templatable(config[CONF_CONTINUOUS], args, cg.bool_)
|
||||||
|
cg.add(var.set_continuous(template_))
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ async def globals_set_to_code(config, action_id, template_arg, args):
|
|||||||
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
templ = await cg.templatable(
|
templ = await cg.templatable(
|
||||||
config[CONF_VALUE], args, None, to_exp=cg.RawExpression
|
config[CONF_VALUE], args, None, to_exp=cg.RawExpression, wrap_constant=True
|
||||||
)
|
)
|
||||||
cg.add(var.set_value(templ))
|
cg.add(var.set_value(templ))
|
||||||
return var
|
return var
|
||||||
|
|||||||
@@ -302,11 +302,13 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
|||||||
|
|
||||||
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||||
cg.add(var.set_url(template_))
|
cg.add(var.set_url(template_))
|
||||||
cg.add(var.set_method(config[CONF_METHOD]))
|
template_ = await cg.templatable(config[CONF_METHOD], args, cg.const_char_ptr)
|
||||||
|
cg.add(var.set_method(template_))
|
||||||
|
|
||||||
capture_response = config[CONF_CAPTURE_RESPONSE]
|
capture_response = config[CONF_CAPTURE_RESPONSE]
|
||||||
if capture_response:
|
if capture_response:
|
||||||
cg.add(var.set_capture_response(capture_response))
|
template_ = await cg.templatable(capture_response, args, cg.bool_)
|
||||||
|
cg.add(var.set_capture_response(template_))
|
||||||
cg.add_define("USE_HTTP_REQUEST_RESPONSE")
|
cg.add_define("USE_HTTP_REQUEST_RESPONSE")
|
||||||
|
|
||||||
cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE]))
|
cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE]))
|
||||||
|
|||||||
@@ -457,7 +457,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void init_request_headers(size_t count) { this->request_headers_.init(count); }
|
void init_request_headers(size_t count) { this->request_headers_.init(count); }
|
||||||
void add_request_header(const char *key, TemplatableValue<const char *, Ts...> value) {
|
void add_request_header(const char *key, TemplatableFn<const char *, Ts...> value) {
|
||||||
this->request_headers_.push_back({key, value});
|
this->request_headers_.push_back({key, value});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,7 +560,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
HttpRequestComponent *parent_;
|
HttpRequestComponent *parent_;
|
||||||
FixedVector<std::pair<const char *, TemplatableValue<const char *, Ts...>>> request_headers_{};
|
FixedVector<std::pair<const char *, TemplatableFn<const char *, Ts...>>> request_headers_{};
|
||||||
std::vector<std::string> lower_case_collect_headers_{"content-type", "content-length"};
|
std::vector<std::string> lower_case_collect_headers_{"content-type", "content-length"};
|
||||||
FixedVector<std::pair<const char *, TemplatableValue<std::string, Ts...>>> json_{};
|
FixedVector<std::pair<const char *, TemplatableValue<std::string, Ts...>>> json_{};
|
||||||
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
||||||
|
|||||||
@@ -24,51 +24,60 @@ template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
|||||||
LightState *state_;
|
LightState *state_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Compact light control action — each field is a function pointer (nullptr = unset).
|
|
||||||
/// Codegen wraps constants in stateless lambdas. 72 bytes vs 128 with TemplatableValue.
|
|
||||||
template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit LightControlAction(LightState *parent) : parent_(parent) {}
|
explicit LightControlAction(LightState *parent) : parent_(parent) {}
|
||||||
|
|
||||||
#define LIGHT_CONTROL_FIELDS(X) \
|
TEMPLATABLE_VALUE(ColorMode, color_mode)
|
||||||
X(ColorMode, color_mode) \
|
TEMPLATABLE_VALUE(bool, state)
|
||||||
X(bool, state) \
|
TEMPLATABLE_VALUE(uint32_t, transition_length)
|
||||||
X(uint32_t, transition_length) \
|
TEMPLATABLE_VALUE(uint32_t, flash_length)
|
||||||
X(uint32_t, flash_length) \
|
TEMPLATABLE_VALUE(float, brightness)
|
||||||
X(float, brightness) \
|
TEMPLATABLE_VALUE(float, color_brightness)
|
||||||
X(float, color_brightness) \
|
TEMPLATABLE_VALUE(float, red)
|
||||||
X(float, red) \
|
TEMPLATABLE_VALUE(float, green)
|
||||||
X(float, green) \
|
TEMPLATABLE_VALUE(float, blue)
|
||||||
X(float, blue) \
|
TEMPLATABLE_VALUE(float, white)
|
||||||
X(float, white) \
|
TEMPLATABLE_VALUE(float, color_temperature)
|
||||||
X(float, color_temperature) \
|
TEMPLATABLE_VALUE(float, cold_white)
|
||||||
X(float, cold_white) \
|
TEMPLATABLE_VALUE(float, warm_white)
|
||||||
X(float, warm_white) \
|
TEMPLATABLE_VALUE(uint32_t, effect)
|
||||||
X(uint32_t, effect)
|
|
||||||
|
|
||||||
#define LIGHT_FIELD_SETTER_(type, name) \
|
|
||||||
void set_##name(type (*f)(Ts...)) { this->name##_ = f; }
|
|
||||||
#define LIGHT_FIELD_APPLY_(type, name) \
|
|
||||||
if (this->name##_) \
|
|
||||||
call.set_##name(this->name##_(x...));
|
|
||||||
#define LIGHT_FIELD_DECL_(type, name) type (*name##_)(Ts...){nullptr};
|
|
||||||
|
|
||||||
LIGHT_CONTROL_FIELDS(LIGHT_FIELD_SETTER_)
|
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(const Ts &...x) override {
|
||||||
auto call = this->parent_->make_call();
|
auto call = this->parent_->make_call();
|
||||||
LIGHT_CONTROL_FIELDS(LIGHT_FIELD_APPLY_)
|
if (this->color_mode_.has_value())
|
||||||
|
call.set_color_mode(this->color_mode_.value(x...));
|
||||||
|
if (this->state_.has_value())
|
||||||
|
call.set_state(this->state_.value(x...));
|
||||||
|
if (this->transition_length_.has_value())
|
||||||
|
call.set_transition_length(this->transition_length_.value(x...));
|
||||||
|
if (this->flash_length_.has_value())
|
||||||
|
call.set_flash_length(this->flash_length_.value(x...));
|
||||||
|
if (this->brightness_.has_value())
|
||||||
|
call.set_brightness(this->brightness_.value(x...));
|
||||||
|
if (this->color_brightness_.has_value())
|
||||||
|
call.set_color_brightness(this->color_brightness_.value(x...));
|
||||||
|
if (this->red_.has_value())
|
||||||
|
call.set_red(this->red_.value(x...));
|
||||||
|
if (this->green_.has_value())
|
||||||
|
call.set_green(this->green_.value(x...));
|
||||||
|
if (this->blue_.has_value())
|
||||||
|
call.set_blue(this->blue_.value(x...));
|
||||||
|
if (this->white_.has_value())
|
||||||
|
call.set_white(this->white_.value(x...));
|
||||||
|
if (this->color_temperature_.has_value())
|
||||||
|
call.set_color_temperature(this->color_temperature_.value(x...));
|
||||||
|
if (this->cold_white_.has_value())
|
||||||
|
call.set_cold_white(this->cold_white_.value(x...));
|
||||||
|
if (this->warm_white_.has_value())
|
||||||
|
call.set_warm_white(this->warm_white_.value(x...));
|
||||||
|
if (this->effect_.has_value())
|
||||||
|
call.set_effect(this->effect_.value(x...));
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
LightState *parent_;
|
LightState *parent_;
|
||||||
LIGHT_CONTROL_FIELDS(LIGHT_FIELD_DECL_)
|
|
||||||
|
|
||||||
#undef LIGHT_FIELD_DECL_
|
|
||||||
#undef LIGHT_FIELD_APPLY_
|
|
||||||
#undef LIGHT_FIELD_SETTER_
|
|
||||||
#undef LIGHT_CONTROL_FIELDS
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class DimRelativeAction : public Action<Ts...> {
|
template<typename... Ts> class DimRelativeAction : public Action<Ts...> {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from typing import Any
|
|
||||||
|
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.config import path_context
|
from esphome.config import path_context
|
||||||
@@ -30,7 +28,7 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE, EsphomeError, Lambda
|
from esphome.core import CORE, EsphomeError, Lambda
|
||||||
from esphome.cpp_generator import LambdaExpression
|
from esphome.cpp_generator import LambdaExpression
|
||||||
from esphome.types import ConfigType, SafeExpType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
from .types import (
|
from .types import (
|
||||||
COLOR_MODES,
|
COLOR_MODES,
|
||||||
@@ -143,28 +141,6 @@ LIGHT_TURN_ON_ACTION_SCHEMA = automation.maybe_simple_id(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _as_lambda(
|
|
||||||
value: Any,
|
|
||||||
args: list[tuple[SafeExpType, str]],
|
|
||||||
output_type: SafeExpType,
|
|
||||||
) -> LambdaExpression:
|
|
||||||
"""Return a stateless lambda expression for a templatable value.
|
|
||||||
|
|
||||||
If value is already a lambda, process it normally. Otherwise wrap
|
|
||||||
the constant in a ``[](...) -> T { return <value>; }`` expression
|
|
||||||
so that LightControlAction can store every field as a plain
|
|
||||||
function pointer.
|
|
||||||
"""
|
|
||||||
if cg.is_template(value):
|
|
||||||
return await cg.process_lambda(value, args, return_type=output_type)
|
|
||||||
return LambdaExpression(
|
|
||||||
f"return {cg.safe_exp(value)};",
|
|
||||||
args,
|
|
||||||
capture="",
|
|
||||||
return_type=output_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_effect_index(config: ConfigType) -> int:
|
def _resolve_effect_index(config: ConfigType) -> int:
|
||||||
"""Resolve a static effect name to its 1-based index at codegen time.
|
"""Resolve a static effect name to its 1-based index at codegen time.
|
||||||
|
|
||||||
@@ -222,9 +198,8 @@ async def light_control_to_code(config, action_id, template_arg, args):
|
|||||||
)
|
)
|
||||||
for conf_key, setter, type_ in FIELDS:
|
for conf_key, setter, type_ in FIELDS:
|
||||||
if conf_key in config:
|
if conf_key in config:
|
||||||
cg.add(
|
template_ = await cg.templatable(config[conf_key], args, type_)
|
||||||
getattr(var, setter)(await _as_lambda(config[conf_key], args, type_))
|
cg.add(getattr(var, setter)(template_))
|
||||||
)
|
|
||||||
|
|
||||||
if CONF_EFFECT in config:
|
if CONF_EFFECT in config:
|
||||||
if isinstance(config[CONF_EFFECT], Lambda):
|
if isinstance(config[CONF_EFFECT], Lambda):
|
||||||
@@ -248,11 +223,10 @@ async def light_control_to_code(config, action_id, template_arg, args):
|
|||||||
cg.add(var.set_effect(wrapper))
|
cg.add(var.set_effect(wrapper))
|
||||||
else:
|
else:
|
||||||
# Static string — resolve effect name to index at codegen time
|
# Static string — resolve effect name to index at codegen time
|
||||||
cg.add(
|
template_ = await cg.templatable(
|
||||||
var.set_effect(
|
_resolve_effect_index(config), args, cg.uint32
|
||||||
await _as_lambda(_resolve_effect_index(config), args, cg.uint32)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
cg.add(var.set_effect(template_))
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -61,15 +61,13 @@ async def send_raw_to_code(config, action_id, template_arg, args):
|
|||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
repeats = await cg.templatable(config[CONF_REPEAT], args, int)
|
template_ = await cg.templatable(config[CONF_REPEAT], args, cg.int_)
|
||||||
inverted = await cg.templatable(config[CONF_INVERTED], args, bool)
|
cg.add(var.set_repeat(template_))
|
||||||
pulse_length = await cg.templatable(config[CONF_PULSE_LENGTH], args, int)
|
template_ = await cg.templatable(config[CONF_INVERTED], args, cg.int_)
|
||||||
code = config[CONF_CODE]
|
cg.add(var.set_inverted(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_PULSE_LENGTH], args, cg.int_)
|
||||||
cg.add(var.set_repeats(repeats))
|
cg.add(var.set_pulse_length(template_))
|
||||||
cg.add(var.set_inverted(inverted))
|
cg.add(var.set_code(config[CONF_CODE]))
|
||||||
cg.add(var.set_pulse_length(pulse_length))
|
|
||||||
cg.add(var.set_data(code))
|
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,7 @@ template<typename... Ts> class SendRawAction : public Action<Ts...> {
|
|||||||
TEMPLATABLE_VALUE(int, inverted);
|
TEMPLATABLE_VALUE(int, inverted);
|
||||||
TEMPLATABLE_VALUE(int, pulse_length);
|
TEMPLATABLE_VALUE(int, pulse_length);
|
||||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, code);
|
TEMPLATABLE_VALUE(std::vector<uint8_t>, code);
|
||||||
|
void set_code(std::initializer_list<uint8_t> data) { this->code_ = std::vector<uint8_t>(data); }
|
||||||
void set_repeats(const int &data) { repeat_ = data; }
|
|
||||||
void set_inverted(const int &data) { inverted_ = data; }
|
|
||||||
void set_pulse_length(const int &data) { pulse_length_ = data; }
|
|
||||||
void set_data(const std::vector<uint8_t> &data) { code_ = data; }
|
|
||||||
|
|
||||||
void play(const Ts &...x) {
|
void play(const Ts &...x) {
|
||||||
int repeats = this->repeat_.value(x...);
|
int repeats = this->repeat_.value(x...);
|
||||||
|
|||||||
@@ -412,7 +412,7 @@ void LvglComponent::flush_cb_(lv_display_t *disp_drv, const lv_area_t *area, uin
|
|||||||
lv_display_flush_ready(disp_drv);
|
lv_display_flush_ready(disp_drv);
|
||||||
}
|
}
|
||||||
|
|
||||||
IdleTrigger::IdleTrigger(LvglComponent *parent, TemplatableValue<uint32_t> timeout) : timeout_(std::move(timeout)) {
|
IdleTrigger::IdleTrigger(LvglComponent *parent, TemplatableFn<uint32_t> timeout) : timeout_(timeout) {
|
||||||
parent->add_on_idle_callback([this](uint32_t idle_time) {
|
parent->add_on_idle_callback([this](uint32_t idle_time) {
|
||||||
if (!this->is_idle_ && idle_time > this->timeout_.value()) {
|
if (!this->is_idle_ && idle_time > this->timeout_.value()) {
|
||||||
this->is_idle_ = true;
|
this->is_idle_ = true;
|
||||||
|
|||||||
@@ -284,10 +284,10 @@ class LvglComponent : public PollingComponent {
|
|||||||
|
|
||||||
class IdleTrigger : public Trigger<> {
|
class IdleTrigger : public Trigger<> {
|
||||||
public:
|
public:
|
||||||
explicit IdleTrigger(LvglComponent *parent, TemplatableValue<uint32_t> timeout);
|
explicit IdleTrigger(LvglComponent *parent, TemplatableFn<uint32_t> timeout);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TemplatableValue<uint32_t> timeout_;
|
TemplatableFn<uint32_t> timeout_;
|
||||||
bool is_idle_{};
|
bool is_idle_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,8 @@ MAX7219_ON_ACTION_SCHEMA = automation.maybe_simple_id(
|
|||||||
async def max7219digit_invert_to_code(config, action_id, template_arg, args):
|
async def max7219digit_invert_to_code(config, action_id, template_arg, args):
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
await cg.register_parented(var, config[CONF_ID])
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
cg.add(var.set_state(config[CONF_STATE]))
|
template_ = await cg.templatable(config[CONF_STATE], args, cg.bool_)
|
||||||
|
cg.add(var.set_state(template_))
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@@ -166,7 +167,8 @@ async def max7219digit_invert_to_code(config, action_id, template_arg, args):
|
|||||||
async def max7219digit_visible_to_code(config, action_id, template_arg, args):
|
async def max7219digit_visible_to_code(config, action_id, template_arg, args):
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
await cg.register_parented(var, config[CONF_ID])
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
cg.add(var.set_state(config[CONF_STATE]))
|
template_ = await cg.templatable(config[CONF_STATE], args, cg.bool_)
|
||||||
|
cg.add(var.set_state(template_))
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@@ -185,7 +187,8 @@ async def max7219digit_visible_to_code(config, action_id, template_arg, args):
|
|||||||
async def max7219digit_reverse_to_code(config, action_id, template_arg, args):
|
async def max7219digit_reverse_to_code(config, action_id, template_arg, args):
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
await cg.register_parented(var, config[CONF_ID])
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
cg.add(var.set_state(config[CONF_STATE]))
|
template_ = await cg.templatable(config[CONF_STATE], args, cg.bool_)
|
||||||
|
cg.add(var.set_state(template_))
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE, Lambda, coroutine_with_priority
|
from esphome.core import CORE, Lambda, coroutine_with_priority
|
||||||
from esphome.coroutine import CoroPriority
|
from esphome.coroutine import CoroPriority
|
||||||
|
from esphome.cpp_generator import LambdaExpression
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
@@ -131,6 +132,12 @@ def mdns_service(
|
|||||||
Returns:
|
Returns:
|
||||||
A StructInitializer representing a MDNSService struct
|
A StructInitializer representing a MDNSService struct
|
||||||
"""
|
"""
|
||||||
|
# Wrap port in a stateless lambda for TemplatableFn storage.
|
||||||
|
# Can't use cg.templatable() here because this is a sync function.
|
||||||
|
if not isinstance(port, LambdaExpression):
|
||||||
|
port = LambdaExpression(
|
||||||
|
f"return {cg.safe_exp(port)};", [], capture="", return_type=cg.uint16
|
||||||
|
)
|
||||||
return cg.StructInitializer(
|
return cg.StructInitializer(
|
||||||
MDNSService,
|
MDNSService,
|
||||||
("service_type", cg.RawExpression(f"MDNS_STR({cg.safe_exp(service)})")),
|
("service_type", cg.RawExpression(f"MDNS_STR({cg.safe_exp(service)})")),
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
|||||||
auto &service = services.emplace_next();
|
auto &service = services.emplace_next();
|
||||||
service.service_type = MDNS_STR(SERVICE_ESPHOMELIB);
|
service.service_type = MDNS_STR(SERVICE_ESPHOMELIB);
|
||||||
service.proto = MDNS_STR(SERVICE_TCP);
|
service.proto = MDNS_STR(SERVICE_TCP);
|
||||||
service.port = api::global_api_server->get_port();
|
service.port = []() -> uint16_t { return api::global_api_server->get_port(); };
|
||||||
|
|
||||||
const auto &friendly_name = App.get_friendly_name();
|
const auto &friendly_name = App.get_friendly_name();
|
||||||
bool friendly_name_empty = friendly_name.empty();
|
bool friendly_name_empty = friendly_name.empty();
|
||||||
@@ -151,7 +151,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
|||||||
auto &prom_service = services.emplace_next();
|
auto &prom_service = services.emplace_next();
|
||||||
prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS);
|
prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS);
|
||||||
prom_service.proto = MDNS_STR(SERVICE_TCP);
|
prom_service.proto = MDNS_STR(SERVICE_TCP);
|
||||||
prom_service.port = USE_WEBSERVER_PORT;
|
prom_service.port = []() -> uint16_t { return USE_WEBSERVER_PORT; };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SENDSPIN
|
#ifdef USE_SENDSPIN
|
||||||
@@ -162,7 +162,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
|||||||
auto &sendspin_service = services.emplace_next();
|
auto &sendspin_service = services.emplace_next();
|
||||||
sendspin_service.service_type = MDNS_STR(SERVICE_SENDSPIN);
|
sendspin_service.service_type = MDNS_STR(SERVICE_SENDSPIN);
|
||||||
sendspin_service.proto = MDNS_STR(SERVICE_TCP);
|
sendspin_service.proto = MDNS_STR(SERVICE_TCP);
|
||||||
sendspin_service.port = USE_SENDSPIN_PORT;
|
sendspin_service.port = []() -> uint16_t { return USE_SENDSPIN_PORT; };
|
||||||
sendspin_service.txt_records = {{MDNS_STR(TXT_SENDSPIN_PATH), MDNS_STR(VALUE_SENDSPIN_PATH)}};
|
sendspin_service.txt_records = {{MDNS_STR(TXT_SENDSPIN_PATH), MDNS_STR(VALUE_SENDSPIN_PATH)}};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
|||||||
auto &web_service = services.emplace_next();
|
auto &web_service = services.emplace_next();
|
||||||
web_service.service_type = MDNS_STR(SERVICE_HTTP);
|
web_service.service_type = MDNS_STR(SERVICE_HTTP);
|
||||||
web_service.proto = MDNS_STR(SERVICE_TCP);
|
web_service.proto = MDNS_STR(SERVICE_TCP);
|
||||||
web_service.port = USE_WEBSERVER_PORT;
|
web_service.port = []() -> uint16_t { return USE_WEBSERVER_PORT; };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_SENDSPIN) && !defined(USE_WEBSERVER) && \
|
#if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_SENDSPIN) && !defined(USE_WEBSERVER) && \
|
||||||
@@ -185,7 +185,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
|||||||
auto &fallback_service = services.emplace_next();
|
auto &fallback_service = services.emplace_next();
|
||||||
fallback_service.service_type = MDNS_STR(SERVICE_HTTP);
|
fallback_service.service_type = MDNS_STR(SERVICE_HTTP);
|
||||||
fallback_service.proto = MDNS_STR(SERVICE_TCP);
|
fallback_service.proto = MDNS_STR(SERVICE_TCP);
|
||||||
fallback_service.port = USE_WEBSERVER_PORT;
|
fallback_service.port = []() -> uint16_t { return USE_WEBSERVER_PORT; };
|
||||||
fallback_service.txt_records = {{MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)}};
|
fallback_service.txt_records = {{MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)}};
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ void MDNSComponent::dump_config() {
|
|||||||
ESP_LOGV(TAG, " Services:");
|
ESP_LOGV(TAG, " Services:");
|
||||||
for (const auto &service : this->services_) {
|
for (const auto &service : this->services_) {
|
||||||
ESP_LOGV(TAG, " - %s, %s, %d", MDNS_STR_ARG(service.service_type), MDNS_STR_ARG(service.proto),
|
ESP_LOGV(TAG, " - %s, %s, %d", MDNS_STR_ARG(service.service_type), MDNS_STR_ARG(service.proto),
|
||||||
const_cast<TemplatableValue<uint16_t> &>(service.port).value());
|
service.port.value());
|
||||||
for (const auto &record : service.txt_records) {
|
for (const auto &record : service.txt_records) {
|
||||||
ESP_LOGV(TAG, " TXT: %s = %s", MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
ESP_LOGV(TAG, " TXT: %s = %s", MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ struct MDNSService {
|
|||||||
// second label indicating protocol _including_ underscore character prefix
|
// second label indicating protocol _including_ underscore character prefix
|
||||||
// as defined in RFC6763 Section 7, like "_tcp" or "_udp"
|
// as defined in RFC6763 Section 7, like "_tcp" or "_udp"
|
||||||
const MDNSString *proto;
|
const MDNSString *proto;
|
||||||
TemplatableValue<uint16_t> port;
|
TemplatableFn<uint16_t> port;
|
||||||
FixedVector<MDNSTXTRecord> txt_records;
|
FixedVector<MDNSTXTRecord> txt_records;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ static void register_esp32(MDNSComponent *comp, StaticVector<MDNSService, MDNS_S
|
|||||||
txt_records.get()[i].key = MDNS_STR_ARG(record.key);
|
txt_records.get()[i].key = MDNS_STR_ARG(record.key);
|
||||||
txt_records.get()[i].value = MDNS_STR_ARG(record.value);
|
txt_records.get()[i].value = MDNS_STR_ARG(record.value);
|
||||||
}
|
}
|
||||||
uint16_t port = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
uint16_t port = service.port.value();
|
||||||
err = mdns_service_add(nullptr, MDNS_STR_ARG(service.service_type), MDNS_STR_ARG(service.proto), port,
|
err = mdns_service_add(nullptr, MDNS_STR_ARG(service.service_type), MDNS_STR_ARG(service.proto), port,
|
||||||
txt_records.get(), service.txt_records.size());
|
txt_records.get(), service.txt_records.size());
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ static void register_esp8266(MDNSComponent *, StaticVector<MDNSService, MDNS_SER
|
|||||||
while (progmem_read_byte((const uint8_t *) service_type) == '_') {
|
while (progmem_read_byte((const uint8_t *) service_type) == '_') {
|
||||||
service_type++;
|
service_type++;
|
||||||
}
|
}
|
||||||
uint16_t port = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
uint16_t port = service.port.value();
|
||||||
MDNS.addService(FPSTR(service_type), FPSTR(proto), port);
|
MDNS.addService(FPSTR(service_type), FPSTR(proto), port);
|
||||||
for (const auto &record : service.txt_records) {
|
for (const auto &record : service.txt_records) {
|
||||||
MDNS.addServiceTxt(FPSTR(service_type), FPSTR(proto), FPSTR(MDNS_STR_ARG(record.key)),
|
MDNS.addServiceTxt(FPSTR(service_type), FPSTR(proto), FPSTR(MDNS_STR_ARG(record.key)),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ static void register_libretiny(MDNSComponent *, StaticVector<MDNSService, MDNS_S
|
|||||||
while (*service_type == '_') {
|
while (*service_type == '_') {
|
||||||
service_type++;
|
service_type++;
|
||||||
}
|
}
|
||||||
uint16_t port_ = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
uint16_t port_ = service.port.value();
|
||||||
MDNS.addService(service_type, proto, port_);
|
MDNS.addService(service_type, proto, port_);
|
||||||
for (const auto &record : service.txt_records) {
|
for (const auto &record : service.txt_records) {
|
||||||
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ static void register_rp2040(MDNSComponent *, StaticVector<MDNSService, MDNS_SERV
|
|||||||
while (*service_type == '_') {
|
while (*service_type == '_') {
|
||||||
service_type++;
|
service_type++;
|
||||||
}
|
}
|
||||||
uint16_t port = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
uint16_t port = service.port.value();
|
||||||
MDNS.addService(service_type, proto, port);
|
MDNS.addService(service_type, proto, port);
|
||||||
for (const auto &record : service.txt_records) {
|
for (const auto &record : service.txt_records) {
|
||||||
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
||||||
|
|||||||
@@ -448,7 +448,11 @@ async def number_to_to_code(config, action_id, template_arg, args):
|
|||||||
template_ = await cg.templatable(cycle, args, bool)
|
template_ = await cg.templatable(cycle, args, bool)
|
||||||
cg.add(var.set_cycle(template_))
|
cg.add(var.set_cycle(template_))
|
||||||
if (mode := config.get(CONF_MODE)) is not None:
|
if (mode := config.get(CONF_MODE)) is not None:
|
||||||
cg.add(var.set_operation(NUMBER_OPERATION_OPTIONS[mode]))
|
template_ = await cg.templatable(
|
||||||
|
NUMBER_OPERATION_OPTIONS[mode], args, NumberOperation
|
||||||
|
)
|
||||||
|
cg.add(var.set_operation(template_))
|
||||||
if (cycle := config.get(CONF_CYCLE)) is not None:
|
if (cycle := config.get(CONF_CYCLE)) is not None:
|
||||||
cg.add(var.set_cycle(cycle))
|
template_ = await cg.templatable(cycle, args, cg.bool_)
|
||||||
|
cg.add(var.set_cycle(template_))
|
||||||
return var
|
return var
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ class ValueRangeTrigger : public Trigger<float>, public Component {
|
|||||||
Number *parent_;
|
Number *parent_;
|
||||||
ESPPreferenceObject rtc_;
|
ESPPreferenceObject rtc_;
|
||||||
bool previous_in_range_{false};
|
bool previous_in_range_{false};
|
||||||
TemplatableValue<float, float> min_{NAN};
|
TemplatableFn<float, float> min_{[](float) -> float { return NAN; }};
|
||||||
TemplatableValue<float, float> max_{NAN};
|
TemplatableFn<float, float> max_{[](float) -> float { return NAN; }};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class NumberInRangeCondition : public Condition<Ts...> {
|
template<typename... Ts> class NumberInRangeCondition : public Condition<Ts...> {
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ void OpenThreadSrpComponent::setup() {
|
|||||||
memcpy(string, host_name.c_str(), host_name_len);
|
memcpy(string, host_name.c_str(), host_name_len);
|
||||||
|
|
||||||
// Set port
|
// Set port
|
||||||
entry->mService.mPort = const_cast<TemplatableValue<uint16_t> &>(service.port).value();
|
entry->mService.mPort = service.port.value();
|
||||||
|
|
||||||
otDnsTxtEntry *txt_entries =
|
otDnsTxtEntry *txt_entries =
|
||||||
reinterpret_cast<otDnsTxtEntry *>(this->pool_alloc_(sizeof(otDnsTxtEntry) * service.txt_records.size()));
|
reinterpret_cast<otDnsTxtEntry *>(this->pool_alloc_(sizeof(otDnsTxtEntry) * service.txt_records.size()));
|
||||||
|
|||||||
@@ -2123,7 +2123,8 @@ async def abbwelcome_action(var, config, args):
|
|||||||
await cg.templatable(config[CONF_MESSAGE_TYPE], args, cg.uint8)
|
await cg.templatable(config[CONF_MESSAGE_TYPE], args, cg.uint8)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
cg.add(var.set_auto_message_id(CONF_MESSAGE_ID not in config))
|
template_ = await cg.templatable(CONF_MESSAGE_ID not in config, args, cg.bool_)
|
||||||
|
cg.add(var.set_auto_message_id(template_))
|
||||||
if CONF_MESSAGE_ID in config:
|
if CONF_MESSAGE_ID in config:
|
||||||
cg.add(
|
cg.add(
|
||||||
var.set_message_id(
|
var.set_message_id(
|
||||||
@@ -2231,3 +2232,9 @@ async def Toto_action(var, config, args):
|
|||||||
cg.add(var.set_rc_code_2(template_))
|
cg.add(var.set_rc_code_2(template_))
|
||||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
||||||
cg.add(var.set_command(template_))
|
cg.add(var.set_command(template_))
|
||||||
|
# Set toto-specific defaults (only if user didn't configure repeat)
|
||||||
|
if CONF_REPEAT not in config:
|
||||||
|
template_ = await cg.templatable(3, args, cg.uint32)
|
||||||
|
cg.add(var.set_send_times(template_))
|
||||||
|
template_ = await cg.templatable(36000, args, cg.uint32)
|
||||||
|
cg.add(var.set_send_wait(template_))
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ template<typename... Ts> class TotoAction : public RemoteTransmitterActionBase<T
|
|||||||
data.rc_code_1 = this->rc_code_1_.value(x...);
|
data.rc_code_1 = this->rc_code_1_.value(x...);
|
||||||
data.rc_code_2 = this->rc_code_2_.value(x...);
|
data.rc_code_2 = this->rc_code_2_.value(x...);
|
||||||
data.command = this->command_.value(x...);
|
data.command = this->command_.value(x...);
|
||||||
this->set_send_times(this->send_times_.value_or(x..., 3));
|
|
||||||
this->set_send_wait(this->send_wait_.value_or(x..., 36000));
|
|
||||||
TotoProtocol().encode(dst, data);
|
TotoProtocol().encode(dst, data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ template<class... As, typename... Ts> class ScriptExecuteAction<Script<As...>, T
|
|||||||
public:
|
public:
|
||||||
ScriptExecuteAction(Script<As...> *script) : script_(script) {}
|
ScriptExecuteAction(Script<As...> *script) : script_(script) {}
|
||||||
|
|
||||||
using Args = std::tuple<TemplatableValue<As, Ts...>...>;
|
using Args = std::tuple<TemplatableFn<As, Ts...>...>;
|
||||||
|
|
||||||
template<typename... F> void set_args(F... x) { args_ = Args{x...}; }
|
template<typename... F> void set_args(F... x) { args_ = Args{x...}; }
|
||||||
|
|
||||||
|
|||||||
@@ -282,7 +282,11 @@ async def select_operation_to_code(config, action_id, template_arg, args):
|
|||||||
template_ = await cg.templatable(cycle, args, bool)
|
template_ = await cg.templatable(cycle, args, bool)
|
||||||
cg.add(var.set_cycle(template_))
|
cg.add(var.set_cycle(template_))
|
||||||
if (mode := config.get(CONF_MODE)) is not None:
|
if (mode := config.get(CONF_MODE)) is not None:
|
||||||
cg.add(var.set_operation(SELECT_OPERATION_OPTIONS[mode]))
|
template_ = await cg.templatable(
|
||||||
|
SELECT_OPERATION_OPTIONS[mode], args, SelectOperation
|
||||||
|
)
|
||||||
|
cg.add(var.set_operation(template_))
|
||||||
if (cycle := config.get(CONF_CYCLE)) is not None:
|
if (cycle := config.get(CONF_CYCLE)) is not None:
|
||||||
cg.add(var.set_cycle(cycle))
|
template_ = await cg.templatable(cycle, args, cg.bool_)
|
||||||
|
cg.add(var.set_cycle(template_))
|
||||||
return var
|
return var
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ class ValueRangeTrigger : public Trigger<float>, public Component {
|
|||||||
Sensor *parent_;
|
Sensor *parent_;
|
||||||
ESPPreferenceObject rtc_;
|
ESPPreferenceObject rtc_;
|
||||||
bool previous_in_range_{false};
|
bool previous_in_range_{false};
|
||||||
TemplatableValue<float, float> min_{NAN};
|
TemplatableFn<float, float> min_{[](float) -> float { return NAN; }};
|
||||||
TemplatableValue<float, float> max_{NAN};
|
TemplatableFn<float, float> max_{[](float) -> float { return NAN; }};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class SensorInRangeCondition : public Condition<Ts...> {
|
template<typename... Ts> class SensorInRangeCondition : public Condition<Ts...> {
|
||||||
|
|||||||
@@ -213,17 +213,17 @@ optional<float> LambdaFilter::new_value(float value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OffsetFilter
|
// OffsetFilter
|
||||||
OffsetFilter::OffsetFilter(TemplatableValue<float> offset) : offset_(std::move(offset)) {}
|
OffsetFilter::OffsetFilter(TemplatableFn<float> offset) : offset_(offset) {}
|
||||||
|
|
||||||
optional<float> OffsetFilter::new_value(float value) { return value + this->offset_.value(); }
|
optional<float> OffsetFilter::new_value(float value) { return value + this->offset_.value(); }
|
||||||
|
|
||||||
// MultiplyFilter
|
// MultiplyFilter
|
||||||
MultiplyFilter::MultiplyFilter(TemplatableValue<float> multiplier) : multiplier_(std::move(multiplier)) {}
|
MultiplyFilter::MultiplyFilter(TemplatableFn<float> multiplier) : multiplier_(multiplier) {}
|
||||||
|
|
||||||
optional<float> MultiplyFilter::new_value(float value) { return value * this->multiplier_.value(); }
|
optional<float> MultiplyFilter::new_value(float value) { return value * this->multiplier_.value(); }
|
||||||
|
|
||||||
// ValueListFilter helper (non-template, shared by all ValueListFilter<N> instantiations)
|
// ValueListFilter helper (non-template, shared by all ValueListFilter<N> instantiations)
|
||||||
bool value_list_matches_any(Sensor *parent, float sensor_value, const TemplatableValue<float> *values, size_t count) {
|
bool value_list_matches_any(Sensor *parent, float sensor_value, const TemplatableFn<float> *values, size_t count) {
|
||||||
int8_t accuracy = parent->get_accuracy_decimals();
|
int8_t accuracy = parent->get_accuracy_decimals();
|
||||||
float accuracy_mult = pow10_int(accuracy);
|
float accuracy_mult = pow10_int(accuracy);
|
||||||
float rounded_sensor = roundf(accuracy_mult * sensor_value);
|
float rounded_sensor = roundf(accuracy_mult * sensor_value);
|
||||||
@@ -258,7 +258,7 @@ optional<float> ThrottleFilter::new_value(float value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ThrottleWithPriorityFilter helper (non-template, keeps App access in .cpp)
|
// ThrottleWithPriorityFilter helper (non-template, keeps App access in .cpp)
|
||||||
optional<float> throttle_with_priority_new_value(Sensor *parent, float value, const TemplatableValue<float> *values,
|
optional<float> throttle_with_priority_new_value(Sensor *parent, float value, const TemplatableFn<float> *values,
|
||||||
size_t count, uint32_t &last_input, uint32_t min_time_between_inputs) {
|
size_t count, uint32_t &last_input, uint32_t min_time_between_inputs) {
|
||||||
const uint32_t now = App.get_loop_component_start_time();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (last_input == 0 || now - last_input >= min_time_between_inputs ||
|
if (last_input == 0 || now - last_input >= min_time_between_inputs ||
|
||||||
|
|||||||
@@ -311,26 +311,26 @@ class StatelessLambdaFilter : public Filter {
|
|||||||
/// 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:
|
||||||
explicit OffsetFilter(TemplatableValue<float> offset);
|
explicit OffsetFilter(TemplatableFn<float> offset);
|
||||||
|
|
||||||
optional<float> new_value(float value) override;
|
optional<float> new_value(float value) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TemplatableValue<float> offset_;
|
TemplatableFn<float> offset_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A simple filter that multiplies to each value it receives by `multiplier`.
|
/// A simple filter that multiplies to each value it receives by `multiplier`.
|
||||||
class MultiplyFilter : public Filter {
|
class MultiplyFilter : public Filter {
|
||||||
public:
|
public:
|
||||||
explicit MultiplyFilter(TemplatableValue<float> multiplier);
|
explicit MultiplyFilter(TemplatableFn<float> multiplier);
|
||||||
optional<float> new_value(float value) override;
|
optional<float> new_value(float value) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TemplatableValue<float> multiplier_;
|
TemplatableFn<float> multiplier_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Non-template helper for value matching (implementation in filter.cpp)
|
/// Non-template helper for value matching (implementation in filter.cpp)
|
||||||
bool value_list_matches_any(Sensor *parent, float sensor_value, const TemplatableValue<float> *values, size_t count);
|
bool value_list_matches_any(Sensor *parent, float sensor_value, const TemplatableFn<float> *values, size_t count);
|
||||||
|
|
||||||
/** Base class for filters that compare sensor values against a fixed list of configured values.
|
/** Base class for filters that compare sensor values against a fixed list of configured values.
|
||||||
*
|
*
|
||||||
@@ -342,7 +342,7 @@ bool value_list_matches_any(Sensor *parent, float sensor_value, const Templatabl
|
|||||||
*/
|
*/
|
||||||
template<size_t N> class ValueListFilter : public Filter {
|
template<size_t N> class ValueListFilter : public Filter {
|
||||||
protected:
|
protected:
|
||||||
explicit ValueListFilter(std::initializer_list<TemplatableValue<float>> values) {
|
explicit ValueListFilter(std::initializer_list<TemplatableFn<float>> values) {
|
||||||
init_array_from(this->values_, values);
|
init_array_from(this->values_, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,13 +351,13 @@ template<size_t N> class ValueListFilter : public Filter {
|
|||||||
return value_list_matches_any(this->parent_, sensor_value, this->values_.data(), N);
|
return value_list_matches_any(this->parent_, sensor_value, this->values_.data(), N);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<TemplatableValue<float>, N> values_{};
|
std::array<TemplatableFn<float>, N> values_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A simple filter that only forwards the filter chain if it doesn't receive `value_to_filter_out`.
|
/// A simple filter that only forwards the filter chain if it doesn't receive `value_to_filter_out`.
|
||||||
template<size_t N> class FilterOutValueFilter : public ValueListFilter<N> {
|
template<size_t N> class FilterOutValueFilter : public ValueListFilter<N> {
|
||||||
public:
|
public:
|
||||||
explicit FilterOutValueFilter(std::initializer_list<TemplatableValue<float>> values_to_filter_out)
|
explicit FilterOutValueFilter(std::initializer_list<TemplatableFn<float>> values_to_filter_out)
|
||||||
: ValueListFilter<N>(values_to_filter_out) {}
|
: ValueListFilter<N>(values_to_filter_out) {}
|
||||||
|
|
||||||
optional<float> new_value(float value) override {
|
optional<float> new_value(float value) override {
|
||||||
@@ -379,14 +379,14 @@ class ThrottleFilter : public Filter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Non-template helper for ThrottleWithPriorityFilter (implementation in filter.cpp)
|
/// Non-template helper for ThrottleWithPriorityFilter (implementation in filter.cpp)
|
||||||
optional<float> throttle_with_priority_new_value(Sensor *parent, float value, const TemplatableValue<float> *values,
|
optional<float> throttle_with_priority_new_value(Sensor *parent, float value, const TemplatableFn<float> *values,
|
||||||
size_t count, uint32_t &last_input, uint32_t min_time_between_inputs);
|
size_t count, uint32_t &last_input, uint32_t min_time_between_inputs);
|
||||||
|
|
||||||
/// Same as 'throttle' but will immediately publish values contained in `value_to_prioritize`.
|
/// Same as 'throttle' but will immediately publish values contained in `value_to_prioritize`.
|
||||||
template<size_t N> class ThrottleWithPriorityFilter : public ValueListFilter<N> {
|
template<size_t N> class ThrottleWithPriorityFilter : public ValueListFilter<N> {
|
||||||
public:
|
public:
|
||||||
explicit ThrottleWithPriorityFilter(uint32_t min_time_between_inputs,
|
explicit ThrottleWithPriorityFilter(uint32_t min_time_between_inputs,
|
||||||
std::initializer_list<TemplatableValue<float>> prioritized_values)
|
std::initializer_list<TemplatableFn<float>> prioritized_values)
|
||||||
: ValueListFilter<N>(prioritized_values), min_time_between_inputs_(min_time_between_inputs) {}
|
: ValueListFilter<N>(prioritized_values), min_time_between_inputs_(min_time_between_inputs) {}
|
||||||
|
|
||||||
optional<float> new_value(float value) override {
|
optional<float> new_value(float value) override {
|
||||||
@@ -430,15 +430,15 @@ class TimeoutFilterLast : public TimeoutFilterBase {
|
|||||||
// Timeout filter with configured value - evaluates TemplatableValue after timeout
|
// Timeout filter with configured value - evaluates TemplatableValue after timeout
|
||||||
class TimeoutFilterConfigured : public TimeoutFilterBase {
|
class TimeoutFilterConfigured : public TimeoutFilterBase {
|
||||||
public:
|
public:
|
||||||
explicit TimeoutFilterConfigured(uint32_t time_period, const TemplatableValue<float> &new_value)
|
explicit TimeoutFilterConfigured(uint32_t time_period, const TemplatableFn<float> &new_value)
|
||||||
: TimeoutFilterBase(time_period), value_(new_value) {}
|
: TimeoutFilterBase(time_period), value_(new_value) {}
|
||||||
|
|
||||||
optional<float> new_value(float value) override;
|
optional<float> new_value(float value) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
float get_output_value() override { return this->value_.value(); }
|
float get_output_value() override { return this->value_.value(); }
|
||||||
TemplatableValue<float> value_; // 16 bytes (configured output value, can be lambda)
|
TemplatableFn<float> value_; // 4 bytes (configured output value, can be lambda)
|
||||||
// Total: 8 (base) + 16 = 24 bytes + vtable ptr + Component overhead
|
// Total: 8 (base) + 4 = 12 bytes + vtable ptr + Component overhead
|
||||||
};
|
};
|
||||||
|
|
||||||
class DebounceFilter : public Filter, public Component {
|
class DebounceFilter : public Filter, public Component {
|
||||||
|
|||||||
@@ -516,7 +516,8 @@ async def play_on_device_media_media_action(config, action_id, template_arg, arg
|
|||||||
announcement = await cg.templatable(config[CONF_ANNOUNCEMENT], args, cg.bool_)
|
announcement = await cg.templatable(config[CONF_ANNOUNCEMENT], args, cg.bool_)
|
||||||
enqueue = await cg.templatable(config[CONF_ENQUEUE], args, cg.bool_)
|
enqueue = await cg.templatable(config[CONF_ENQUEUE], args, cg.bool_)
|
||||||
|
|
||||||
cg.add(var.set_audio_file(media_file))
|
template_ = await cg.templatable(media_file, args, audio.AudioFile.operator("ptr"))
|
||||||
|
cg.add(var.set_audio_file(template_))
|
||||||
cg.add(var.set_announcement(announcement))
|
cg.add(var.set_announcement(announcement))
|
||||||
cg.add(var.set_enqueue(enqueue))
|
cg.add(var.set_enqueue(enqueue))
|
||||||
return var
|
return var
|
||||||
|
|||||||
@@ -312,7 +312,8 @@ async def set_playlist_delay_action_to_code(
|
|||||||
parent = await cg.get_variable(config[CONF_ID])
|
parent = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||||
|
|
||||||
cg.add(var.set_pipeline(config[CONF_PIPELINE]))
|
template_ = await cg.templatable(config[CONF_PIPELINE], args, cg.uint8)
|
||||||
|
cg.add(var.set_pipeline(template_))
|
||||||
|
|
||||||
template_ = await cg.templatable(config[CONF_DELAY], args, cg.uint32)
|
template_ = await cg.templatable(config[CONF_DELAY], args, cg.uint32)
|
||||||
cg.add(var.set_delay(template_))
|
cg.add(var.set_delay(template_))
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ async def sprinkler_set_multiplier_to_code(config, action_id, template_arg, args
|
|||||||
async def sprinkler_set_queued_valve_to_code(config, action_id, template_arg, args):
|
async def sprinkler_set_queued_valve_to_code(config, action_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
template_ = await cg.templatable(config[CONF_VALVE_NUMBER], args, cg.uint8)
|
template_ = await cg.templatable(config[CONF_VALVE_NUMBER], args, cg.size_t)
|
||||||
cg.add(var.set_valve_number(template_))
|
cg.add(var.set_valve_number(template_))
|
||||||
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
||||||
cg.add(var.set_valve_run_duration(template_))
|
cg.add(var.set_valve_run_duration(template_))
|
||||||
@@ -487,7 +487,7 @@ async def sprinkler_set_valve_run_duration_to_code(
|
|||||||
):
|
):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
template_ = await cg.templatable(config[CONF_VALVE_NUMBER], args, cg.uint8)
|
template_ = await cg.templatable(config[CONF_VALVE_NUMBER], args, cg.size_t)
|
||||||
cg.add(var.set_valve_number(template_))
|
cg.add(var.set_valve_number(template_))
|
||||||
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
||||||
cg.add(var.set_valve_run_duration(template_))
|
cg.add(var.set_valve_run_duration(template_))
|
||||||
@@ -525,7 +525,7 @@ async def sprinkler_start_full_cycle_to_code(config, action_id, template_arg, ar
|
|||||||
async def sprinkler_start_single_valve_to_code(config, action_id, template_arg, args):
|
async def sprinkler_start_single_valve_to_code(config, action_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
template_ = await cg.templatable(config[CONF_VALVE_NUMBER], args, cg.uint8)
|
template_ = await cg.templatable(config[CONF_VALVE_NUMBER], args, cg.size_t)
|
||||||
cg.add(var.set_valve_to_start(template_))
|
cg.add(var.set_valve_to_start(template_))
|
||||||
if CONF_RUN_DURATION in config:
|
if CONF_RUN_DURATION in config:
|
||||||
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
template_ = await cg.templatable(config[CONF_RUN_DURATION], args, cg.uint32)
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ template<typename... Ts> class StartSingleValveAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit StartSingleValveAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
explicit StartSingleValveAction(Sprinkler *a_sprinkler) : sprinkler_(a_sprinkler) {}
|
||||||
|
|
||||||
TEMPLATABLE_VALUE(size_t, valve_to_start)
|
// TemplatableValue (not TemplatableFn) — also set from C++ with raw values in sprinkler.cpp
|
||||||
|
template<typename V> void set_valve_to_start(V valve_to_start) { this->valve_to_start_ = valve_to_start; }
|
||||||
TEMPLATABLE_VALUE(uint32_t, valve_run_duration)
|
TEMPLATABLE_VALUE(uint32_t, valve_run_duration)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(const Ts &...x) override {
|
||||||
@@ -118,6 +119,7 @@ template<typename... Ts> class StartSingleValveAction : public Action<Ts...> {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
Sprinkler *sprinkler_;
|
Sprinkler *sprinkler_;
|
||||||
|
TemplatableValue<size_t, Ts...> valve_to_start_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class ShutdownAction : public Action<Ts...> {
|
template<typename... Ts> class ShutdownAction : public Action<Ts...> {
|
||||||
|
|||||||
@@ -34,70 +34,236 @@ template<int... S> struct gens<0, S...> { using type = seq<S...>; };
|
|||||||
#endif
|
#endif
|
||||||
// NOLINTEND(readability-identifier-naming)
|
// NOLINTEND(readability-identifier-naming)
|
||||||
|
|
||||||
|
/// Function-pointer-only templatable storage (4 bytes on 32-bit).
|
||||||
|
/// Used by the TEMPLATABLE_VALUE macro for codegen-managed fields.
|
||||||
|
/// Codegen wraps constants in stateless lambdas so only a function pointer is needed.
|
||||||
|
template<typename T, typename... X> class TemplatableFn {
|
||||||
|
public:
|
||||||
|
TemplatableFn() = default;
|
||||||
|
TemplatableFn(std::nullptr_t) = delete;
|
||||||
|
|
||||||
|
// Exact return type match — direct function pointer storage
|
||||||
|
template<typename F> TemplatableFn(F f) requires std::convertible_to<F, T (*)(X...)> : f_(f) {}
|
||||||
|
|
||||||
|
// Convertible return type (e.g., int -> uint8_t) — casting trampoline.
|
||||||
|
// Stateless lambdas are default-constructible in C++20, so F{} recreates the lambda inside
|
||||||
|
// the trampoline without capturing. This compiles to the same code as a direct call + cast.
|
||||||
|
// Deprecated: codegen should use the correct output type to avoid the trampoline.
|
||||||
|
template<typename F>
|
||||||
|
[[deprecated("Lambda return type does not match TemplatableFn<T> — use the correct type in "
|
||||||
|
"codegen")]] TemplatableFn(F) requires(!std::convertible_to<F, T (*)(X...)>) &&
|
||||||
|
std::invocable<F, X...> &&std::convertible_to<std::invoke_result_t<F, X...>, T> &&std::is_empty_v<F>
|
||||||
|
&&std::default_initializable<F> : f_([](X... x) -> T { return static_cast<T>(F{}(x...)); }) {}
|
||||||
|
|
||||||
|
// Reject any callable that didn't match the above (stateful lambdas or inconvertible return types)
|
||||||
|
template<typename F>
|
||||||
|
TemplatableFn(F) requires std::invocable<F, X...> &&
|
||||||
|
(!std::convertible_to<F, T (*)(X...)>) &&(!std::is_empty_v<F> ||
|
||||||
|
!std::convertible_to<std::invoke_result_t<F, X...>, T> ||
|
||||||
|
!std::default_initializable<F>) = delete;
|
||||||
|
|
||||||
|
bool has_value() const { return this->f_ != nullptr; }
|
||||||
|
|
||||||
|
T value(X... x) const { return this->f_ ? this->f_(x...) : T{}; }
|
||||||
|
|
||||||
|
optional<T> optional_value(X... x) const {
|
||||||
|
if (!this->f_)
|
||||||
|
return {};
|
||||||
|
return this->f_(x...);
|
||||||
|
}
|
||||||
|
|
||||||
|
T value_or(X... x, T default_value) const { return this->f_ ? this->f_(x...) : default_value; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
T (*f_)(X...){nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward declaration for TemplatableValue (string specialization needs it)
|
||||||
|
template<typename T, typename... X> class TemplatableValue;
|
||||||
|
|
||||||
|
/// Selects TemplatableFn (4 bytes) for trivially copyable types, TemplatableValue (8 bytes) otherwise.
|
||||||
|
/// Non-trivial types (std::string, std::vector<uint8_t>, etc.) need TemplatableValue for raw value
|
||||||
|
/// storage, PROGMEM/FlashStringHelper support (strings), and proper copy/move/destruction.
|
||||||
|
template<typename T, typename... X>
|
||||||
|
using TemplatableStorage =
|
||||||
|
std::conditional_t<std::is_trivially_copyable_v<T>, TemplatableFn<T, X...>, TemplatableValue<T, X...>>;
|
||||||
|
|
||||||
#define TEMPLATABLE_VALUE_(type, name) \
|
#define TEMPLATABLE_VALUE_(type, name) \
|
||||||
protected: \
|
protected: \
|
||||||
TemplatableValue<type, Ts...> name##_{}; \
|
TemplatableStorage<type, Ts...> name##_{}; \
|
||||||
\
|
\
|
||||||
public: \
|
public: \
|
||||||
template<typename V> void set_##name(V name) { this->name##_ = name; }
|
template<typename V> void set_##name(V name) { this->name##_ = name; }
|
||||||
|
|
||||||
#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
|
#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
|
||||||
|
|
||||||
|
/// Primary TemplatableValue: stores either a constant value or a function pointer.
|
||||||
|
/// No std::function, no string-specific paths. 8 bytes on 32-bit.
|
||||||
|
/// Accepts raw constants for backward compatibility with direct C++ usage.
|
||||||
template<typename T, typename... X> class TemplatableValue {
|
template<typename T, typename... X> class TemplatableValue {
|
||||||
// For std::string, store pointer to heap-allocated string to keep union pointer-sized.
|
public:
|
||||||
// For other types, store value inline.
|
TemplatableValue() = default;
|
||||||
static constexpr bool USE_HEAP_STORAGE = std::same_as<T, std::string>;
|
TemplatableValue(std::nullptr_t) = delete;
|
||||||
|
|
||||||
|
// Accept raw constants
|
||||||
|
template<typename V> TemplatableValue(V value) requires(!std::invocable<V, X...>) : tag_(VALUE) {
|
||||||
|
new (&this->storage_.value_) T(static_cast<T>(std::move(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept stateless lambdas (convertible to function pointer)
|
||||||
|
template<typename F> TemplatableValue(F f) requires std::convertible_to<F, T (*)(X...)> : tag_(FN) {
|
||||||
|
this->storage_.f_ = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertible return type (e.g., int -> uint8_t) — casting trampoline
|
||||||
|
template<typename F>
|
||||||
|
[[deprecated("Lambda return type does not match TemplatableValue<T> — use the correct type in "
|
||||||
|
"codegen")]] TemplatableValue(F) requires(!std::convertible_to<F, T (*)(X...)>) &&
|
||||||
|
std::invocable<F, X...> &&std::convertible_to<std::invoke_result_t<F, X...>, T> &&std::is_empty_v<F>
|
||||||
|
&&std::default_initializable<F> : tag_(FN) {
|
||||||
|
this->storage_.f_ = [](X... x) -> T { return static_cast<T>(F{}(x...)); };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject any callable that didn't match the above
|
||||||
|
template<typename F>
|
||||||
|
TemplatableValue(F) requires std::invocable<F, X...> &&
|
||||||
|
(!std::convertible_to<F, T (*)(X...)>) &&(!std::is_empty_v<F> ||
|
||||||
|
!std::convertible_to<std::invoke_result_t<F, X...>, T> ||
|
||||||
|
!std::default_initializable<F>) = delete;
|
||||||
|
|
||||||
|
TemplatableValue(const TemplatableValue &other) : tag_(other.tag_) {
|
||||||
|
if (this->tag_ == VALUE) {
|
||||||
|
new (&this->storage_.value_) T(other.storage_.value_);
|
||||||
|
} else if (this->tag_ == FN) {
|
||||||
|
this->storage_.f_ = other.storage_.f_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplatableValue(TemplatableValue &&other) noexcept : tag_(other.tag_) {
|
||||||
|
if (this->tag_ == VALUE) {
|
||||||
|
new (&this->storage_.value_) T(std::move(other.storage_.value_));
|
||||||
|
other.destroy_();
|
||||||
|
} else if (this->tag_ == FN) {
|
||||||
|
this->storage_.f_ = other.storage_.f_;
|
||||||
|
}
|
||||||
|
other.tag_ = NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplatableValue &operator=(const TemplatableValue &other) {
|
||||||
|
if (this != &other) {
|
||||||
|
this->destroy_();
|
||||||
|
this->tag_ = other.tag_;
|
||||||
|
if (this->tag_ == VALUE) {
|
||||||
|
new (&this->storage_.value_) T(other.storage_.value_);
|
||||||
|
} else if (this->tag_ == FN) {
|
||||||
|
this->storage_.f_ = other.storage_.f_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplatableValue &operator=(TemplatableValue &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
this->destroy_();
|
||||||
|
this->tag_ = other.tag_;
|
||||||
|
if (this->tag_ == VALUE) {
|
||||||
|
new (&this->storage_.value_) T(std::move(other.storage_.value_));
|
||||||
|
other.destroy_();
|
||||||
|
} else if (this->tag_ == FN) {
|
||||||
|
this->storage_.f_ = other.storage_.f_;
|
||||||
|
}
|
||||||
|
other.tag_ = NONE;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~TemplatableValue() { this->destroy_(); }
|
||||||
|
|
||||||
|
bool has_value() const { return this->tag_ != NONE; }
|
||||||
|
|
||||||
|
T value(X... x) const {
|
||||||
|
if (this->tag_ == FN)
|
||||||
|
return this->storage_.f_(x...);
|
||||||
|
if (this->tag_ == VALUE)
|
||||||
|
return this->storage_.value_;
|
||||||
|
return T{};
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<T> optional_value(X... x) const {
|
||||||
|
if (this->tag_ == NONE)
|
||||||
|
return {};
|
||||||
|
return this->value(x...);
|
||||||
|
}
|
||||||
|
|
||||||
|
T value_or(X... x, T default_value) const {
|
||||||
|
if (this->tag_ == NONE)
|
||||||
|
return default_value;
|
||||||
|
return this->value(x...);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void destroy_() {
|
||||||
|
if constexpr (!std::is_trivially_destructible_v<T>) {
|
||||||
|
if (this->tag_ == VALUE)
|
||||||
|
this->storage_.value_.~T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Tag : uint8_t { NONE, VALUE, FN } tag_{NONE};
|
||||||
|
// Union with explicit ctor/dtor to support non-trivially-constructible/destructible T
|
||||||
|
// (e.g., std::vector<uint8_t>). Lifetime of value_ is managed externally via
|
||||||
|
// placement new and destroy_().
|
||||||
|
union Storage {
|
||||||
|
constexpr Storage() : f_(nullptr) {}
|
||||||
|
constexpr ~Storage() {}
|
||||||
|
T value_;
|
||||||
|
T (*f_)(X...);
|
||||||
|
} storage_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Specialization for std::string: supports VALUE, STATIC_STRING, FLASH_STRING,
|
||||||
|
/// stateless lambdas, and stateful lambdas (std::function).
|
||||||
|
template<typename... X> class TemplatableValue<std::string, X...> {
|
||||||
public:
|
public:
|
||||||
TemplatableValue() : type_(NONE) {}
|
TemplatableValue() : type_(NONE) {}
|
||||||
|
|
||||||
// For const char* when T is std::string: store pointer directly, no heap allocation
|
// For const char*: store pointer directly, no heap allocation.
|
||||||
// String remains in flash and is only converted to std::string when value() is called
|
// String remains in flash and is only converted to std::string when value() is called.
|
||||||
TemplatableValue(const char *str) requires std::same_as<T, std::string> : type_(STATIC_STRING) {
|
TemplatableValue(const char *str) : type_(STATIC_STRING) { this->static_str_ = str; }
|
||||||
this->static_str_ = str;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
// On ESP8266, __FlashStringHelper* is a distinct type from const char*.
|
// On ESP8266, __FlashStringHelper* is a distinct type from const char*.
|
||||||
// ESPHOME_F(s) expands to F(s) which returns __FlashStringHelper* pointing to PROGMEM.
|
// ESPHOME_F(s) expands to F(s) which returns __FlashStringHelper* pointing to PROGMEM.
|
||||||
// Store as FLASH_STRING — value()/is_empty()/ref_or_copy_to() use _P functions
|
// Store as FLASH_STRING — value()/is_empty()/ref_or_copy_to() use _P functions.
|
||||||
// to access the PROGMEM pointer safely.
|
TemplatableValue(const __FlashStringHelper *str) : type_(FLASH_STRING) {
|
||||||
TemplatableValue(const __FlashStringHelper *str) requires std::same_as<T, std::string> : type_(FLASH_STRING) {
|
|
||||||
this->static_str_ = reinterpret_cast<const char *>(str);
|
this->static_str_ = reinterpret_cast<const char *>(str);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template<typename F> TemplatableValue(F value) requires(!std::invocable<F, X...>) : type_(VALUE) {
|
template<typename F> TemplatableValue(F value) requires(!std::invocable<F, X...>) : type_(VALUE) {
|
||||||
if constexpr (USE_HEAP_STORAGE) {
|
this->value_ = new std::string(std::move(value));
|
||||||
this->value_ = new T(std::move(value));
|
|
||||||
} else {
|
|
||||||
new (&this->value_) T(std::move(value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For stateless lambdas (convertible to function pointer): use function pointer
|
// For stateless lambdas (convertible to function pointer): use function pointer
|
||||||
template<typename F>
|
template<typename F>
|
||||||
TemplatableValue(F f) requires std::invocable<F, X...> && std::convertible_to<F, T (*)(X...)>
|
TemplatableValue(F f) requires std::invocable<F, X...> && std::convertible_to<F, std::string (*)(X...)>
|
||||||
: type_(STATELESS_LAMBDA) {
|
: type_(STATELESS_LAMBDA) {
|
||||||
this->stateless_f_ = f; // Implicit conversion to function pointer
|
this->stateless_f_ = f; // Implicit conversion to function pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
// For stateful lambdas (not convertible to function pointer): use std::function
|
// For stateful lambdas (not convertible to function pointer): use std::function
|
||||||
template<typename F>
|
template<typename F>
|
||||||
TemplatableValue(F f) requires std::invocable<F, X...> &&(!std::convertible_to<F, T (*)(X...)>) : type_(LAMBDA) {
|
TemplatableValue(F f) requires std::invocable<F, X...> &&(!std::convertible_to<F, std::string (*)(X...)>)
|
||||||
this->f_ = new std::function<T(X...)>(std::move(f));
|
: type_(LAMBDA) {
|
||||||
|
this->f_ = new std::function<std::string(X...)>(std::move(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy constructor
|
// Copy constructor
|
||||||
TemplatableValue(const TemplatableValue &other) : type_(other.type_) {
|
TemplatableValue(const TemplatableValue &other) : type_(other.type_) {
|
||||||
if (this->type_ == VALUE) {
|
if (this->type_ == VALUE) {
|
||||||
if constexpr (USE_HEAP_STORAGE) {
|
this->value_ = new std::string(*other.value_);
|
||||||
this->value_ = new T(*other.value_);
|
|
||||||
} else {
|
|
||||||
new (&this->value_) T(other.value_);
|
|
||||||
}
|
|
||||||
} else if (this->type_ == LAMBDA) {
|
} else if (this->type_ == LAMBDA) {
|
||||||
this->f_ = new std::function<T(X...)>(*other.f_);
|
this->f_ = new std::function<std::string(X...)>(*other.f_);
|
||||||
} else if (this->type_ == STATELESS_LAMBDA) {
|
} else if (this->type_ == STATELESS_LAMBDA) {
|
||||||
this->stateless_f_ = other.stateless_f_;
|
this->stateless_f_ = other.stateless_f_;
|
||||||
} else if (this->type_ == STATIC_STRING || this->type_ == FLASH_STRING) {
|
} else if (this->type_ == STATIC_STRING || this->type_ == FLASH_STRING) {
|
||||||
@@ -108,12 +274,8 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
// Move constructor
|
// Move constructor
|
||||||
TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) {
|
TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) {
|
||||||
if (this->type_ == VALUE) {
|
if (this->type_ == VALUE) {
|
||||||
if constexpr (USE_HEAP_STORAGE) {
|
this->value_ = other.value_;
|
||||||
this->value_ = other.value_;
|
other.value_ = nullptr;
|
||||||
other.value_ = nullptr;
|
|
||||||
} else {
|
|
||||||
new (&this->value_) T(std::move(other.value_));
|
|
||||||
}
|
|
||||||
} else if (this->type_ == LAMBDA) {
|
} else if (this->type_ == LAMBDA) {
|
||||||
this->f_ = other.f_;
|
this->f_ = other.f_;
|
||||||
other.f_ = nullptr;
|
other.f_ = nullptr;
|
||||||
@@ -144,11 +306,7 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
|
|
||||||
~TemplatableValue() {
|
~TemplatableValue() {
|
||||||
if (this->type_ == VALUE) {
|
if (this->type_ == VALUE) {
|
||||||
if constexpr (USE_HEAP_STORAGE) {
|
delete this->value_;
|
||||||
delete this->value_;
|
|
||||||
} else {
|
|
||||||
this->value_.~T();
|
|
||||||
}
|
|
||||||
} else if (this->type_ == LAMBDA) {
|
} else if (this->type_ == LAMBDA) {
|
||||||
delete this->f_;
|
delete this->f_;
|
||||||
}
|
}
|
||||||
@@ -157,53 +315,40 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
|
|
||||||
bool has_value() const { return this->type_ != NONE; }
|
bool has_value() const { return this->type_ != NONE; }
|
||||||
|
|
||||||
T value(X... x) const {
|
std::string value(X... x) const {
|
||||||
switch (this->type_) {
|
switch (this->type_) {
|
||||||
case STATELESS_LAMBDA:
|
case STATELESS_LAMBDA:
|
||||||
return this->stateless_f_(x...); // Direct function pointer call
|
return this->stateless_f_(x...); // Direct function pointer call
|
||||||
case LAMBDA:
|
case LAMBDA:
|
||||||
return (*this->f_)(x...); // std::function call
|
return (*this->f_)(x...); // std::function call
|
||||||
case VALUE:
|
case VALUE:
|
||||||
if constexpr (USE_HEAP_STORAGE) {
|
return *this->value_;
|
||||||
return *this->value_;
|
|
||||||
} else {
|
|
||||||
return this->value_;
|
|
||||||
}
|
|
||||||
case STATIC_STRING:
|
case STATIC_STRING:
|
||||||
// if constexpr required: code must compile for all T, but STATIC_STRING
|
return std::string(this->static_str_);
|
||||||
// can only be set when T is std::string (enforced by constructor constraint)
|
|
||||||
if constexpr (std::same_as<T, std::string>) {
|
|
||||||
return std::string(this->static_str_);
|
|
||||||
}
|
|
||||||
__builtin_unreachable();
|
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
case FLASH_STRING:
|
case FLASH_STRING: {
|
||||||
// PROGMEM pointer — must use _P functions to access on ESP8266
|
// PROGMEM pointer — must use _P functions to access on ESP8266
|
||||||
if constexpr (std::same_as<T, std::string>) {
|
size_t len = strlen_P(this->static_str_);
|
||||||
size_t len = strlen_P(this->static_str_);
|
std::string result(len, '\0');
|
||||||
std::string result(len, '\0');
|
memcpy_P(result.data(), this->static_str_, len);
|
||||||
memcpy_P(result.data(), this->static_str_, len);
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
|
||||||
__builtin_unreachable();
|
|
||||||
#endif
|
#endif
|
||||||
case NONE:
|
case NONE:
|
||||||
default:
|
default:
|
||||||
return T{};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<T> optional_value(X... x) {
|
optional<std::string> optional_value(X... x) const {
|
||||||
if (!this->has_value()) {
|
if (!this->has_value())
|
||||||
return {};
|
return {};
|
||||||
}
|
|
||||||
return this->value(x...);
|
return this->value(x...);
|
||||||
}
|
}
|
||||||
|
|
||||||
T value_or(X... x, T default_value) {
|
std::string value_or(X... x, std::string default_value) const {
|
||||||
if (!this->has_value()) {
|
if (!this->has_value())
|
||||||
return default_value;
|
return default_value;
|
||||||
}
|
|
||||||
return this->value(x...);
|
return this->value(x...);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,10 +361,10 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
/// The pointer is always directly readable — FLASH_STRING uses a separate type.
|
/// The pointer is always directly readable — FLASH_STRING uses a separate type.
|
||||||
const char *get_static_string() const { return this->static_str_; }
|
const char *get_static_string() const { return this->static_str_; }
|
||||||
|
|
||||||
/// Check if the string value is empty without allocating (for std::string specialization).
|
/// Check if the string value is empty without allocating.
|
||||||
/// For NONE, returns true. For STATIC_STRING/VALUE, checks without allocation.
|
/// For NONE, returns true. For STATIC_STRING/VALUE, checks without allocation.
|
||||||
/// For LAMBDA/STATELESS_LAMBDA, must call value() which may allocate.
|
/// For LAMBDA/STATELESS_LAMBDA, must call value() which may allocate.
|
||||||
bool is_empty() const requires std::same_as<T, std::string> {
|
bool is_empty() const {
|
||||||
switch (this->type_) {
|
switch (this->type_) {
|
||||||
case NONE:
|
case NONE:
|
||||||
return true;
|
return true;
|
||||||
@@ -245,7 +390,7 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
/// @param lambda_buf Buffer used only for copy cases (must remain valid while StringRef is used).
|
/// @param lambda_buf Buffer used only for copy cases (must remain valid while StringRef is used).
|
||||||
/// @param lambda_buf_size Size of the buffer.
|
/// @param lambda_buf_size Size of the buffer.
|
||||||
/// @return StringRef pointing to the string data.
|
/// @return StringRef pointing to the string data.
|
||||||
StringRef ref_or_copy_to(char *lambda_buf, size_t lambda_buf_size) const requires std::same_as<T, std::string> {
|
StringRef ref_or_copy_to(char *lambda_buf, size_t lambda_buf_size) const {
|
||||||
switch (this->type_) {
|
switch (this->type_) {
|
||||||
case NONE:
|
case NONE:
|
||||||
return StringRef();
|
return StringRef();
|
||||||
@@ -278,22 +423,20 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected : enum : uint8_t {
|
protected:
|
||||||
NONE,
|
enum : uint8_t {
|
||||||
VALUE,
|
NONE,
|
||||||
LAMBDA,
|
VALUE,
|
||||||
STATELESS_LAMBDA,
|
LAMBDA,
|
||||||
STATIC_STRING, // For const char* when T is std::string - avoids heap allocation
|
STATELESS_LAMBDA,
|
||||||
FLASH_STRING, // PROGMEM pointer on ESP8266; never set on other platforms
|
STATIC_STRING, // For const char* — avoids heap allocation
|
||||||
} type_;
|
FLASH_STRING, // PROGMEM pointer on ESP8266; never set on other platforms
|
||||||
// For std::string, use heap pointer to minimize union size (4 bytes vs 12+).
|
} type_;
|
||||||
// For other types, store value inline as before.
|
|
||||||
using ValueStorage = std::conditional_t<USE_HEAP_STORAGE, T *, T>;
|
|
||||||
union {
|
union {
|
||||||
ValueStorage value_; // T for inline storage, T* for heap storage
|
std::string *value_; // Heap-allocated string (VALUE)
|
||||||
std::function<T(X...)> *f_;
|
std::function<std::string(X...)> *f_; // Heap-allocated std::function (LAMBDA)
|
||||||
T (*stateless_f_)(X...);
|
std::string (*stateless_f_)(X...); // Function pointer (STATELESS_LAMBDA)
|
||||||
const char *static_str_; // For STATIC_STRING and FLASH_STRING types
|
const char *static_str_; // For STATIC_STRING and FLASH_STRING types
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -819,11 +819,17 @@ async def templatable(
|
|||||||
args: list[tuple[SafeExpType, str]],
|
args: list[tuple[SafeExpType, str]],
|
||||||
output_type: SafeExpType | None,
|
output_type: SafeExpType | None,
|
||||||
to_exp: Callable | dict = None,
|
to_exp: Callable | dict = None,
|
||||||
|
*,
|
||||||
|
wrap_constant: bool = False,
|
||||||
):
|
):
|
||||||
"""Generate code for a templatable config option.
|
"""Generate code for a templatable config option.
|
||||||
|
|
||||||
If `value` is a templated value, the lambda expression is returned.
|
If `value` is a templated value, the lambda expression is returned.
|
||||||
Otherwise the value is returned as-is (optionally process with to_exp).
|
For std::string output, constants are returned as-is (with PROGMEM wrapping),
|
||||||
|
using the std::string-specific TemplatableValue specialization.
|
||||||
|
For all other output types, constants are wrapped in stateless lambdas
|
||||||
|
so that TemplatableFn-backed macro-generated fields can store them as
|
||||||
|
function pointers.
|
||||||
|
|
||||||
:param value: The value to process.
|
:param value: The value to process.
|
||||||
:param args: The arguments for the lambda expression.
|
:param args: The arguments for the lambda expression.
|
||||||
@@ -833,20 +839,28 @@ async def templatable(
|
|||||||
"""
|
"""
|
||||||
if is_template(value):
|
if is_template(value):
|
||||||
return await process_lambda(value, args, return_type=output_type)
|
return await process_lambda(value, args, return_type=output_type)
|
||||||
if to_exp is None:
|
# Late import to avoid circular dependency (cpp_generator <-> cpp_types).
|
||||||
|
from esphome.cpp_types import std_string
|
||||||
|
|
||||||
|
if to_exp is not None:
|
||||||
|
value = to_exp[value] if isinstance(to_exp, dict) else to_exp(value)
|
||||||
|
elif (
|
||||||
|
isinstance(value, str) and output_type is not None and output_type is std_string
|
||||||
|
):
|
||||||
# Automatically wrap static strings in ESPHOME_F() for PROGMEM storage on ESP8266.
|
# Automatically wrap static strings in ESPHOME_F() for PROGMEM storage on ESP8266.
|
||||||
# On other platforms ESPHOME_F() is a no-op returning const char*.
|
# On other platforms ESPHOME_F() is a no-op returning const char*.
|
||||||
# Lazy import to avoid circular dependency (cpp_generator <-> cpp_types).
|
return FlashStringLiteral(value)
|
||||||
# Identity check (is) avoids brittle string comparison.
|
# Wrap non-string constants in stateless lambdas so that TemplatableFn
|
||||||
if isinstance(value, str) and output_type is not None:
|
# (used by TEMPLATABLE_VALUE macro) stores them as function pointers.
|
||||||
from esphome.cpp_types import std_string
|
# wrap_constant=True forces wrapping even with output_type=None (compiler deduces type).
|
||||||
|
if (output_type is not None or wrap_constant) and output_type is not std_string:
|
||||||
if output_type is std_string:
|
return LambdaExpression(
|
||||||
return FlashStringLiteral(value)
|
f"return {safe_exp(value)};",
|
||||||
return value
|
args,
|
||||||
if isinstance(to_exp, dict):
|
capture="",
|
||||||
return to_exp[value]
|
return_type=output_type,
|
||||||
return to_exp(value)
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class MockObj(Expression):
|
class MockObj(Expression):
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ static void SensorFilter_Chain3(benchmark::State &state) {
|
|||||||
Sensor sensor;
|
Sensor sensor;
|
||||||
|
|
||||||
sensor.add_filters({
|
sensor.add_filters({
|
||||||
new OffsetFilter(1.0f),
|
new OffsetFilter([]() -> float { return 1.0f; }),
|
||||||
new MultiplyFilter(2.0f),
|
new MultiplyFilter([]() -> float { return 2.0f; }),
|
||||||
new SlidingWindowMovingAverageFilter(5, 1, 1),
|
new SlidingWindowMovingAverageFilter(5, 1, 1),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -669,11 +669,11 @@ async def test_templatable__int_with_std_string() -> None:
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_templatable__string_with_non_string_output_type() -> None:
|
async def test_templatable__string_with_non_string_output_type() -> None:
|
||||||
"""Static string with non-std::string output_type returns raw string."""
|
"""Static string with non-std::string output_type returns stateless lambda."""
|
||||||
result = await cg.templatable("hello", [], ct.bool_)
|
result = await cg.templatable("hello", [], ct.bool_)
|
||||||
|
|
||||||
assert isinstance(result, str)
|
assert isinstance(result, cg.LambdaExpression)
|
||||||
assert result == "hello"
|
assert result.capture == ""
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -684,6 +684,15 @@ async def test_templatable__with_to_exp_callable() -> None:
|
|||||||
assert result == 84
|
assert result == 84
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_templatable__with_to_exp_callable_and_output_type() -> None:
|
||||||
|
"""When to_exp is provided with non-string output_type, result is lambda-wrapped."""
|
||||||
|
result = await cg.templatable(42, [], ct.int_, to_exp=lambda x: x * 2)
|
||||||
|
|
||||||
|
assert isinstance(result, cg.LambdaExpression)
|
||||||
|
assert result.capture == ""
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_templatable__with_to_exp_dict() -> None:
|
async def test_templatable__with_to_exp_dict() -> None:
|
||||||
"""When to_exp is a dict, value is looked up."""
|
"""When to_exp is a dict, value is looked up."""
|
||||||
|
|||||||
Reference in New Issue
Block a user