light effect to uint32_t

This commit is contained in:
J. Nick Koston
2026-02-24 20:38:33 -06:00
parent 3460a8c922
commit ae296af582
4 changed files with 84 additions and 3 deletions

View File

@@ -41,7 +41,7 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
TEMPLATABLE_VALUE(float, color_temperature)
TEMPLATABLE_VALUE(float, cold_white)
TEMPLATABLE_VALUE(float, warm_white)
TEMPLATABLE_VALUE(std::string, effect)
TEMPLATABLE_VALUE(uint32_t, effect)
void play(const Ts &...x) override {
auto call = this->parent_->make_call();

View File

@@ -10,12 +10,14 @@ from esphome.const import (
CONF_COLOR_MODE,
CONF_COLOR_TEMPERATURE,
CONF_EFFECT,
CONF_EFFECTS,
CONF_FLASH_LENGTH,
CONF_GREEN,
CONF_ID,
CONF_LIMIT_MODE,
CONF_MAX_BRIGHTNESS,
CONF_MIN_BRIGHTNESS,
CONF_NAME,
CONF_RANGE_FROM,
CONF_RANGE_TO,
CONF_RED,
@@ -24,6 +26,8 @@ from esphome.const import (
CONF_WARM_WHITE,
CONF_WHITE,
)
from esphome.core import CORE, Lambda
from esphome.cpp_generator import LambdaExpression
from .types import (
COLOR_MODES,
@@ -110,6 +114,25 @@ LIGHT_TURN_ON_ACTION_SCHEMA = automation.maybe_simple_id(
)
def _resolve_effect_index(config):
"""Resolve a static effect name to its 1-based index at codegen time.
Effect index 0 means "None" (no effect). Effects are 1-indexed matching
the C++ convention in LightState.
"""
effect_name = config[CONF_EFFECT]
if effect_name.lower() == "none":
return 0
light_id = config[CONF_ID]
light_path = CORE.config.get_path_for_id(light_id)[:-1]
light_config = CORE.config.get_config_for_path(light_path)
for i, effect_conf in enumerate(light_config.get(CONF_EFFECTS, [])):
key = next(iter(effect_conf))
if effect_conf[key][CONF_NAME].lower() == effect_name.lower():
return i + 1
raise ValueError(f"Effect '{effect_name}' not found in light '{light_id}'")
@automation.register_action(
"light.turn_off", LightControlAction, LIGHT_TURN_OFF_ACTION_SCHEMA
)
@@ -164,8 +187,26 @@ async def light_control_to_code(config, action_id, template_arg, args):
template_ = await cg.templatable(config[CONF_WARM_WHITE], args, float)
cg.add(var.set_warm_white(template_))
if CONF_EFFECT in config:
template_ = await cg.templatable(config[CONF_EFFECT], args, cg.std_string)
cg.add(var.set_effect(template_))
if isinstance(config[CONF_EFFECT], Lambda):
# Lambda returns a string — wrap in a C++ lambda that resolves
# the effect name to its uint32_t index at runtime
inner_lambda = await cg.process_lambda(
config[CONF_EFFECT], args, return_type=cg.std_string
)
fwd_args = ", ".join(n for _, n in args)
wrapper = LambdaExpression(
f"auto __effect_s = ({inner_lambda})({fwd_args});\n"
f"return {paren}->get_effect_index("
f"__effect_s.c_str(), __effect_s.size());",
args,
capture="",
return_type=cg.uint32,
)
cg.add(var.set_effect(wrapper))
else:
# Static string — resolve effect name to index at codegen time
effect_index = _resolve_effect_index(config)
cg.add(var.set_effect(effect_index))
return var

View File

@@ -200,6 +200,20 @@ class LightState : public EntityBase, public Component {
return 0; // Effect not found
}
/// Get effect index by name (const char* overload, avoids std::string construction).
uint32_t get_effect_index(const char *name, size_t len) const {
StringRef ref(name, len);
if (str_equals_case_insensitive(ref, StringRef("none", 4))) {
return 0;
}
for (size_t i = 0; i < this->effects_.size(); i++) {
if (str_equals_case_insensitive(ref, this->effects_[i]->get_name())) {
return i + 1;
}
}
return 0;
}
/// Get effect by index. Returns nullptr if index is invalid.
LightEffect *get_effect_by_index(uint32_t index) const {
if (index == 0 || index > this->effects_.size()) {

View File

@@ -71,6 +71,32 @@ esphome:
- light.control:
id: test_monochromatic_light
state: on
# Test static effect name resolution at codegen time
- light.turn_on:
id: test_monochromatic_light
effect: Strobe
- light.turn_on:
id: test_monochromatic_light
effect: none
# Test resolving a different effect on the same light
- light.control:
id: test_monochromatic_light
effect: My Flicker
# Test effect: None (capitalized)
- light.control:
id: test_monochromatic_light
effect: None
# Test effect lambda with no args (on_boot has empty Ts...)
- light.turn_on:
id: test_monochromatic_light
effect: !lambda 'return "Strobe";'
# Test effect lambda with non-empty args (repeat passes uint32_t iteration)
- repeat:
count: 3
then:
- light.turn_on:
id: test_monochromatic_light
effect: !lambda 'return iteration > 1 ? "Strobe" : "none";'
- light.dim_relative:
id: test_monochromatic_light
relative_brightness: 5%