[core][select] Pass the select.set option as a plain literal (#19568)

This commit is contained in:
J. Nick Koston
2026-09-24 09:05:24 -04:00
committed by GitHub
parent 75360a5d52
commit 993c96875c
4 changed files with 35 additions and 9 deletions
+9
View File
@@ -224,6 +224,15 @@ def flash_string(config: ConfigType, value: str) -> str:
return str(cg.safe_exp(value))
def literal_with_length(config: ConfigType, value: str) -> str:
"""Renderer for a ``(const char *, size_t)`` target: a plain literal plus its byte length.
The target compares or copies the bytes in place, so it needs the RAM literal rather than
the PROGMEM rendering on ESP8266, and the length saves a strlen.
"""
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
@dataclass(frozen=True)
class ApplyCall:
"""One statement from config keys, e.g. ``"set_range({}, {})"`` with ``((CONF_LOW, cg.float_), ...)``.
+2 -8
View File
@@ -494,12 +494,6 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
)
def _literal_with_length(config: ConfigType, value: str) -> str:
# The (const char *, size_t) overload compares bytes in place, so it needs a plain
# literal rather than the default PROGMEM rendering on ESP8266, and skips a strlen.
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
automation.register_apply_action(
"climate.control",
CLIMATE_CONTROL_ACTION_SCHEMA,
@@ -517,14 +511,14 @@ automation.register_apply_action(
CONF_CUSTOM_FAN_MODE,
"set_fan_mode",
cg.std_string,
const_fn=_literal_with_length,
const_fn=automation.literal_with_length,
),
automation.ApplyField(CONF_PRESET, "set_preset", ClimatePreset),
automation.ApplyField(
CONF_CUSTOM_PRESET,
"set_preset",
cg.std_string,
const_fn=_literal_with_length,
const_fn=automation.literal_with_length,
),
automation.ApplyField(CONF_SWING_MODE, "set_swing_mode", ClimateSwingMode),
call="make_call",
+6 -1
View File
@@ -142,7 +142,12 @@ automation.register_apply_action(
cv.Required(CONF_OPTION): cv.templatable(cv.string_strict),
}
),
automation.ApplyField(CONF_OPTION, "set_option", cg.std_string),
automation.ApplyField(
CONF_OPTION,
"set_option",
cg.std_string,
const_fn=automation.literal_with_length,
),
call="make_call",
)
+18
View File
@@ -18,6 +18,7 @@ from esphome.automation import (
TriggerOnTrueForwarder,
build_callback_automations,
has_non_synchronous_actions,
literal_with_length,
maybe_simple_id,
register_apply_action,
register_apply_condition,
@@ -838,6 +839,23 @@ async def test_apply_string_constant_stays_in_flash_on_esp8266(
)
@pytest.mark.asyncio
@pytest.mark.parametrize("platform", ["esp32", "esp8266"])
async def test_apply_literal_with_length_is_plain_on_every_platform(
registries: tuple[Registry, Registry], mock_cg: MockCodegen, platform: str
) -> None:
"""A (const char *, size_t) target gets the RAM literal and its byte length, never a flash copy."""
fields = (
ApplyField("option", "set_option", cg.std_string, const_fn=literal_with_length),
)
await _run_apply_action(
registries, fields, {"option": "h\u00e9llo"}, platform=platform
)
text = _apply_lambda(mock_cg)
assert f'::{PARENT_OBJ}->set_option("h\\303\\251llo", 6);' in text
assert "progmem_string" not in text
@pytest.mark.asyncio
async def test_register_apply_condition_predicate(
registries: tuple[Registry, Registry], mock_cg: MockCodegen