Share the unique global name logic, emit the no-random marker symbol, test the color limits

This commit is contained in:
J. Nick Koston
2026-10-06 09:04:57 -04:00
committed by GitHub
parent 1e581db5d9
commit 6c6d6e2060
4 changed files with 40 additions and 20 deletions
+1
View File
@@ -45,6 +45,7 @@ from esphome.cpp_generator import ( # noqa: F401
static_const_array,
static_function,
templatable,
unique_global_id,
variable,
with_local_variable,
)
+3 -12
View File
@@ -1,6 +1,5 @@
from esphome import automation
import esphome.codegen as cg
from esphome.config import iter_ids
import esphome.config_validation as cv
from esphome.const import (
CONF_ALPHA,
@@ -30,9 +29,8 @@ from esphome.const import (
CONF_WHITE,
CONF_WIDTH,
)
from esphome.core import CORE, ID
from esphome.core import CORE
from esphome.cpp_generator import MockObjClass
from esphome.helpers import ensure_unique_string
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType
from esphome.util import Registry
@@ -494,7 +492,7 @@ async def addressable_color_wipe_effect_to_code(config, effect_id):
int(round(color[CONF_BLUE] * color_brightness * 255)),
int(round(color[CONF_WHITE] * 255)),
)
random_slot = NO_RANDOM_SLOT
random_slot = cg.RawExpression("light::NO_RANDOM_SLOT")
if color[CONF_RANDOM]:
random_slot = len(random_colors)
random_colors.append(rgbw)
@@ -518,14 +516,7 @@ async def addressable_color_wipe_effect_to_code(config, effect_id):
random_storage = cg.nullptr
if random_colors:
# One RAM slot per random entry; it starts at the configured color, as before
used = {str(i) for i, _ in iter_ids(CORE.config)} | {
str(i) for i in CORE.variables
}
id_ = ID(
ensure_unique_string(f"{effect_id.id}_random_colors", used),
is_declaration=True,
type=Color,
)
id_ = cg.unique_global_id(f"{effect_id.id}_random_colors", Color)
values = cg.ArrayInitializer(
*(Color(r, g, b, w) for r, g, b, w in random_colors)
)
+12 -7
View File
@@ -468,6 +468,17 @@ def progmem_array(id_, rhs) -> "MockObj":
return obj
def unique_global_id(name: str, type_: "MockObjClass") -> ID:
"""A declaration ID for a generated global, unique against config ids, variables and reserved names."""
from esphome.config import iter_ids
from esphome.config_validation import RESERVED_IDS
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
return ID(ensure_unique_string(name, used), is_declaration=True, type=type_)
def shared_progmem_array(
name: str, type_: "MockObjClass", rhs: SafeExpType, *, share: bool = True
) -> "MockObj":
@@ -477,18 +488,12 @@ def shared_progmem_array(
must be captureless. Its name is made unique against every config id and variable.
``share=False`` always emits a new array, e.g. for lambdas that may keep static state.
"""
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 share and (array := arrays.get(key)) is not None:
return array
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
id_ = ID(ensure_unique_string(name, used), is_declaration=True, type=type_)
id_ = unique_global_id(name, type_)
# Global, so any scope can use it; anything a lambda references is already declared.
CORE.add_global(ProgmemAssignmentExpression(type_, id_, rhs))
array = MockObj(id_, ".")
@@ -6,6 +6,10 @@ from collections.abc import Callable
from pathlib import Path
import re
import pytest
import esphome.config_validation as cv
def test_effect_colors_use_flash_tables(
generate_main: Callable[[str | Path], str],
@@ -26,4 +30,23 @@ def test_effect_colors_use_flash_tables(
# Only the random entry gets a RAM slot, starting at its configured color
assert re.search(r"static Color \w+_random_colors\[\] = \{Color\(", main_cpp)
assert ".random_slot = 0," in main_cpp
assert ".random_slot = 255," in main_cpp
assert ".random_slot = light::NO_RANDOM_SLOT," in main_cpp
def _color_wipe_colors(colors: list[dict]) -> list[dict]:
from esphome.components.light.effects import EFFECTS_REGISTRY
entry = EFFECTS_REGISTRY["addressable_color_wipe"]
return entry.schema({"name": "Wipe", "colors": colors})["colors"]
def test_color_wipe_requires_a_color() -> None:
with pytest.raises(cv.Invalid):
_color_wipe_colors([])
def test_color_wipe_random_entry_limit() -> None:
random_color = {"random": True, "num_leds": 1}
assert len(_color_wipe_colors([random_color] * 254)) == 254
with pytest.raises(cv.Invalid, match="At most 254 colors can be random"):
_color_wipe_colors([random_color] * 255)