mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 14:43:51 +00:00
[lvgl] Fix nested lambdas in automations unable to access parameters (#11583)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.const import CONF_ITEMS
|
||||
@@ -12,6 +13,7 @@ from esphome.core import ID, Lambda
|
||||
from esphome.cpp_generator import LambdaExpression, MockObj
|
||||
from esphome.cpp_types import uint32
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import Expression, SafeExpType
|
||||
|
||||
from .helpers import requires_component
|
||||
|
||||
@@ -42,7 +44,13 @@ def static_cast(type, value):
|
||||
def call_lambda(lamb: LambdaExpression):
|
||||
expr = lamb.content.strip()
|
||||
if expr.startswith("return") and expr.endswith(";"):
|
||||
return expr[6:][:-1].strip()
|
||||
return expr[6:-1].strip()
|
||||
# If lambda has parameters, call it with those parameter names
|
||||
# Parameter names come from hardcoded component code (like "x", "it", "event")
|
||||
# not from user input, so they're safe to use directly
|
||||
if lamb.parameters and lamb.parameters.parameters:
|
||||
param_names = ", ".join(str(param.id) for param in lamb.parameters.parameters)
|
||||
return f"{lamb}({param_names})"
|
||||
return f"{lamb}()"
|
||||
|
||||
|
||||
@@ -65,10 +73,20 @@ class LValidator:
|
||||
return cv.returning_lambda(value)
|
||||
return self.validator(value)
|
||||
|
||||
async def process(self, value, args=()):
|
||||
async def process(
|
||||
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
|
||||
) -> Expression:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, Lambda):
|
||||
# Local import to avoid circular import
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
return cg.RawExpression(
|
||||
call_lambda(
|
||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import image
|
||||
from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw
|
||||
@@ -17,6 +19,7 @@ from esphome.cpp_generator import MockObj
|
||||
from esphome.cpp_types import ESPTime, int32, uint32
|
||||
from esphome.helpers import cpp_string_escape
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import Expression, SafeExpType
|
||||
|
||||
from . import types as ty
|
||||
from .defines import (
|
||||
@@ -388,11 +391,23 @@ class TextValidator(LValidator):
|
||||
return value
|
||||
return super().__call__(value)
|
||||
|
||||
async def process(self, value, args=()):
|
||||
async def process(
|
||||
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
|
||||
) -> Expression:
|
||||
# Local import to avoid circular import at module level
|
||||
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
|
||||
if isinstance(value, dict):
|
||||
if format_str := value.get(CONF_FORMAT):
|
||||
args = [str(x) for x in value[CONF_ARGS]]
|
||||
arg_expr = cg.RawExpression(",".join(args))
|
||||
str_args = [str(x) for x in value[CONF_ARGS]]
|
||||
arg_expr = cg.RawExpression(",".join(str_args))
|
||||
format_str = cpp_string_escape(format_str)
|
||||
return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()")
|
||||
if time_format := value.get(CONF_TIME_FORMAT):
|
||||
|
||||
@@ -164,6 +164,9 @@ class LambdaContext(CodeContext):
|
||||
code_text.append(text)
|
||||
return code_text
|
||||
|
||||
def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]:
|
||||
return self.parameters
|
||||
|
||||
async def __aenter__(self):
|
||||
await super().__aenter__()
|
||||
add_line_marks(self.where)
|
||||
@@ -178,9 +181,8 @@ class LvContext(LambdaContext):
|
||||
|
||||
added_lambda_count = 0
|
||||
|
||||
def __init__(self, args=None):
|
||||
self.args = args or LVGL_COMP_ARG
|
||||
super().__init__(parameters=self.args)
|
||||
def __init__(self):
|
||||
super().__init__(parameters=LVGL_COMP_ARG)
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
await super().__aexit__(exc_type, exc_val, exc_tb)
|
||||
@@ -189,6 +191,11 @@ class LvContext(LambdaContext):
|
||||
cg.add(expression)
|
||||
return expression
|
||||
|
||||
def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]:
|
||||
# When generating automations, we don't want the `lv_component` parameter to be passed
|
||||
# to the lambda.
|
||||
return []
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.add(*args)
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from ..defines import CONF_WIDGET
|
||||
from ..lvcode import (
|
||||
API_EVENT,
|
||||
EVENT_ARG,
|
||||
LVGL_COMP_ARG,
|
||||
UPDATE_EVENT,
|
||||
LambdaContext,
|
||||
LvContext,
|
||||
@@ -30,7 +29,7 @@ async def to_code(config):
|
||||
await wait_for_widgets()
|
||||
async with LambdaContext(EVENT_ARG) as lamb:
|
||||
lv_add(sensor.publish_state(widget.get_value()))
|
||||
async with LvContext(LVGL_COMP_ARG):
|
||||
async with LvContext():
|
||||
lv_add(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj,
|
||||
|
||||
@@ -52,6 +52,19 @@ number:
|
||||
widget: spinbox_id
|
||||
id: lvgl_spinbox_number
|
||||
name: LVGL Spinbox Number
|
||||
- platform: template
|
||||
id: test_brightness
|
||||
name: "Test Brightness"
|
||||
min_value: 0
|
||||
max_value: 255
|
||||
step: 1
|
||||
optimistic: true
|
||||
# Test lambda in automation accessing x parameter directly
|
||||
# This is a real-world pattern from user configs
|
||||
on_value:
|
||||
- lambda: !lambda |-
|
||||
// Direct use of x parameter in automation
|
||||
ESP_LOGD("test", "Brightness: %.0f", x);
|
||||
|
||||
light:
|
||||
- platform: lvgl
|
||||
@@ -110,3 +123,21 @@ text:
|
||||
platform: lvgl
|
||||
widget: hello_label
|
||||
mode: text
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
id: test_text_sensor
|
||||
name: "Test Text Sensor"
|
||||
# Test nested lambdas in LVGL actions can access automation parameters
|
||||
on_value:
|
||||
- lvgl.label.update:
|
||||
id: hello_label
|
||||
text: !lambda return x.c_str();
|
||||
- lvgl.label.update:
|
||||
id: hello_label
|
||||
text: !lambda |-
|
||||
// Test complex lambda with conditionals accessing x parameter
|
||||
if (x == "*") {
|
||||
return "WILDCARD";
|
||||
}
|
||||
return x.c_str();
|
||||
|
||||
@@ -257,7 +257,30 @@ lvgl:
|
||||
text: "Hello shiny day"
|
||||
text_color: 0xFFFFFF
|
||||
align: bottom_mid
|
||||
- label:
|
||||
id: setup_lambda_label
|
||||
# Test lambda in widget property during setup (LvContext)
|
||||
# Should NOT receive lv_component parameter
|
||||
text: !lambda |-
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "Setup: %d", 42);
|
||||
return std::string(buf);
|
||||
align: top_mid
|
||||
text_font: space16
|
||||
- label:
|
||||
id: chip_info_label
|
||||
# Test complex setup lambda (real-world pattern)
|
||||
# Should NOT receive lv_component parameter
|
||||
text: !lambda |-
|
||||
// Test conditional compilation and string formatting
|
||||
char buf[64];
|
||||
#ifdef USE_ESP_IDF
|
||||
snprintf(buf, sizeof(buf), "IDF: v%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR);
|
||||
#else
|
||||
snprintf(buf, sizeof(buf), "Arduino");
|
||||
#endif
|
||||
return std::string(buf);
|
||||
align: top_left
|
||||
- obj:
|
||||
align: center
|
||||
arc_opa: COVER
|
||||
|
||||
Reference in New Issue
Block a user