[light] Eliminate static-local guard variables by hoisting names to PROGMEM_STRING_TABLE

The PROGMEM_STRING_TABLE expansion is entirely constexpr-initialized, so it doesn't require the per-static thread-safe-init guard variables that the previous `static const LogString *const FIELD_NAMES[8] PROGMEM = { LOG_STR(...) }` form emitted inside validate_() (LOG_STR contains a GCC statement-expression, which is not a constant expression, so GCC fell back to dynamic init with an 8-byte guard).

Combined the 8 clamp-field names and "Color temperature" into a single ValidateFieldNames table; index 8 is reserved for CT. log_value_out_of_range_() now takes the resolved `const LogString *` directly, so the helper no longer needs its internal progmem_read_ptr dereference.

Impact vs. prior commit (isolated light build):

  ESP8266 .irom0.text: 267912 -> 267784  (-128 B)

  ESP8266 .bss guard vars on light symbols: 16 -> 0 (-16 B RAM)

  ESP32-IDF .flash.text: 136988 -> 136996 (+8 B, marginal)
This commit is contained in:
J. Nick Koston
2026-04-13 16:38:44 -10:00
parent de44b8e859
commit bdd1c413de
+21 -20
View File
@@ -12,12 +12,8 @@ namespace esphome::light {
static const char *const TAG = "light";
// Cold-path logger. Caller handles the clamp so the in-range hot path avoids
// the call-site spill/reload. `param_name_progmem` is a pointer into FIELD_NAMES
// in flash; the `progmem_read_ptr` is a no-op on non-ESP8266.
static void log_value_out_of_range_(const char *name, float value, const LogString *const *param_name_progmem,
float min, float max) {
const auto *param_name =
reinterpret_cast<const LogString *>(progmem_read_ptr(reinterpret_cast<const char *const *>(param_name_progmem)));
// the call-site spill/reload around the out-of-line call.
static void log_value_out_of_range_(const char *name, float value, const LogString *param_name, float min, float max) {
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_ARG(param_name), value, min, max);
}
@@ -59,6 +55,22 @@ static void log_invalid_parameter(const char *name, const LogString *message) {
PROGMEM_STRING_TABLE(ColorModeHumanStrings, "Unknown", "On/Off", "Brightness", "White", "Color temperature",
"Cold/warm white", "RGB", "RGBW", "RGB + color temperature", "RGB + cold/warm white");
// Field names for validate_(). PROGMEM_STRING_TABLE uses constexpr init so no
// per-static guard variables in RAM (a plain LOG_STR array of static locals
// would need them because LOG_STR is a statement-expression on ESP8266).
// Indices 0-7 match FieldFlags bits 0-7; index 8 is color_temperature.
PROGMEM_STRING_TABLE(ValidateFieldNames,
"Brightness", // FLAG_HAS_BRIGHTNESS (bit 0)
"Color brightness", // FLAG_HAS_COLOR_BRIGHTNESS (bit 1)
"Red", // FLAG_HAS_RED (bit 2)
"Green", // FLAG_HAS_GREEN (bit 3)
"Blue", // FLAG_HAS_BLUE (bit 4)
"White", // FLAG_HAS_WHITE (bit 5)
"Cold white", // FLAG_HAS_COLD_WHITE (bit 6)
"Warm white", // FLAG_HAS_WARM_WHITE (bit 7)
"Color temperature");
static constexpr uint8_t VALIDATE_CT_INDEX = 8;
static const LogString *color_mode_to_human(ColorMode color_mode) {
return ColorModeHumanStrings::get_log_str(ColorModeBitPolicy::to_bit(color_mode), 0);
}
@@ -306,17 +318,6 @@ LightColorValues LightCall::validate_() {
ESPHOME_LIGHT_ASSERT_CLAMP_FIELD(7, WARM_WHITE, warm_white_);
#undef ESPHOME_LIGHT_ASSERT_CLAMP_FIELD
static const LogString *const FIELD_NAMES[8] PROGMEM = {
LOG_STR("Brightness"), // FLAG_HAS_BRIGHTNESS (bit 0)
LOG_STR("Color brightness"), // FLAG_HAS_COLOR_BRIGHTNESS (bit 1)
LOG_STR("Red"), // FLAG_HAS_RED (bit 2)
LOG_STR("Green"), // FLAG_HAS_GREEN (bit 3)
LOG_STR("Blue"), // FLAG_HAS_BLUE (bit 4)
LOG_STR("White"), // FLAG_HAS_WHITE (bit 5)
LOG_STR("Cold white"), // FLAG_HAS_COLD_WHITE (bit 6)
LOG_STR("Warm white"), // FLAG_HAS_WARM_WHITE (bit 7)
};
// Iterate only the set bits (ctz + clear-lowest) so the hot path is
// O(popcount) — HA can drive perform() at high frequency.
float *const src_fields = &this->brightness_;
@@ -327,7 +328,7 @@ LightColorValues LightCall::validate_() {
active &= active - 1; // clear lowest set bit
float &value = src_fields[bit];
if (float_out_of_unit_range(value)) {
log_value_out_of_range_(name, value, &FIELD_NAMES[bit], 0.0f, 1.0f);
log_value_out_of_range_(name, value, ValidateFieldNames::get_log_str(bit, 0), 0.0f, 1.0f);
value = clamp_unit_float(value);
}
dst_fields[bit] = value;
@@ -335,11 +336,11 @@ LightColorValues LightCall::validate_() {
// color_temperature has a runtime range from traits — no bit-pattern shortcut.
if (this->has_color_temperature()) {
static const LogString *const CT_NAME PROGMEM = LOG_STR("Color temperature");
const float ct_min = traits.get_min_mireds();
const float ct_max = traits.get_max_mireds();
if (this->color_temperature_ < ct_min || this->color_temperature_ > ct_max) {
log_value_out_of_range_(name, this->color_temperature_, &CT_NAME, ct_min, ct_max);
log_value_out_of_range_(name, this->color_temperature_, ValidateFieldNames::get_log_str(VALIDATE_CT_INDEX, 0),
ct_min, ct_max);
this->color_temperature_ = clamp(this->color_temperature_, ct_min, ct_max);
}
v.color_temperature_ = this->color_temperature_;