diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 983a4777e81..969a6c8e8c9 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -11,6 +11,7 @@ from esphome.const import ( PLATFORM_ESP8266, ) from esphome.core import CORE, TimePeriod +from esphome.types import ConfigType, TemplateArgsType from . import FILTER_SOURCE_FILES, Nextion, nextion_ns, nextion_ref # noqa: F401 pylint: disable=unused-import from .base_component import ( @@ -177,6 +178,10 @@ _CALLBACK_AUTOMATIONS = ( automation.CallbackAutomation( CONF_ON_BUFFER_OVERFLOW, "add_buffer_overflow_event_callback" ), +) + +# The strings live only while the callback runs +_CUSTOM_CALLBACK_AUTOMATIONS = ( automation.CallbackAutomation( CONF_ON_CUSTOM_BINARY_SENSOR, "add_custom_binary_sensor_callback", @@ -293,3 +298,18 @@ async def to_code(config): cg.add_define(define_name) await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) + for entry in _CUSTOM_CALLBACK_AUTOMATIONS: + for conf in config.get(entry.conf_key, []): + await automation.build_callback_automation( + var, entry.callback_method, _owning_args(entry.args, conf), conf + ) + + +def _owning_args(args: TemplateArgsType, conf: ConfigType) -> TemplateArgsType: + """Pass std::string instead of StringRef to an automation that defers, as it keeps its arguments.""" + if not automation.has_non_synchronous_actions(conf): + return args + return [ + (cg.std_string if type_ is cg.StringRef else type_, name) + for type_, name in args + ] diff --git a/tests/component_tests/nextion/__init__.py b/tests/component_tests/nextion/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/component_tests/nextion/config/custom_triggers.yaml b/tests/component_tests/nextion/config/custom_triggers.yaml new file mode 100644 index 00000000000..6391a8babe8 --- /dev/null +++ b/tests/component_tests/nextion/config/custom_triggers.yaml @@ -0,0 +1,21 @@ +esphome: + name: test + +esp32: + board: esp32dev + framework: + type: esp-idf + +uart: + tx_pin: 17 + rx_pin: 16 + baud_rate: 115200 + +display: + - platform: nextion + on_custom_text_sensor: + - then: + - lambda: 'ESP_LOGD("test", "%s=%s", key.c_str(), value.c_str());' + - then: + - delay: 1s + - lambda: 'ESP_LOGD("test", "%s=%s", key.c_str(), value.c_str());' diff --git a/tests/component_tests/nextion/test_custom_trigger_args.py b/tests/component_tests/nextion/test_custom_trigger_args.py new file mode 100644 index 00000000000..765b7ec786c --- /dev/null +++ b/tests/component_tests/nextion/test_custom_trigger_args.py @@ -0,0 +1,47 @@ +"""Tests for the argument types of the nextion custom-protocol triggers.""" + +from collections.abc import Callable +from pathlib import Path + +import esphome.codegen as cg +from esphome.components.nextion.display import _owning_args +from esphome.core import Lambda + +ARGS = [(cg.StringRef, "key"), (cg.bool_, "value")] +SYNCHRONOUS_ACTION = {"lambda": Lambda('ESP_LOGD("test", "ran");')} +DEFERRING_ACTION = {"delay": "1s"} +TRUE_CONDITION = {"lambda": Lambda("return true;")} + + +def test_synchronous_automation_keeps_string_ref() -> None: + assert _owning_args(ARGS, {"then": [SYNCHRONOUS_ACTION]}) == ARGS + + +def test_deferring_automation_gets_std_string() -> None: + assert _owning_args(ARGS, {"then": [SYNCHRONOUS_ACTION, DEFERRING_ACTION]}) == [ + (cg.std_string, "key"), + (cg.bool_, "value"), + ] + + +def test_nested_deferring_action_gets_std_string() -> None: + actions = [{"if": {"condition": TRUE_CONDITION, "then": [DEFERRING_ACTION]}}] + assert _owning_args(ARGS, {"then": actions})[0] == (cg.std_string, "key") + + +def test_all_string_refs_swapped() -> None: + args = [(cg.StringRef, "key"), (cg.StringRef, "value")] + assert _owning_args(args, {"then": [DEFERRING_ACTION]}) == [ + (cg.std_string, "key"), + (cg.std_string, "value"), + ] + + +def test_generated_automation_types( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + main_cpp = generate_main(component_config_path("custom_triggers.yaml")) + + assert "Automation" in main_cpp + assert "Automation" in main_cpp diff --git a/tests/components/nextion/common.yaml b/tests/components/nextion/common.yaml index 4f4347d89a9..fc2c833e83c 100644 --- a/tests/components/nextion/common.yaml +++ b/tests/components/nextion/common.yaml @@ -287,13 +287,20 @@ display: then: logger.log: "Nextion reported a buffer overflow!" on_custom_text_sensor: - then: - - lambda: |- - // key: StringRef, value: StringRef - if (key == "csv") { - // parse value here, or forward to your own component - ESP_LOGD("nextion.csv", "Got CSV: %s", value.c_str()); - } + - then: + - lambda: |- + // key: StringRef, value: StringRef + if (key == "csv") { + // parse value here, or forward to your own component + ESP_LOGD("nextion.csv", "Got CSV: %s", value.c_str()); + } + # Deferring: key and value are passed as std::string + - then: + - delay: 1s + - lambda: |- + if (key == "csv") { + ESP_LOGD("nextion.csv", "Got CSV earlier: %s", value.c_str()); + } on_custom_sensor: then: - lambda: |- @@ -308,9 +315,13 @@ display: ESP_LOGD("nextion.btn", "btn1=%s", ONOFF(value)); } on_custom_switch: - then: - - lambda: |- - ESP_LOGD("nextion.sw", "%s=%s", key.c_str(), ONOFF(value)); + - then: + - lambda: |- + ESP_LOGD("nextion.sw", "%s=%s", key.c_str(), ONOFF(value)); + - then: + - delay: 1s + - lambda: |- + ESP_LOGD("nextion.sw", "%s was %s", key.c_str(), ONOFF(value)); on_page: then: lambda: 'ESP_LOGD("display","Display shows new page %u", x);'