[cover] Extract build_apply_lambda_action helper, add typing, use kwargs

This commit is contained in:
J. Nick Koston
2026-04-29 07:06:55 -05:00
parent f7c8df8234
commit 964cfaa730
2 changed files with 81 additions and 61 deletions
+52 -21
View File
@@ -295,37 +295,38 @@ COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
)
@automation.register_action(
"cover.control", ControlAction, COVER_CONTROL_ACTION_SCHEMA, synchronous=True
)
async def cover_control_to_code(config, action_id, template_arg, args):
async def build_apply_lambda_action(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
fields: tuple[tuple[str, str, object], ...],
prefix_args: list[tuple[object, str]],
statement_fn,
) -> MockObj:
"""Fold configured fields into a single stateless apply lambda action.
Used by both `cover.control` and `cover.template.publish` (and shared
with the template/cover platform). Constants are emitted as flash
immediates; user lambdas are invoked inline so trigger args still flow.
The trigger arg types are wrapped as `const T &` to match the
`void (*)(..., const Ts &...)` ApplyFn signature.
"""
paren = await cg.get_variable(config[CONF_ID])
# All configured fields are folded into a single stateless lambda whose
# constants live in flash; the action stores only a function pointer.
# CONF_STATE and CONF_POSITION are cv.Exclusive in the schema, so at most
# one is present and both dispatch to set_position.
FIELDS = (
(CONF_STOP, "set_stop", cg.bool_),
(CONF_STATE, "set_position", cg.float_),
(CONF_POSITION, "set_position", cg.float_),
(CONF_TILT, "set_tilt", cg.float_),
)
fwd_args = ", ".join(name for _, name in args)
body_lines: list[str] = []
for conf_key, setter, type_ in FIELDS:
for conf_key, target, 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}));")
value_expr = f"({inner})({fwd_args})"
else:
body_lines.append(f"call.{setter}({cg.safe_exp(value)});")
value_expr = str(cg.safe_exp(value))
body_lines.append(statement_fn(target, value_expr))
# Match ControlAction::ApplyFn signature: const Ts &... for trigger args.
apply_args = [
(CoverCall.operator("ref"), "call"),
*prefix_args,
*((t.operator("const").operator("ref"), n) for t, n in args),
]
apply_lambda = LambdaExpression(
@@ -337,6 +338,36 @@ async def cover_control_to_code(config, action_id, template_arg, args):
return cg.new_Pvariable(action_id, template_arg, paren, apply_lambda)
# CONF_STATE and CONF_POSITION are cv.Exclusive in the schema, so at most
# one is present and both dispatch to set_position.
_COVER_CONTROL_FIELDS = (
(CONF_STOP, "set_stop", cg.bool_),
(CONF_STATE, "set_position", cg.float_),
(CONF_POSITION, "set_position", cg.float_),
(CONF_TILT, "set_tilt", cg.float_),
)
@automation.register_action(
"cover.control", ControlAction, COVER_CONTROL_ACTION_SCHEMA, synchronous=True
)
async def cover_control_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
return await build_apply_lambda_action(
config=config,
action_id=action_id,
template_arg=template_arg,
args=args,
fields=_COVER_CONTROL_FIELDS,
prefix_args=[(CoverCall.operator("ref"), "call")],
statement_fn=lambda setter, expr: f"call.{setter}({expr});",
)
COVER_CONDITION_SCHEMA = cv.maybe_simple_value(
{cv.Required(CONF_ID): cv.use_id(Cover)}, key=CONF_ID
)
+29 -40
View File
@@ -19,8 +19,9 @@ from esphome.const import (
CONF_TILT_ACTION,
CONF_TILT_LAMBDA,
)
from esphome.core import Lambda
from esphome.cpp_generator import LambdaExpression
from esphome.core import ID
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType, TemplateArgsType
from .. import template_ns
@@ -112,6 +113,16 @@ async def to_code(config):
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
# CONF_STATE and CONF_POSITION are cv.Exclusive in the schema, so at most
# one is present and both map to the position field.
_COVER_PUBLISH_FIELDS = (
(CONF_STATE, "position", cg.float_),
(CONF_POSITION, "position", cg.float_),
(CONF_TILT, "tilt", cg.float_),
(CONF_CURRENT_OPERATION, "current_operation", cover.CoverOperation),
)
@automation.register_action(
"cover.template.publish",
cover.CoverPublishAction,
@@ -128,42 +139,20 @@ async def to_code(config):
),
synchronous=True,
)
async def cover_template_publish_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
# All configured fields are folded into a single stateless lambda whose
# constants live in flash; the action stores only a function pointer.
# The lambda mutates Cover fields directly (no CoverCall) since publish
# is a state push, not a control request.
# CONF_STATE and CONF_POSITION are cv.Exclusive in the schema, so at most
# one is present and both map to the position field.
FIELDS = (
(CONF_STATE, "position", cg.float_),
(CONF_POSITION, "position", cg.float_),
(CONF_TILT, "tilt", cg.float_),
(CONF_CURRENT_OPERATION, "current_operation", cover.CoverOperation),
async def cover_template_publish_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
# Mutates Cover fields directly (no CoverCall) since publish is a state
# push, not a control request.
return await cover.build_apply_lambda_action(
config=config,
action_id=action_id,
template_arg=template_arg,
args=args,
fields=_COVER_PUBLISH_FIELDS,
prefix_args=[(cover.Cover.operator("ptr"), "cover")],
statement_fn=lambda field, expr: f"cover->{field} = {expr};",
)
fwd_args = ", ".join(name for _, name in args)
body_lines: list[str] = []
for conf_key, field, 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"cover->{field} = ({inner})({fwd_args});")
else:
body_lines.append(f"cover->{field} = {cg.safe_exp(value)};")
# Match CoverPublishAction::ApplyFn: const Ts &... for trigger args.
apply_args = [
(cover.Cover.operator("ptr"), "cover"),
*((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)