1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-23 12:13:49 +01:00
This commit is contained in:
J. Nick Koston
2025-10-18 13:05:59 -10:00
parent 3ef402ef64
commit 89903929f3
3 changed files with 96 additions and 66 deletions

View File

@@ -104,6 +104,9 @@ constexpr ColorModeHelper operator|(ColorModeHelper lhs, ColorMode rhs) {
return static_cast<ColorMode>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
}
// Type alias for raw color mode bitmask values
using color_mode_bitmask_t = uint16_t;
/// Bitmask for storing a set of ColorMode values efficiently.
/// Replaces std::set<ColorMode> to eliminate red-black tree overhead (~586 bytes).
class ColorModeMask {
@@ -143,7 +146,7 @@ class ColorModeMask {
using pointer = const ColorMode *;
using reference = ColorMode;
constexpr Iterator(uint16_t mask, int bit) : mask_(mask), bit_(bit) { advance_to_next_set_bit_(); }
constexpr Iterator(color_mode_bitmask_t mask, int bit) : mask_(mask), bit_(bit) { advance_to_next_set_bit_(); }
constexpr ColorMode operator*() const { return bit_to_mode(bit_); }
@@ -159,52 +162,92 @@ class ColorModeMask {
private:
constexpr void advance_to_next_set_bit_() {
while (bit_ < 16 && !(mask_ & (1 << bit_))) {
while (bit_ < MAX_BIT_INDEX && !(mask_ & (1 << bit_))) {
++bit_;
}
}
uint16_t mask_;
color_mode_bitmask_t mask_;
int bit_;
};
constexpr Iterator begin() const { return Iterator(mask_, 0); }
constexpr Iterator end() const { return Iterator(mask_, 16); }
constexpr Iterator end() const { return Iterator(mask_, MAX_BIT_INDEX); }
/// Get the raw bitmask value for API encoding
constexpr uint16_t get_mask() const { return this->mask_; }
constexpr color_mode_bitmask_t get_mask() const { return this->mask_; }
/// Find the first set bit in a bitmask and return the corresponding ColorMode
/// Used for optimizing compute_color_mode_() intersection logic
static constexpr ColorMode first_mode_from_mask(color_mode_bitmask_t mask) {
// Find the position of the first set bit (least significant bit)
int bit = 0;
while (bit < MAX_BIT_INDEX && !(mask & (1 << bit))) {
++bit;
}
return bit_to_mode(bit);
}
/// Check if a ColorMode is present in a raw bitmask value
/// Useful for checking intersection results without creating a temporary ColorModeMask
static constexpr bool mask_contains(color_mode_bitmask_t mask, ColorMode mode) {
return (mask & (1 << mode_to_bit(mode))) != 0;
}
/// Build a bitmask of modes that match the given capability requirements
/// @param require_caps Capabilities that must be present in the mode
/// @param exclude_caps Capabilities that must not be present in the mode (for none case)
/// @return Raw bitmask value
static constexpr color_mode_bitmask_t build_mask_matching(uint8_t require_caps, uint8_t exclude_caps = 0) {
color_mode_bitmask_t mask = 0;
// Check each mode to see if it matches the requirements
// Skip UNKNOWN (bit 0), iterate through actual color modes (bits 1-9)
for (int bit = 1; bit < COLOR_MODE_COUNT; ++bit) {
ColorMode mode = bit_to_mode(bit);
uint8_t mode_val = static_cast<uint8_t>(mode);
// Mode matches if it has all required caps and none of the excluded caps
if ((mode_val & require_caps) == require_caps && (exclude_caps == 0 || (mode_val & exclude_caps) == 0)) {
mask |= (1 << bit);
}
}
return mask;
}
private:
// Using uint16_t instead of uint32_t for more efficient iteration (fewer bits to scan).
// Currently only 10 ColorMode values exist, so 16 bits is sufficient.
// Can be changed to uint32_t if more than 16 color modes are needed in the future.
// Note: Due to struct padding, uint16_t and uint32_t result in same LightTraits size (12 bytes).
uint16_t mask_{0};
color_mode_bitmask_t mask_{0};
// Constants for ColorMode count and bit range
static constexpr int COLOR_MODE_COUNT = 10; // UNKNOWN through RGB_COLD_WARM_WHITE
static constexpr int MAX_BIT_INDEX = sizeof(color_mode_bitmask_t) * 8; // Number of bits in bitmask type
/// Map ColorMode enum values to bit positions (0-9)
static constexpr int mode_to_bit(ColorMode mode) {
// Using switch instead of lookup table to avoid RAM usage on ESP8266
// The compiler optimizes this efficiently
switch (mode) {
case ColorMode::UNKNOWN:
case ColorMode::UNKNOWN: // 0
return 0;
case ColorMode::ON_OFF:
case ColorMode::ON_OFF: // 1
return 1;
case ColorMode::BRIGHTNESS:
case ColorMode::BRIGHTNESS: // 3
return 2;
case ColorMode::WHITE:
case ColorMode::WHITE: // 7
return 3;
case ColorMode::COLOR_TEMPERATURE:
case ColorMode::COLOR_TEMPERATURE: // 11
return 4;
case ColorMode::COLD_WARM_WHITE:
case ColorMode::COLD_WARM_WHITE: // 19
return 5;
case ColorMode::RGB:
case ColorMode::RGB: // 35
return 6;
case ColorMode::RGB_WHITE:
case ColorMode::RGB_WHITE: // 39
return 7;
case ColorMode::RGB_COLOR_TEMPERATURE:
case ColorMode::RGB_COLOR_TEMPERATURE: // 47
return 8;
case ColorMode::RGB_COLD_WARM_WHITE:
case ColorMode::RGB_COLD_WARM_WHITE: // 51
return 9;
default:
return 0;
@@ -215,25 +258,25 @@ class ColorModeMask {
// Using switch instead of lookup table to avoid RAM usage on ESP8266
switch (bit) {
case 0:
return ColorMode::UNKNOWN;
return ColorMode::UNKNOWN; // 0
case 1:
return ColorMode::ON_OFF;
return ColorMode::ON_OFF; // 1
case 2:
return ColorMode::BRIGHTNESS;
return ColorMode::BRIGHTNESS; // 3
case 3:
return ColorMode::WHITE;
return ColorMode::WHITE; // 7
case 4:
return ColorMode::COLOR_TEMPERATURE;
return ColorMode::COLOR_TEMPERATURE; // 11
case 5:
return ColorMode::COLD_WARM_WHITE;
return ColorMode::COLD_WARM_WHITE; // 19
case 6:
return ColorMode::RGB;
return ColorMode::RGB; // 35
case 7:
return ColorMode::RGB_WHITE;
return ColorMode::RGB_WHITE; // 39
case 8:
return ColorMode::RGB_COLOR_TEMPERATURE;
return ColorMode::RGB_COLOR_TEMPERATURE; // 47
case 9:
return ColorMode::RGB_COLD_WARM_WHITE;
return ColorMode::RGB_COLD_WARM_WHITE; // 51
default:
return ColorMode::UNKNOWN;
}

View File

@@ -425,20 +425,19 @@ ColorMode LightCall::compute_color_mode_() {
// If no color mode is specified, we try to guess the color mode. This is needed for backward compatibility to
// pre-colormode clients and automations, but also for the MQTT API, where HA doesn't let us know which color mode
// was used for some reason.
ColorModeMask suitable_modes = this->get_suitable_color_modes_();
// Compute intersection of suitable and supported modes using bitwise AND
color_mode_bitmask_t intersection = this->get_suitable_color_modes_mask_() & supported_modes.get_mask();
// Don't change if the current mode is suitable.
if (suitable_modes.contains(current_mode)) {
// Don't change if the current mode is in the intersection (suitable AND supported)
if (ColorModeMask::mask_contains(intersection, current_mode)) {
ESP_LOGI(TAG, "'%s': color mode not specified; retaining %s", this->parent_->get_name().c_str(),
LOG_STR_ARG(color_mode_to_human(current_mode)));
return current_mode;
}
// Use the preferred suitable mode.
for (auto mode : suitable_modes) {
if (!supported_modes.contains(mode))
continue;
if (intersection != 0) {
ColorMode mode = ColorModeMask::first_mode_from_mask(intersection);
ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(),
LOG_STR_ARG(color_mode_to_human(mode)));
return mode;
@@ -451,7 +450,7 @@ ColorMode LightCall::compute_color_mode_() {
LOG_STR_ARG(color_mode_to_human(color_mode)));
return color_mode;
}
ColorModeMask LightCall::get_suitable_color_modes_() {
color_mode_bitmask_t LightCall::get_suitable_color_modes_mask_() {
bool has_white = this->has_white() && this->white_ > 0.0f;
bool has_ct = this->has_color_temperature();
bool has_cwww =
@@ -459,39 +458,27 @@ ColorModeMask LightCall::get_suitable_color_modes_() {
bool has_rgb = (this->has_color_brightness() && this->color_brightness_ > 0.0f) ||
(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)
// Build required capabilities mask
uint8_t require_caps = static_cast<uint8_t>(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS);
if (has_rgb)
require_caps |= static_cast<uint8_t>(ColorCapability::RGB);
if (has_white)
require_caps |= static_cast<uint8_t>(ColorCapability::WHITE);
if (has_ct)
require_caps |= static_cast<uint8_t>(ColorCapability::COLOR_TEMPERATURE);
if (has_cwww)
require_caps |= static_cast<uint8_t>(ColorCapability::COLD_WARM_WHITE);
uint8_t key = KEY(has_white, has_ct, has_cwww, has_rgb);
switch (key) {
case KEY(true, false, false, false): // white only
return {ColorMode::WHITE, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
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
// If no specific color parameters set, exclude modes with color capabilities
uint8_t exclude_caps = 0;
if (!has_rgb && !has_white && !has_ct && !has_cwww) {
// For "none" case, we want all modes but don't exclude anything
// Just require ON_OFF + BRIGHTNESS which all modes have
return ColorModeMask::build_mask_matching(
static_cast<uint8_t>(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS), exclude_caps);
}
#undef KEY
return ColorModeMask::build_mask_matching(require_caps, exclude_caps);
}
LightCall &LightCall::set_effect(const std::string &effect) {

View File

@@ -185,8 +185,8 @@ class LightCall {
//// Compute the color mode that should be used for this call.
ColorMode compute_color_mode_();
/// Get potential color modes for this light call.
ColorModeMask get_suitable_color_modes_();
/// Get potential color modes bitmask for this light call.
color_mode_bitmask_t get_suitable_color_modes_mask_();
/// Some color modes also can be set using non-native parameters, transform those calls.
void transform_parameters_();