[core] Add cg.shared_progmem_array for deduplicated PROGMEM tables

Emits one PROGMEM array per distinct type and contents and returns the
same variable to later callers. The array name is made unique against
every config id and registered variable, so a generated table can never
collide with a user id.
This commit is contained in:
J. Nick Koston
2026-10-02 20:45:20 -05:00
parent f5c79eeec0
commit ad1e5ef9d0
3 changed files with 61 additions and 1 deletions
+1
View File
@@ -40,6 +40,7 @@ from esphome.cpp_generator import ( # noqa: F401
progmem_array,
safe_exp,
set_cpp_standard,
shared_progmem_array,
statement,
static_const_array,
static_function,
+31 -1
View File
@@ -19,7 +19,12 @@ from esphome.core import (
TimePeriodNanoseconds,
TimePeriodSeconds,
)
from esphome.helpers import cpp_string_escape, indent, indent_all_but_first_and_last
from esphome.helpers import (
cpp_string_escape,
ensure_unique_string,
indent,
indent_all_but_first_and_last,
)
from esphome.types import Expression, SafeExpType, TemplateArgsType
from esphome.util import OrderedDict
from esphome.yaml_util import ESPHomeDataBase
@@ -463,6 +468,31 @@ def progmem_array(id_, rhs) -> "MockObj":
return obj
def shared_progmem_array(
name: str, type_: "MockObjClass", rhs: SafeExpType
) -> "MockObj":
"""Emit a PROGMEM array once per distinct type and contents; later calls reuse it.
The name is made unique against every config id and registered variable. Call it at
setup() top level: the array is a static local that later reuses must be able to see.
"""
from esphome.config import iter_ids
from esphome.config_validation import RESERVED_IDS
arrays: dict[str, MockObj] = CORE.data.setdefault("shared_progmem_array", {})
rhs = safe_exp(rhs)
key = f"{type_} {rhs}"
if (array := arrays.get(key)) is None:
used = {str(i) for i, _ in iter_ids(CORE.config)}
used |= {str(i) for i in CORE.variables}
used |= set(RESERVED_IDS) | CORE.loaded_integrations
unique = ensure_unique_string(name, used)
array = arrays[key] = progmem_array(
ID(unique, is_declaration=True, type=type_), rhs
)
return array
def static_const_array(id_, rhs) -> "MockObj":
rhs = safe_exp(rhs)
obj = MockObj(id_, ".")
+29
View File
@@ -4,6 +4,7 @@ import math
import pytest
from esphome import cpp_generator as cg, cpp_types as ct
from esphome.core import CORE, ID
class TestExpressions:
@@ -808,3 +809,31 @@ async def test_templatable__lambda_with_std_string() -> None:
result = await cg.templatable(lambda_obj, [], ct.std_string)
assert isinstance(result, cg.LambdaExpression)
class TestSharedProgmemArray:
def test_identical_contents_share_one_array(self) -> None:
CORE.config = {}
a = cg.shared_progmem_array("table", ct.uint8, [1, 2, 3])
b = cg.shared_progmem_array("table", ct.uint8, [1, 2, 3])
c = cg.shared_progmem_array("table", ct.uint8, [4])
assert a is b
assert str(a) != str(c)
assert sum("PROGMEM" in str(st) for st in CORE.main_statements) == 2
def test_same_contents_different_type_are_separate(self) -> None:
CORE.config = {}
a = cg.shared_progmem_array("table", ct.uint8, [1])
b = cg.shared_progmem_array("table", ct.uint16, [1])
assert str(a) != str(b)
def test_name_avoids_config_ids(self) -> None:
CORE.config = {"sensor": [{"id": ID("table", is_declaration=True)}]}
array = cg.shared_progmem_array("table", ct.uint8, [1])
assert str(array) == "table_2"
def test_name_avoids_registered_variables(self) -> None:
CORE.config = {}
CORE.register_variable(ID("table", is_declaration=True), cg.MockObj("table"))
array = cg.shared_progmem_array("table", ct.uint8, [1])
assert str(array) == "table_2"