mirror of
https://github.com/esphome/esphome.git
synced 2026-05-31 17:06:40 +08:00
[light] Reduce validate_ clamp code size and speed up unit-range clamps (#15728)
This commit is contained in:
@@ -10,13 +10,10 @@ namespace esphome::light {
|
||||
|
||||
static const char *const TAG = "light";
|
||||
|
||||
// Helper functions to reduce code size for logging
|
||||
static void clamp_and_log_if_invalid(const char *name, float &value, const LogString *param_name, float min = 0.0f,
|
||||
float max = 1.0f) {
|
||||
if (value < min || value > max) {
|
||||
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_ARG(param_name), value, min, max);
|
||||
value = clamp(value, min, max);
|
||||
}
|
||||
// Cold-path logger; caller handles the clamp so the in-range hot path avoids
|
||||
// the spill/reload around the 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);
|
||||
}
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
|
||||
@@ -57,6 +54,12 @@ 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");
|
||||
|
||||
// Indices 0-7 match FieldFlags bits 0-7; index 8 is color_temperature.
|
||||
// PROGMEM_STRING_TABLE is constexpr-init (no RAM guard variable).
|
||||
PROGMEM_STRING_TABLE(ValidateFieldNames, "Brightness", "Color brightness", "Red", "Green", "Blue", "White",
|
||||
"Cold white", "Warm white", "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);
|
||||
}
|
||||
@@ -277,25 +280,37 @@ LightColorValues LightCall::validate_() {
|
||||
if (this->has_state())
|
||||
v.set_state(this->state_);
|
||||
|
||||
// clamp_and_log_if_invalid already clamps in-place, so assign directly
|
||||
// to avoid redundant clamp code from the setter being inlined.
|
||||
#define VALIDATE_AND_APPLY(field, name_str, ...) \
|
||||
if (this->has_##field()) { \
|
||||
clamp_and_log_if_invalid(name, this->field##_, LOG_STR(name_str), ##__VA_ARGS__); \
|
||||
v.field##_ = this->field##_; \
|
||||
// FieldFlags bits 0-7 must match unit_fields_ array indices.
|
||||
static_assert(FLAG_HAS_BRIGHTNESS == 1u << 0 && FLAG_HAS_COLOR_BRIGHTNESS == 1u << 1 && FLAG_HAS_RED == 1u << 2 &&
|
||||
FLAG_HAS_GREEN == 1u << 3 && FLAG_HAS_BLUE == 1u << 4 && FLAG_HAS_WHITE == 1u << 5 &&
|
||||
FLAG_HAS_COLD_WHITE == 1u << 6 && FLAG_HAS_WARM_WHITE == 1u << 7,
|
||||
"FieldFlags bits 0-7 must match unit_fields_ indices");
|
||||
|
||||
// Iterate set bits only (ctz + clear-lowest) — HA can drive perform()
|
||||
// at high frequency so the hot path is O(popcount).
|
||||
unsigned active = this->flags_ & CLAMP_FLAGS_MASK;
|
||||
while (active != 0) {
|
||||
unsigned bit = __builtin_ctz(active);
|
||||
active &= active - 1; // clear lowest set bit
|
||||
float &value = this->unit_fields_[bit];
|
||||
if (float_out_of_unit_range(value)) {
|
||||
log_value_out_of_range(name, value, ValidateFieldNames::get_log_str(bit, 0), 0.0f, 1.0f);
|
||||
value = clamp_unit_float(value);
|
||||
}
|
||||
v.unit_fields_[bit] = value;
|
||||
}
|
||||
|
||||
VALIDATE_AND_APPLY(brightness, "Brightness")
|
||||
VALIDATE_AND_APPLY(color_brightness, "Color brightness")
|
||||
VALIDATE_AND_APPLY(red, "Red")
|
||||
VALIDATE_AND_APPLY(green, "Green")
|
||||
VALIDATE_AND_APPLY(blue, "Blue")
|
||||
VALIDATE_AND_APPLY(white, "White")
|
||||
VALIDATE_AND_APPLY(cold_white, "Cold white")
|
||||
VALIDATE_AND_APPLY(warm_white, "Warm white")
|
||||
VALIDATE_AND_APPLY(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds())
|
||||
|
||||
#undef VALIDATE_AND_APPLY
|
||||
// color_temperature: runtime range from traits.
|
||||
if (this->has_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_, 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_;
|
||||
}
|
||||
|
||||
v.normalize_color();
|
||||
|
||||
|
||||
@@ -195,25 +195,26 @@ class LightCall {
|
||||
/// Some color modes also can be set using non-native parameters, transform those calls.
|
||||
void transform_parameters_(const LightTraits &traits);
|
||||
|
||||
// Bitfield flags - each flag indicates whether a corresponding value has been set.
|
||||
// Bits 0-7 index unit_fields_[] in validate_(); don't reorder (asserts in light_call.cpp).
|
||||
enum FieldFlags : uint16_t {
|
||||
FLAG_HAS_STATE = 1 << 0,
|
||||
FLAG_HAS_TRANSITION = 1 << 1,
|
||||
FLAG_HAS_FLASH = 1 << 2,
|
||||
FLAG_HAS_EFFECT = 1 << 3,
|
||||
FLAG_HAS_BRIGHTNESS = 1 << 4,
|
||||
FLAG_HAS_COLOR_BRIGHTNESS = 1 << 5,
|
||||
FLAG_HAS_RED = 1 << 6,
|
||||
FLAG_HAS_GREEN = 1 << 7,
|
||||
FLAG_HAS_BLUE = 1 << 8,
|
||||
FLAG_HAS_WHITE = 1 << 9,
|
||||
FLAG_HAS_COLOR_TEMPERATURE = 1 << 10,
|
||||
FLAG_HAS_COLD_WHITE = 1 << 11,
|
||||
FLAG_HAS_WARM_WHITE = 1 << 12,
|
||||
FLAG_HAS_BRIGHTNESS = 1 << 0,
|
||||
FLAG_HAS_COLOR_BRIGHTNESS = 1 << 1,
|
||||
FLAG_HAS_RED = 1 << 2,
|
||||
FLAG_HAS_GREEN = 1 << 3,
|
||||
FLAG_HAS_BLUE = 1 << 4,
|
||||
FLAG_HAS_WHITE = 1 << 5,
|
||||
FLAG_HAS_COLD_WHITE = 1 << 6,
|
||||
FLAG_HAS_WARM_WHITE = 1 << 7,
|
||||
FLAG_HAS_COLOR_TEMPERATURE = 1 << 8,
|
||||
FLAG_HAS_STATE = 1 << 9,
|
||||
FLAG_HAS_TRANSITION = 1 << 10,
|
||||
FLAG_HAS_FLASH = 1 << 11,
|
||||
FLAG_HAS_EFFECT = 1 << 12,
|
||||
FLAG_HAS_COLOR_MODE = 1 << 13,
|
||||
FLAG_PUBLISH = 1 << 14,
|
||||
FLAG_SAVE = 1 << 15,
|
||||
};
|
||||
static constexpr uint16_t CLAMP_FLAGS_MASK = 0x00FFu; // bits 0-7
|
||||
|
||||
inline bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; }
|
||||
inline bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; }
|
||||
@@ -239,19 +240,11 @@ class LightCall {
|
||||
LightState *parent_;
|
||||
|
||||
// Light state values - use flags_ to check if a value has been set.
|
||||
// Group 4-byte aligned members first
|
||||
uint32_t transition_length_;
|
||||
uint32_t flash_length_;
|
||||
uint32_t effect_;
|
||||
float brightness_;
|
||||
float color_brightness_;
|
||||
float red_;
|
||||
float green_;
|
||||
float blue_;
|
||||
float white_;
|
||||
ESPHOME_LIGHT_UNIT_FIELDS_UNION();
|
||||
float color_temperature_;
|
||||
float cold_white_;
|
||||
float warm_white_;
|
||||
|
||||
// Smaller members at the end for better packing
|
||||
uint16_t flags_{FLAG_PUBLISH | FLAG_SAVE}; // Tracks which values are set
|
||||
|
||||
@@ -3,11 +3,62 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "color_mode.h"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace esphome::light {
|
||||
|
||||
inline static uint8_t to_uint8_scale(float x) { return static_cast<uint8_t>(roundf(x * 255.0f)); }
|
||||
|
||||
// IEEE 754 bit patterns. Values in [0.0f, 1.0f] have bits <= ONE_F_BITS;
|
||||
// negatives have the sign bit set (→ huge unsigned). A single unsigned compare
|
||||
// replaces two soft-float __ltsf2/__gtsf2 calls on ESP8266.
|
||||
static constexpr uint32_t ONE_F_BITS = 0x3F800000u; // 1.0f
|
||||
static constexpr uint32_t NEG_ZERO_F_BITS = 0x80000000u; // -0.0f / sign-bit mask
|
||||
static_assert(sizeof(float) == sizeof(uint32_t), "float must be 32-bit");
|
||||
static_assert(std::numeric_limits<float>::is_iec559, "IEEE 754 float required");
|
||||
|
||||
// Union pun — memcpy/bit_cast don't fold on xtensa-gcc (see api/proto.h).
|
||||
// -0.0f is numerically zero so it's reported in range (no warning, no clamp).
|
||||
inline bool float_out_of_unit_range(float x) {
|
||||
union {
|
||||
float f;
|
||||
uint32_t u;
|
||||
} pun;
|
||||
pun.f = x;
|
||||
return pun.u > ONE_F_BITS && pun.u != NEG_ZERO_F_BITS;
|
||||
}
|
||||
|
||||
// Clamps to [0.0f, 1.0f] without float compares. Out of range: sign bit set
|
||||
// (negatives, -NaN, -Inf) → 0.0f; sign bit clear (>1, +NaN, +Inf) → 1.0f.
|
||||
inline float clamp_unit_float(float x) {
|
||||
union {
|
||||
float f;
|
||||
uint32_t u;
|
||||
} pun;
|
||||
pun.f = x;
|
||||
if (pun.u <= ONE_F_BITS)
|
||||
return x;
|
||||
return (pun.u & NEG_ZERO_F_BITS) ? 0.0f : 1.0f; // sign bit → negative → clamp to 0
|
||||
}
|
||||
|
||||
// Shared anonymous union: eight unit-range floats alias unit_fields_[8] so
|
||||
// LightCall::validate_() can iterate them as a real array. GCC/Clang ext.
|
||||
#define ESPHOME_LIGHT_UNIT_FIELDS_UNION() \
|
||||
union { \
|
||||
struct { \
|
||||
float brightness_; \
|
||||
float color_brightness_; \
|
||||
float red_; \
|
||||
float green_; \
|
||||
float blue_; \
|
||||
float white_; \
|
||||
float cold_white_; \
|
||||
float warm_white_; \
|
||||
}; \
|
||||
float unit_fields_[8]; \
|
||||
}
|
||||
|
||||
/** This class represents the color state for a light object.
|
||||
*
|
||||
* The representation of the color state is dependent on the active color mode. A color mode consists of multiple
|
||||
@@ -52,9 +103,9 @@ class LightColorValues {
|
||||
green_(1.0f),
|
||||
blue_(1.0f),
|
||||
white_(1.0f),
|
||||
color_temperature_{0.0f},
|
||||
cold_white_{1.0f},
|
||||
warm_white_{1.0f},
|
||||
color_temperature_{0.0f},
|
||||
color_mode_(ColorMode::UNKNOWN) {}
|
||||
|
||||
LightColorValues(ColorMode color_mode, float state, float brightness, float color_brightness, float red, float green,
|
||||
@@ -220,39 +271,39 @@ class LightColorValues {
|
||||
/// Get the binary true/false state of these light color values.
|
||||
bool is_on() const { return this->get_state() != 0.0f; }
|
||||
/// Set the state of these light color values. In range from 0.0 (off) to 1.0 (on)
|
||||
void set_state(float state) { this->state_ = clamp(state, 0.0f, 1.0f); }
|
||||
void set_state(float state) { this->state_ = clamp_unit_float(state); }
|
||||
/// Set the state of these light color values as a binary true/false.
|
||||
void set_state(bool state) { this->state_ = state ? 1.0f : 0.0f; }
|
||||
|
||||
/// Get the brightness property of these light color values. In range 0.0 to 1.0
|
||||
float get_brightness() const { return this->brightness_; }
|
||||
/// Set the brightness property of these light color values. In range 0.0 to 1.0
|
||||
void set_brightness(float brightness) { this->brightness_ = clamp(brightness, 0.0f, 1.0f); }
|
||||
void set_brightness(float brightness) { this->brightness_ = clamp_unit_float(brightness); }
|
||||
|
||||
/// Get the color brightness property of these light color values. In range 0.0 to 1.0
|
||||
float get_color_brightness() const { return this->color_brightness_; }
|
||||
/// Set the color brightness property of these light color values. In range 0.0 to 1.0
|
||||
void set_color_brightness(float brightness) { this->color_brightness_ = clamp(brightness, 0.0f, 1.0f); }
|
||||
void set_color_brightness(float brightness) { this->color_brightness_ = clamp_unit_float(brightness); }
|
||||
|
||||
/// Get the red property of these light color values. In range 0.0 to 1.0
|
||||
float get_red() const { return this->red_; }
|
||||
/// Set the red property of these light color values. In range 0.0 to 1.0
|
||||
void set_red(float red) { this->red_ = clamp(red, 0.0f, 1.0f); }
|
||||
void set_red(float red) { this->red_ = clamp_unit_float(red); }
|
||||
|
||||
/// Get the green property of these light color values. In range 0.0 to 1.0
|
||||
float get_green() const { return this->green_; }
|
||||
/// Set the green property of these light color values. In range 0.0 to 1.0
|
||||
void set_green(float green) { this->green_ = clamp(green, 0.0f, 1.0f); }
|
||||
void set_green(float green) { this->green_ = clamp_unit_float(green); }
|
||||
|
||||
/// Get the blue property of these light color values. In range 0.0 to 1.0
|
||||
float get_blue() const { return this->blue_; }
|
||||
/// Set the blue property of these light color values. In range 0.0 to 1.0
|
||||
void set_blue(float blue) { this->blue_ = clamp(blue, 0.0f, 1.0f); }
|
||||
void set_blue(float blue) { this->blue_ = clamp_unit_float(blue); }
|
||||
|
||||
/// Get the white property of these light color values. In range 0.0 to 1.0
|
||||
float get_white() const { return white_; }
|
||||
/// Set the white property of these light color values. In range 0.0 to 1.0
|
||||
void set_white(float white) { this->white_ = clamp(white, 0.0f, 1.0f); }
|
||||
void set_white(float white) { this->white_ = clamp_unit_float(white); }
|
||||
|
||||
/// Get the color temperature property of these light color values in mired.
|
||||
float get_color_temperature() const { return this->color_temperature_; }
|
||||
@@ -277,26 +328,19 @@ class LightColorValues {
|
||||
/// Get the cold white property of these light color values. In range 0.0 to 1.0.
|
||||
float get_cold_white() const { return this->cold_white_; }
|
||||
/// Set the cold white property of these light color values. In range 0.0 to 1.0.
|
||||
void set_cold_white(float cold_white) { this->cold_white_ = clamp(cold_white, 0.0f, 1.0f); }
|
||||
void set_cold_white(float cold_white) { this->cold_white_ = clamp_unit_float(cold_white); }
|
||||
|
||||
/// Get the warm white property of these light color values. In range 0.0 to 1.0.
|
||||
float get_warm_white() const { return this->warm_white_; }
|
||||
/// Set the warm white property of these light color values. In range 0.0 to 1.0.
|
||||
void set_warm_white(float warm_white) { this->warm_white_ = clamp(warm_white, 0.0f, 1.0f); }
|
||||
void set_warm_white(float warm_white) { this->warm_white_ = clamp_unit_float(warm_white); }
|
||||
|
||||
friend class LightCall;
|
||||
|
||||
protected:
|
||||
float state_; ///< ON / OFF, float for transition
|
||||
float brightness_;
|
||||
float color_brightness_;
|
||||
float red_;
|
||||
float green_;
|
||||
float blue_;
|
||||
float white_;
|
||||
ESPHOME_LIGHT_UNIT_FIELDS_UNION();
|
||||
float color_temperature_; ///< Color Temperature in Mired
|
||||
float cold_white_;
|
||||
float warm_white_;
|
||||
ColorMode color_mode_;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user