mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[light] Use bitmask instead of std::set for color modes
This commit is contained in:
		| @@ -506,7 +506,7 @@ message ListEntitiesLightResponse { | ||||
|   string name = 3; | ||||
|   reserved 4; // Deprecated: was string unique_id | ||||
|  | ||||
|   repeated ColorMode supported_color_modes = 12 [(container_pointer) = "std::set<light::ColorMode>"]; | ||||
|   repeated ColorMode supported_color_modes = 12 [(fixed_vector) = true]; | ||||
|   // next four supports_* are for legacy clients, newer clients should use color modes | ||||
|   // Deprecated in API version 1.6 | ||||
|   bool legacy_supports_brightness = 5 [deprecated=true]; | ||||
|   | ||||
| @@ -477,7 +477,11 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c | ||||
|   auto *light = static_cast<light::LightState *>(entity); | ||||
|   ListEntitiesLightResponse msg; | ||||
|   auto traits = light->get_traits(); | ||||
|   msg.supported_color_modes = &traits.get_supported_color_modes_for_api_(); | ||||
|   const auto &color_modes_mask = traits.get_supported_color_modes(); | ||||
|   msg.supported_color_modes.init(color_modes_mask.size()); | ||||
|   for (auto mode : color_modes_mask) { | ||||
|     msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode)); | ||||
|   } | ||||
|   if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) || | ||||
|       traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) { | ||||
|     msg.min_mireds = traits.get_min_mireds(); | ||||
|   | ||||
| @@ -471,7 +471,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(1, this->object_id_ref_); | ||||
|   buffer.encode_fixed32(2, this->key); | ||||
|   buffer.encode_string(3, this->name_ref_); | ||||
|   for (const auto &it : *this->supported_color_modes) { | ||||
|   for (auto &it : this->supported_color_modes) { | ||||
|     buffer.encode_uint32(12, static_cast<uint32_t>(it), true); | ||||
|   } | ||||
|   buffer.encode_float(9, this->min_mireds); | ||||
| @@ -492,8 +492,8 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { | ||||
|   size.add_length(1, this->object_id_ref_.size()); | ||||
|   size.add_fixed32(1, this->key); | ||||
|   size.add_length(1, this->name_ref_.size()); | ||||
|   if (!this->supported_color_modes->empty()) { | ||||
|     for (const auto &it : *this->supported_color_modes) { | ||||
|   if (!this->supported_color_modes.empty()) { | ||||
|     for (const auto &it : this->supported_color_modes) { | ||||
|       size.add_uint32_force(1, static_cast<uint32_t>(it)); | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -790,7 +790,7 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   const char *message_name() const override { return "list_entities_light_response"; } | ||||
| #endif | ||||
|   const std::set<light::ColorMode> *supported_color_modes{}; | ||||
|   FixedVector<enums::ColorMode> supported_color_modes{}; | ||||
|   float min_mireds{0.0f}; | ||||
|   float max_mireds{0.0f}; | ||||
|   std::vector<std::string> effects{}; | ||||
|   | ||||
| @@ -913,7 +913,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||
|   dump_field(out, "object_id", this->object_id_ref_); | ||||
|   dump_field(out, "key", this->key); | ||||
|   dump_field(out, "name", this->name_ref_); | ||||
|   for (const auto &it : *this->supported_color_modes) { | ||||
|   for (const auto &it : this->supported_color_modes) { | ||||
|     dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4); | ||||
|   } | ||||
|   dump_field(out, "min_mireds", this->min_mireds); | ||||
|   | ||||
| @@ -104,5 +104,132 @@ constexpr ColorModeHelper operator|(ColorModeHelper lhs, ColorMode rhs) { | ||||
|   return static_cast<ColorMode>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs)); | ||||
| } | ||||
|  | ||||
| /// Bitmask for storing a set of ColorMode values efficiently. | ||||
| /// Replaces std::set<ColorMode> to eliminate red-black tree overhead (~586 bytes). | ||||
| class ColorModeMask { | ||||
|  public: | ||||
|   constexpr ColorModeMask() = default; | ||||
|  | ||||
|   /// Support initializer list syntax: {ColorMode::RGB, ColorMode::WHITE} | ||||
|   constexpr ColorModeMask(std::initializer_list<ColorMode> modes) { | ||||
|     for (auto mode : modes) { | ||||
|       this->add(mode); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   constexpr void add(ColorMode mode) { this->mask_ |= (1 << mode_to_bit(mode)); } | ||||
|  | ||||
|   constexpr bool contains(ColorMode mode) const { return (this->mask_ & (1 << mode_to_bit(mode))) != 0; } | ||||
|  | ||||
|   constexpr size_t size() const { | ||||
|     // Count set bits | ||||
|     uint16_t n = this->mask_; | ||||
|     size_t count = 0; | ||||
|     while (n) { | ||||
|       count += n & 1; | ||||
|       n >>= 1; | ||||
|     } | ||||
|     return count; | ||||
|   } | ||||
|  | ||||
|   /// Iterator support for API encoding | ||||
|   class Iterator { | ||||
|    public: | ||||
|     using iterator_category = std::forward_iterator_tag; | ||||
|     using value_type = ColorMode; | ||||
|     using difference_type = std::ptrdiff_t; | ||||
|     using pointer = const ColorMode *; | ||||
|     using reference = ColorMode; | ||||
|  | ||||
|     constexpr Iterator(uint16_t mask, int bit) : mask_(mask), bit_(bit) { advance_to_next_set_bit(); } | ||||
|  | ||||
|     constexpr ColorMode operator*() const { return bit_to_mode(bit_); } | ||||
|  | ||||
|     constexpr Iterator &operator++() { | ||||
|       ++bit_; | ||||
|       advance_to_next_set_bit(); | ||||
|       return *this; | ||||
|     } | ||||
|  | ||||
|     constexpr bool operator==(const Iterator &other) const { return bit_ == other.bit_; } | ||||
|  | ||||
|     constexpr bool operator!=(const Iterator &other) const { return !(*this == other); } | ||||
|  | ||||
|    private: | ||||
|     constexpr void advance_to_next_set_bit() { | ||||
|       while (bit_ < 16 && !(mask_ & (1 << bit_))) { | ||||
|         ++bit_; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     uint16_t mask_; | ||||
|     int bit_; | ||||
|   }; | ||||
|  | ||||
|   constexpr Iterator begin() const { return Iterator(mask_, 0); } | ||||
|   constexpr Iterator end() const { return Iterator(mask_, 16); } | ||||
|  | ||||
|  private: | ||||
|   uint16_t mask_{0}; | ||||
|  | ||||
|   /// 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: | ||||
|         return 0; | ||||
|       case ColorMode::ON_OFF: | ||||
|         return 1; | ||||
|       case ColorMode::BRIGHTNESS: | ||||
|         return 2; | ||||
|       case ColorMode::WHITE: | ||||
|         return 3; | ||||
|       case ColorMode::COLOR_TEMPERATURE: | ||||
|         return 4; | ||||
|       case ColorMode::COLD_WARM_WHITE: | ||||
|         return 5; | ||||
|       case ColorMode::RGB: | ||||
|         return 6; | ||||
|       case ColorMode::RGB_WHITE: | ||||
|         return 7; | ||||
|       case ColorMode::RGB_COLOR_TEMPERATURE: | ||||
|         return 8; | ||||
|       case ColorMode::RGB_COLD_WARM_WHITE: | ||||
|         return 9; | ||||
|       default: | ||||
|         return 0; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   static constexpr ColorMode bit_to_mode(int bit) { | ||||
|     // Using switch instead of lookup table to avoid RAM usage on ESP8266 | ||||
|     switch (bit) { | ||||
|       case 0: | ||||
|         return ColorMode::UNKNOWN; | ||||
|       case 1: | ||||
|         return ColorMode::ON_OFF; | ||||
|       case 2: | ||||
|         return ColorMode::BRIGHTNESS; | ||||
|       case 3: | ||||
|         return ColorMode::WHITE; | ||||
|       case 4: | ||||
|         return ColorMode::COLOR_TEMPERATURE; | ||||
|       case 5: | ||||
|         return ColorMode::COLD_WARM_WHITE; | ||||
|       case 6: | ||||
|         return ColorMode::RGB; | ||||
|       case 7: | ||||
|         return ColorMode::RGB_WHITE; | ||||
|       case 8: | ||||
|         return ColorMode::RGB_COLOR_TEMPERATURE; | ||||
|       case 9: | ||||
|         return ColorMode::RGB_COLD_WARM_WHITE; | ||||
|       default: | ||||
|         return ColorMode::UNKNOWN; | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace light | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -425,10 +425,10 @@ 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. | ||||
|   std::set<ColorMode> suitable_modes = this->get_suitable_color_modes_(); | ||||
|   ColorModeMask suitable_modes = this->get_suitable_color_modes_(); | ||||
|  | ||||
|   // Don't change if the current mode is suitable. | ||||
|   if (suitable_modes.count(current_mode) > 0) { | ||||
|   if (suitable_modes.contains(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; | ||||
| @@ -436,7 +436,7 @@ ColorMode LightCall::compute_color_mode_() { | ||||
|  | ||||
|   // Use the preferred suitable mode. | ||||
|   for (auto mode : suitable_modes) { | ||||
|     if (supported_modes.count(mode) == 0) | ||||
|     if (!supported_modes.contains(mode)) | ||||
|       continue; | ||||
|  | ||||
|     ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(), | ||||
| @@ -451,7 +451,7 @@ ColorMode LightCall::compute_color_mode_() { | ||||
|            LOG_STR_ARG(color_mode_to_human(color_mode))); | ||||
|   return color_mode; | ||||
| } | ||||
| std::set<ColorMode> LightCall::get_suitable_color_modes_() { | ||||
| ColorModeMask LightCall::get_suitable_color_modes_() { | ||||
|   bool has_white = this->has_white() && this->white_ > 0.0f; | ||||
|   bool has_ct = this->has_color_temperature(); | ||||
|   bool has_cwww = | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "light_color_values.h" | ||||
| #include <set> | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| @@ -187,7 +186,7 @@ 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. | ||||
|   std::set<ColorMode> get_suitable_color_modes_(); | ||||
|   ColorModeMask get_suitable_color_modes_(); | ||||
|   /// Some color modes also can be set using non-native parameters, transform those calls. | ||||
|   void transform_parameters_(); | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,6 @@ | ||||
|  | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "color_mode.h" | ||||
| #include <set> | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| @@ -19,12 +18,15 @@ class LightTraits { | ||||
|  public: | ||||
|   LightTraits() = default; | ||||
|  | ||||
|   const std::set<ColorMode> &get_supported_color_modes() const { return this->supported_color_modes_; } | ||||
|   void set_supported_color_modes(std::set<ColorMode> supported_color_modes) { | ||||
|     this->supported_color_modes_ = std::move(supported_color_modes); | ||||
|   const ColorModeMask &get_supported_color_modes() const { return this->supported_color_modes_; } | ||||
|   void set_supported_color_modes(ColorModeMask supported_color_modes) { | ||||
|     this->supported_color_modes_ = supported_color_modes; | ||||
|   } | ||||
|   void set_supported_color_modes(std::initializer_list<ColorMode> modes) { | ||||
|     this->supported_color_modes_ = ColorModeMask(modes); | ||||
|   } | ||||
|  | ||||
|   bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.count(color_mode); } | ||||
|   bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.contains(color_mode); } | ||||
|   bool supports_color_capability(ColorCapability color_capability) const { | ||||
|     for (auto mode : this->supported_color_modes_) { | ||||
|       if (mode & color_capability) | ||||
| @@ -59,17 +61,7 @@ class LightTraits { | ||||
|   void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; } | ||||
|  | ||||
|  protected: | ||||
| #ifdef USE_API | ||||
|   // The API connection is a friend class to access internal methods | ||||
|   friend class api::APIConnection; | ||||
|   // This method returns a reference to the internal color modes set. | ||||
|   // It is used by the API to avoid copying data when encoding messages. | ||||
|   // Warning: Do not use this method outside of the API connection code. | ||||
|   // It returns a reference to internal data that can be invalidated. | ||||
|   const std::set<ColorMode> &get_supported_color_modes_for_api_() const { return this->supported_color_modes_; } | ||||
| #endif | ||||
|  | ||||
|   std::set<ColorMode> supported_color_modes_{}; | ||||
|   ColorModeMask supported_color_modes_{}; | ||||
|   float min_mireds_{0}; | ||||
|   float max_mireds_{0}; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user