From b0cf94c40944d86220ec493e0d21783f8036a9ab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 10:38:41 -0600 Subject: [PATCH] Auto-wrap static strings in ESPHOME_F() via templatable() Move FlashStringLiteral wrapping from per-component manual code into cg.templatable() itself. When output_type is std::string and the value is a static string (not a lambda), it is automatically wrapped in ESPHOME_F() for PROGMEM storage on ESP8266. On other platforms ESPHOME_F() is a no-op returning const char*. This makes all ~50 existing cg.templatable(..., cg.std_string) call sites across every component benefit automatically, with no per-component changes needed. Simplify api/__init__.py by switching from output_type=None to cg.std_string and removing the manual isinstance/FlashStringLiteral checks that are now redundant. --- esphome/components/api/__init__.py | 31 ++++++++---------------------- esphome/cpp_generator.py | 4 ++++ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 51654f1b7c1..da75133a8ab 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -524,30 +524,23 @@ async def homeassistant_service_to_code( cg.add_define("USE_API_HOMEASSISTANT_SERVICES") serv = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, serv, False) - templ = await cg.templatable(config[CONF_ACTION], args, None) - # Wrap static strings in ESPHOME_F() for PROGMEM on ESP8266 - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) + templ = await cg.templatable(config[CONF_ACTION], args, cg.std_string) cg.add(var.set_service(templ)) # Initialize FixedVectors with exact sizes from config cg.add(var.init_data(len(config[CONF_DATA]))) for key, value in config[CONF_DATA].items(): - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) + templ = await cg.templatable(value, args, cg.std_string) cg.add(var.add_data(cg.FlashStringLiteral(key), templ)) cg.add(var.init_data_template(len(config[CONF_DATA_TEMPLATE]))) for key, value in config[CONF_DATA_TEMPLATE].items(): - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) + templ = await cg.templatable(value, args, cg.std_string) cg.add(var.add_data_template(cg.FlashStringLiteral(key), templ)) cg.add(var.init_variables(len(config[CONF_VARIABLES]))) for key, value in config[CONF_VARIABLES].items(): - templ = await cg.templatable(value, args, None) + templ = await cg.templatable(value, args, cg.std_string) cg.add(var.add_variable(cg.FlashStringLiteral(key), templ)) if on_error := config.get(CONF_ON_ERROR): @@ -616,29 +609,23 @@ async def homeassistant_event_to_code(config, action_id, template_arg, args): cg.add_define("USE_API_HOMEASSISTANT_SERVICES") serv = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, serv, True) - templ = await cg.templatable(config[CONF_EVENT], args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) + templ = await cg.templatable(config[CONF_EVENT], args, cg.std_string) cg.add(var.set_service(templ)) # Initialize FixedVectors with exact sizes from config cg.add(var.init_data(len(config[CONF_DATA]))) for key, value in config[CONF_DATA].items(): - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) + templ = await cg.templatable(value, args, cg.std_string) cg.add(var.add_data(cg.FlashStringLiteral(key), templ)) cg.add(var.init_data_template(len(config[CONF_DATA_TEMPLATE]))) for key, value in config[CONF_DATA_TEMPLATE].items(): - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) + templ = await cg.templatable(value, args, cg.std_string) cg.add(var.add_data_template(cg.FlashStringLiteral(key), templ)) cg.add(var.init_variables(len(config[CONF_VARIABLES]))) for key, value in config[CONF_VARIABLES].items(): - templ = await cg.templatable(value, args, None) + templ = await cg.templatable(value, args, cg.std_string) cg.add(var.add_variable(cg.FlashStringLiteral(key), templ)) return var @@ -666,8 +653,6 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg # Initialize FixedVector with exact size (1 data field) cg.add(var.init_data(1)) templ = await cg.templatable(config[CONF_TAG], args, cg.std_string) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) cg.add(var.add_data(cg.FlashStringLiteral("tag_id"), templ)) return var diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 020f54d6b2b..c1476aa728b 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -778,6 +778,10 @@ async def templatable( if is_template(value): return await process_lambda(value, args, return_type=output_type) if to_exp is None: + # Automatically wrap static strings in ESPHOME_F() for PROGMEM storage on ESP8266. + # On other platforms ESPHOME_F() is a no-op returning const char*. + if isinstance(value, str) and str(output_type) == "std::string": + return FlashStringLiteral(value) return value if isinstance(to_exp, dict): return to_exp[value]