diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 1241d59627..059996b740 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -104,6 +104,9 @@ constexpr ColorModeHelper operator|(ColorModeHelper lhs, ColorMode rhs) { return static_cast(static_cast(lhs) | static_cast(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 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(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; } diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 4e6251492d..fbc1b8f97d 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -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(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS); + if (has_rgb) + require_caps |= static_cast(ColorCapability::RGB); + if (has_white) + require_caps |= static_cast(ColorCapability::WHITE); + if (has_ct) + require_caps |= static_cast(ColorCapability::COLOR_TEMPERATURE); + if (has_cwww) + require_caps |= static_cast(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(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) { diff --git a/esphome/components/light/light_call.h b/esphome/components/light/light_call.h index e87ccd3efd..6931b58b9d 100644 --- a/esphome/components/light/light_call.h +++ b/esphome/components/light/light_call.h @@ -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_();