[light] Reduce flash memory usage by optimizing validation and color mode logic (#9921)

This commit is contained in:
J. Nick Koston 2025-07-26 18:56:35 -10:00 committed by GitHub
parent e00839a608
commit bcc56648c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 97 additions and 32 deletions

View File

@ -9,6 +9,11 @@ namespace light {
static const char *const TAG = "light"; static const char *const TAG = "light";
// Helper function to reduce code size for validation warnings
static void log_validation_warning(const char *name, const char *param_name, float val, float min, float max) {
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, param_name, val, min, max);
}
// Macro to reduce repetitive setter code // Macro to reduce repetitive setter code
#define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \ #define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \
LightCall &LightCall::set_##name(optional<type>(name)) { \ LightCall &LightCall::set_##name(optional<type>(name)) { \
@ -223,8 +228,7 @@ LightColorValues LightCall::validate_() {
if (this->has_##name_()) { \ if (this->has_##name_()) { \
auto val = this->name_##_; \ auto val = this->name_##_; \
if (val < (min) || val > (max)) { \ if (val < (min) || val > (max)) { \
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \ log_validation_warning(name, LOG_STR_LITERAL(upper_name), val, (min), (max)); \
(min), (max)); \
this->name_##_ = clamp(val, (min), (max)); \ this->name_##_ = clamp(val, (min), (max)); \
} \ } \
} }
@ -442,41 +446,39 @@ std::set<ColorMode> LightCall::get_suitable_color_modes_() {
bool has_rgb = (this->has_color_brightness() && this->color_brightness_ > 0.0f) || bool has_rgb = (this->has_color_brightness() && this->color_brightness_ > 0.0f) ||
(this->has_red() || this->has_green() || this->has_blue()); (this->has_red() || this->has_green() || this->has_blue());
// Build key from flags: [rgb][cwww][ct][white]
#define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3) #define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3)
#define ENTRY(white, ct, cwww, rgb, ...) \
std::make_tuple<uint8_t, std::set<ColorMode>>(KEY(white, ct, cwww, rgb), __VA_ARGS__)
// Flag order: white, color temperature, cwww, rgb uint8_t key = KEY(has_white, has_ct, has_cwww, has_rgb);
std::array<std::tuple<uint8_t, std::set<ColorMode>>, 10> lookup_table{
ENTRY(true, false, false, false,
{ColorMode::WHITE, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
ColorMode::RGB_COLD_WARM_WHITE}),
ENTRY(false, true, false, false,
{ColorMode::COLOR_TEMPERATURE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
ColorMode::RGB_COLD_WARM_WHITE}),
ENTRY(true, true, false, false,
{ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
ENTRY(false, false, true, false, {ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLD_WARM_WHITE}),
ENTRY(false, false, false, false,
{ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE, ColorMode::RGB,
ColorMode::WHITE, ColorMode::COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE}),
ENTRY(true, false, false, true,
{ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
ENTRY(false, true, false, true, {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
ENTRY(true, true, false, true, {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
ENTRY(false, false, true, true, {ColorMode::RGB_COLD_WARM_WHITE}),
ENTRY(false, false, false, true,
{ColorMode::RGB, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
};
auto key = KEY(has_white, has_ct, has_cwww, has_rgb); switch (key) {
for (auto &item : lookup_table) { case KEY(true, false, false, false): // white only
if (std::get<0>(item) == key) return {ColorMode::WHITE, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
return std::get<1>(item); ColorMode::RGB_COLD_WARM_WHITE};
case KEY(false, true, false, false): // ct only
return {ColorMode::COLOR_TEMPERATURE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
ColorMode::RGB_COLD_WARM_WHITE};
case KEY(true, true, false, false): // white + ct
return {ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE};
case KEY(false, false, true, false): // cwww only
return {ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLD_WARM_WHITE};
case KEY(false, false, false, false): // none
return {ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE, ColorMode::RGB,
ColorMode::WHITE, ColorMode::COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE};
case KEY(true, false, false, true): // rgb + white
return {ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE};
case KEY(false, true, false, true): // rgb + ct
case KEY(true, true, false, true): // rgb + white + ct
return {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE};
case KEY(false, false, true, true): // rgb + cwww
return {ColorMode::RGB_COLD_WARM_WHITE};
case KEY(false, false, false, true): // rgb only
return {ColorMode::RGB, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE};
default:
return {}; // conflicting flags
} }
// This happens if there are conflicting flags given. #undef KEY
return {};
} }
LightCall &LightCall::set_effect(const std::string &effect) { LightCall &LightCall::set_effect(const std::string &effect) {

View File

@ -180,6 +180,69 @@ async def test_light_calls(
state = await wait_for_state_change(rgb_light.key) state = await wait_for_state_change(rgb_light.key)
assert state.state is False assert state.state is False
# Test color mode combinations to verify get_suitable_color_modes optimization
# Test 22: White only mode
client.light_command(key=rgbcw_light.key, state=True, white=0.5)
state = await wait_for_state_change(rgbcw_light.key)
assert state.state is True
# Test 23: Color temperature only mode
client.light_command(key=rgbcw_light.key, state=True, color_temperature=300)
state = await wait_for_state_change(rgbcw_light.key)
assert state.color_temperature == pytest.approx(300)
# Test 24: Cold/warm white only mode
client.light_command(
key=rgbcw_light.key, state=True, cold_white=0.6, warm_white=0.4
)
state = await wait_for_state_change(rgbcw_light.key)
assert state.cold_white == pytest.approx(0.6)
assert state.warm_white == pytest.approx(0.4)
# Test 25: RGB only mode
client.light_command(key=rgb_light.key, state=True, rgb=(0.5, 0.5, 0.5))
state = await wait_for_state_change(rgb_light.key)
assert state.state is True
# Test 26: RGB + white combination
client.light_command(
key=rgbcw_light.key, state=True, rgb=(0.3, 0.3, 0.3), white=0.5
)
state = await wait_for_state_change(rgbcw_light.key)
assert state.state is True
# Test 27: RGB + color temperature combination
client.light_command(
key=rgbcw_light.key, state=True, rgb=(0.4, 0.4, 0.4), color_temperature=280
)
state = await wait_for_state_change(rgbcw_light.key)
assert state.state is True
# Test 28: RGB + cold/warm white combination
client.light_command(
key=rgbcw_light.key,
state=True,
rgb=(0.2, 0.2, 0.2),
cold_white=0.5,
warm_white=0.5,
)
state = await wait_for_state_change(rgbcw_light.key)
assert state.state is True
# Test 29: White + color temperature combination
client.light_command(
key=rgbcw_light.key, state=True, white=0.6, color_temperature=320
)
state = await wait_for_state_change(rgbcw_light.key)
assert state.state is True
# Test 30: No specific color parameters (tests default mode selection)
client.light_command(key=rgbcw_light.key, state=True, brightness=0.75)
state = await wait_for_state_change(rgbcw_light.key)
assert state.state is True
assert state.brightness == pytest.approx(0.75)
# Final cleanup - turn all lights off # Final cleanup - turn all lights off
for light in lights: for light in lights:
client.light_command( client.light_command(