diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 713f20fb95..f47fc06b3d 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -31,17 +31,19 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_WEB_SERVER, ) -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import CORE, CoroPriority, Lambda, coroutine_with_priority from esphome.core.entity_helpers import ( entity_duplicate_validator, queue_entity_register, setup_entity, ) +from esphome.cpp_generator import LambdaExpression IS_PLATFORM_COMPONENT = True fan_ns = cg.esphome_ns.namespace("fan") Fan = fan_ns.class_("Fan", cg.EntityBase) +FanCall = fan_ns.class_("FanCall") FanDirection = fan_ns.enum("FanDirection", is_class=True) FAN_DIRECTION_ENUM = { @@ -347,17 +349,38 @@ async def fan_turn_off_to_code(config, action_id, template_arg, args): ) async def fan_turn_on_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, paren) - if (oscillating := config.get(CONF_OSCILLATING)) is not None: - template_ = await cg.templatable(oscillating, args, cg.bool_) - cg.add(var.set_oscillating(template_)) - if (speed := config.get(CONF_SPEED)) is not None: - template_ = await cg.templatable(speed, args, cg.int_) - cg.add(var.set_speed(template_)) - if (direction := config.get(CONF_DIRECTION)) is not None: - template_ = await cg.templatable(direction, args, FanDirection) - cg.add(var.set_direction(template_)) - return var + + # All configured fields are folded into a single stateless lambda whose + # constants live in flash; the action stores only a function pointer. + FIELDS = ( + (CONF_OSCILLATING, "set_oscillating", cg.bool_), + (CONF_SPEED, "set_speed", cg.int_), + (CONF_DIRECTION, "set_direction", FanDirection), + ) + + fwd_args = ", ".join(name for _, name in args) + body_lines: list[str] = [] + for conf_key, setter, type_ in FIELDS: + if (value := config.get(conf_key)) is None: + continue + if isinstance(value, Lambda): + inner = await cg.process_lambda(value, args, return_type=type_) + body_lines.append(f"call.{setter}(({inner})({fwd_args}));") + else: + body_lines.append(f"call.{setter}({cg.safe_exp(value)});") + + # Match TurnOnAction::ApplyFn signature: const Ts &... for trigger args. + apply_args = [ + (FanCall.operator("ref"), "call"), + *((t.operator("const").operator("ref"), n) for t, n in args), + ] + apply_lambda = LambdaExpression( + ["\n".join(body_lines)], + apply_args, + capture="", + return_type=cg.void, + ) + return cg.new_Pvariable(action_id, template_arg, paren, apply_lambda) @automation.register_action( diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 3ee6f89e55..d8eda41b27 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -7,29 +7,24 @@ namespace esphome { namespace fan { +// All configured fields are baked into a single stateless lambda whose +// constants live in flash. The action only stores one function pointer +// plus one parent pointer, regardless of how many fields the user set. +// Trigger args are forwarded to the apply function so user lambdas +// (e.g. `speed: !lambda "return x;"`) keep working. template class TurnOnAction : public Action { public: - explicit TurnOnAction(Fan *state) : state_(state) {} - - TEMPLATABLE_VALUE(bool, oscillating) - TEMPLATABLE_VALUE(int, speed) - TEMPLATABLE_VALUE(FanDirection, direction) + using ApplyFn = void (*)(FanCall &, const Ts &...); + TurnOnAction(Fan *state, ApplyFn apply) : state_(state), apply_(apply) {} void play(const Ts &...x) override { auto call = this->state_->turn_on(); - if (this->oscillating_.has_value()) { - call.set_oscillating(this->oscillating_.value(x...)); - } - if (this->speed_.has_value()) { - call.set_speed(this->speed_.value(x...)); - } - if (this->direction_.has_value()) { - call.set_direction(this->direction_.value(x...)); - } + this->apply_(call, x...); call.perform(); } Fan *state_; + ApplyFn apply_; }; template class TurnOffAction : public Action {