mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 23:21:54 +00:00 
			
		
		
		
	Merge branch 'light_bitmask' into memory_api
This commit is contained in:
		| @@ -506,7 +506,7 @@ message ListEntitiesLightResponse { | |||||||
|   string name = 3; |   string name = 3; | ||||||
|   reserved 4; // Deprecated: was string unique_id |   reserved 4; // Deprecated: was string unique_id | ||||||
|  |  | ||||||
|   repeated ColorMode supported_color_modes = 12 [(enum_as_bitmask) = true]; |   repeated ColorMode supported_color_modes = 12 [(container_pointer_no_template) = "light::ColorModeMask"]; | ||||||
|   // next four supports_* are for legacy clients, newer clients should use color modes |   // next four supports_* are for legacy clients, newer clients should use color modes | ||||||
|   // Deprecated in API version 1.6 |   // Deprecated in API version 1.6 | ||||||
|   bool legacy_supports_brightness = 5 [deprecated=true]; |   bool legacy_supports_brightness = 5 [deprecated=true]; | ||||||
|   | |||||||
| @@ -476,9 +476,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c | |||||||
|   auto *light = static_cast<light::LightState *>(entity); |   auto *light = static_cast<light::LightState *>(entity); | ||||||
|   ListEntitiesLightResponse msg; |   ListEntitiesLightResponse msg; | ||||||
|   auto traits = light->get_traits(); |   auto traits = light->get_traits(); | ||||||
|   // msg.supported_color_modes is uint32_t, but get_mask() returns uint16_t |   // Pass pointer to ColorModeMask so the iterator can encode actual ColorMode enum values | ||||||
|   // The upper 16 bits are zero-extended during assignment (ColorMode only has 10 values) |   msg.supported_color_modes = &traits.get_supported_color_modes(); | ||||||
|   msg.supported_color_modes = traits.get_supported_color_modes().get_mask(); |  | ||||||
|   if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) || |   if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) || | ||||||
|       traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) { |       traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) { | ||||||
|     msg.min_mireds = traits.get_min_mireds(); |     msg.min_mireds = traits.get_min_mireds(); | ||||||
|   | |||||||
| @@ -71,12 +71,13 @@ extend google.protobuf.FieldOptions { | |||||||
|     // and is ideal when the exact size is known before populating the array. |     // and is ideal when the exact size is known before populating the array. | ||||||
|     optional bool fixed_vector = 50013 [default=false]; |     optional bool fixed_vector = 50013 [default=false]; | ||||||
|  |  | ||||||
|     // enum_as_bitmask: Encode repeated enum fields as a uint32_t bitmask |     // container_pointer_no_template: Use a non-template container type for repeated fields | ||||||
|     // When set on a repeated enum field, the field will be stored as a single uint32_t |     // Similar to container_pointer, but for containers that don't take template parameters. | ||||||
|     // where each bit represents whether that enum value is present. This is ideal for |     // The container type is used as-is without appending element type. | ||||||
|     // enums with ≤32 values and eliminates all vector template instantiation overhead. |     // The container must have: | ||||||
|     // The enum values should be sequential starting from 0. |     // - begin() and end() methods returning iterators | ||||||
|     // Encoding: bit N set means enum value N is present in the set. |     // - empty() method | ||||||
|     // Example: {ColorMode::RGB, ColorMode::WHITE} → bitmask with bits 5 and 6 set |     // Example: [(container_pointer_no_template) = "light::ColorModeMask"] | ||||||
|     optional bool enum_as_bitmask = 50014 [default=false]; |     //   generates: const light::ColorModeMask *supported_color_modes{}; | ||||||
|  |     optional string container_pointer_no_template = 50014; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -471,10 +471,8 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(1, this->object_id_ref_); |   buffer.encode_string(1, this->object_id_ref_); | ||||||
|   buffer.encode_fixed32(2, this->key); |   buffer.encode_fixed32(2, this->key); | ||||||
|   buffer.encode_string(3, this->name_ref_); |   buffer.encode_string(3, this->name_ref_); | ||||||
|   for (uint8_t bit = 0; bit < 32; bit++) { |   for (const auto &it : *this->supported_color_modes) { | ||||||
|     if (this->supported_color_modes & (1U << bit)) { |     buffer.encode_uint32(12, static_cast<uint32_t>(it), true); | ||||||
|       buffer.encode_uint32(12, bit, true); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|   buffer.encode_float(9, this->min_mireds); |   buffer.encode_float(9, this->min_mireds); | ||||||
|   buffer.encode_float(10, this->max_mireds); |   buffer.encode_float(10, this->max_mireds); | ||||||
| @@ -494,11 +492,9 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { | |||||||
|   size.add_length(1, this->object_id_ref_.size()); |   size.add_length(1, this->object_id_ref_.size()); | ||||||
|   size.add_fixed32(1, this->key); |   size.add_fixed32(1, this->key); | ||||||
|   size.add_length(1, this->name_ref_.size()); |   size.add_length(1, this->name_ref_.size()); | ||||||
|   if (this->supported_color_modes != 0) { |   if (!this->supported_color_modes->empty()) { | ||||||
|     for (uint8_t bit = 0; bit < 32; bit++) { |     for (const auto &it : *this->supported_color_modes) { | ||||||
|       if (this->supported_color_modes & (1U << bit)) { |       size.add_uint32_force(1, static_cast<uint32_t>(it)); | ||||||
|         size.add_uint32_force(1, static_cast<uint32_t>(bit)); |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   size.add_float(1, this->min_mireds); |   size.add_float(1, this->min_mireds); | ||||||
|   | |||||||
| @@ -790,7 +790,7 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "list_entities_light_response"; } |   const char *message_name() const override { return "list_entities_light_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t supported_color_modes{}; |   const light::ColorModeMask *supported_color_modes{}; | ||||||
|   float min_mireds{0.0f}; |   float min_mireds{0.0f}; | ||||||
|   float max_mireds{0.0f}; |   float max_mireds{0.0f}; | ||||||
|   std::vector<std::string> effects{}; |   std::vector<std::string> effects{}; | ||||||
|   | |||||||
| @@ -913,9 +913,9 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { | |||||||
|   dump_field(out, "object_id", this->object_id_ref_); |   dump_field(out, "object_id", this->object_id_ref_); | ||||||
|   dump_field(out, "key", this->key); |   dump_field(out, "key", this->key); | ||||||
|   dump_field(out, "name", this->name_ref_); |   dump_field(out, "name", this->name_ref_); | ||||||
|   char buffer[64]; |   for (const auto &it : *this->supported_color_modes) { | ||||||
|   snprintf(buffer, sizeof(buffer), "  supported_color_modes: 0x%08" PRIX32 "\n", this->supported_color_modes); |     dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4); | ||||||
|   out.append(buffer); |   } | ||||||
|   dump_field(out, "min_mireds", this->min_mireds); |   dump_field(out, "min_mireds", this->min_mireds); | ||||||
|   dump_field(out, "max_mireds", this->max_mireds); |   dump_field(out, "max_mireds", this->max_mireds); | ||||||
|   for (const auto &it : this->effects) { |   for (const auto &it : this->effects) { | ||||||
|   | |||||||
| @@ -132,6 +132,8 @@ class ColorModeMask { | |||||||
|     return count; |     return count; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   constexpr bool empty() const { return this->mask_ == 0; } | ||||||
|  |  | ||||||
|   /// Iterator support for API encoding |   /// Iterator support for API encoding | ||||||
|   class Iterator { |   class Iterator { | ||||||
|    public: |    public: | ||||||
|   | |||||||
| @@ -1415,11 +1415,15 @@ class RepeatedTypeInfo(TypeInfo): | |||||||
|         super().__init__(field) |         super().__init__(field) | ||||||
|         # Check if this is a pointer field by looking for container_pointer option |         # Check if this is a pointer field by looking for container_pointer option | ||||||
|         self._container_type = get_field_opt(field, pb.container_pointer, "") |         self._container_type = get_field_opt(field, pb.container_pointer, "") | ||||||
|         self._use_pointer = bool(self._container_type) |         # Check for non-template container pointer | ||||||
|  |         self._container_no_template = get_field_opt( | ||||||
|  |             field, pb.container_pointer_no_template, "" | ||||||
|  |         ) | ||||||
|  |         self._use_pointer = bool(self._container_type) or bool( | ||||||
|  |             self._container_no_template | ||||||
|  |         ) | ||||||
|         # Check if this should use FixedVector instead of std::vector |         # Check if this should use FixedVector instead of std::vector | ||||||
|         self._use_fixed_vector = get_field_opt(field, pb.fixed_vector, False) |         self._use_fixed_vector = get_field_opt(field, pb.fixed_vector, False) | ||||||
|         # Check if this should be encoded as a bitmask |  | ||||||
|         self._use_bitmask = get_field_opt(field, pb.enum_as_bitmask, False) |  | ||||||
|  |  | ||||||
|         # For repeated fields, we need to get the base type info |         # For repeated fields, we need to get the base type info | ||||||
|         # but we can't call create_field_type_info as it would cause recursion |         # but we can't call create_field_type_info as it would cause recursion | ||||||
| @@ -1436,15 +1440,18 @@ class RepeatedTypeInfo(TypeInfo): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def cpp_type(self) -> str: |     def cpp_type(self) -> str: | ||||||
|         if self._use_bitmask: |         if self._container_no_template: | ||||||
|             # For bitmask fields, store as a single uint32_t |             # Non-template container: use type as-is without appending template parameters | ||||||
|             return "uint32_t" |             return f"const {self._container_no_template}*" | ||||||
|         if self._use_pointer and self._container_type: |         if self._use_pointer and self._container_type: | ||||||
|             # For pointer fields, use the specified container type |             # For pointer fields, use the specified container type | ||||||
|             # If the container type already includes the element type (e.g., std::set<climate::ClimateMode>) |             # Two cases: | ||||||
|             # use it as-is, otherwise append the element type |             # 1. "std::set<climate::ClimateMode>" - Full type with template params, use as-is | ||||||
|  |             # 2. "std::set" - No <>, append the element type | ||||||
|             if "<" in self._container_type and ">" in self._container_type: |             if "<" in self._container_type and ">" in self._container_type: | ||||||
|  |                 # Has template parameters specified, use as-is | ||||||
|                 return f"const {self._container_type}*" |                 return f"const {self._container_type}*" | ||||||
|  |             # No <> at all, append element type | ||||||
|             return f"const {self._container_type}<{self._ti.cpp_type}>*" |             return f"const {self._container_type}<{self._ti.cpp_type}>*" | ||||||
|         if self._use_fixed_vector: |         if self._use_fixed_vector: | ||||||
|             return f"FixedVector<{self._ti.cpp_type}>" |             return f"FixedVector<{self._ti.cpp_type}>" | ||||||
| @@ -1471,11 +1478,6 @@ class RepeatedTypeInfo(TypeInfo): | |||||||
|         # Pointer fields don't support decoding |         # Pointer fields don't support decoding | ||||||
|         if self._use_pointer: |         if self._use_pointer: | ||||||
|             return None |             return None | ||||||
|         if self._use_bitmask: |  | ||||||
|             # Bitmask fields don't support decoding (only used for device->client messages) |  | ||||||
|             raise RuntimeError( |  | ||||||
|                 f"enum_as_bitmask fields do not support decoding: {self.field_name}" |  | ||||||
|             ) |  | ||||||
|         content = self._ti.decode_varint |         content = self._ti.decode_varint | ||||||
|         if content is None: |         if content is None: | ||||||
|             return None |             return None | ||||||
| @@ -1529,21 +1531,6 @@ class RepeatedTypeInfo(TypeInfo): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def encode_content(self) -> str: |     def encode_content(self) -> str: | ||||||
|         if self._use_bitmask: |  | ||||||
|             # For bitmask fields, iterate through set bits and encode each enum value |  | ||||||
|             # The bitmask is stored as uint32_t where bit N represents enum value N |  | ||||||
|             # Note: We iterate through all 32 bits to support the full range of enum_as_bitmask |  | ||||||
|             # (enums with up to 32 values). Specific uses may have fewer values, but the |  | ||||||
|             # generated code is general-purpose. |  | ||||||
|             assert isinstance(self._ti, EnumType), ( |  | ||||||
|                 "enum_as_bitmask only works with enum fields" |  | ||||||
|             ) |  | ||||||
|             o = "for (uint8_t bit = 0; bit < 32; bit++) {\n" |  | ||||||
|             o += f"  if (this->{self.field_name} & (1U << bit)) {{\n" |  | ||||||
|             o += f"    buffer.{self._ti.encode_func}({self.number}, bit, true);\n" |  | ||||||
|             o += "  }\n" |  | ||||||
|             o += "}" |  | ||||||
|             return o |  | ||||||
|         if self._use_pointer: |         if self._use_pointer: | ||||||
|             # For pointer fields, just dereference (pointer should never be null in our use case) |             # For pointer fields, just dereference (pointer should never be null in our use case) | ||||||
|             o = f"for (const auto &it : *this->{self.field_name}) {{\n" |             o = f"for (const auto &it : *this->{self.field_name}) {{\n" | ||||||
| @@ -1563,13 +1550,6 @@ class RepeatedTypeInfo(TypeInfo): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def dump_content(self) -> str: |     def dump_content(self) -> str: | ||||||
|         if self._use_bitmask: |  | ||||||
|             # For bitmask fields, dump the hex value of the bitmask |  | ||||||
|             return ( |  | ||||||
|                 f"char buffer[64];\n" |  | ||||||
|                 f'snprintf(buffer, sizeof(buffer), "  {self.field_name}: 0x%08" PRIX32 "\\n", this->{self.field_name});\n' |  | ||||||
|                 f"out.append(buffer);" |  | ||||||
|             ) |  | ||||||
|         if self._use_pointer: |         if self._use_pointer: | ||||||
|             # For pointer fields, dereference and use the existing helper |             # For pointer fields, dereference and use the existing helper | ||||||
|             return _generate_array_dump_content( |             return _generate_array_dump_content( | ||||||
| @@ -1586,21 +1566,6 @@ class RepeatedTypeInfo(TypeInfo): | |||||||
|         # For repeated fields, we always need to pass force=True to the underlying type's calculation |         # For repeated fields, we always need to pass force=True to the underlying type's calculation | ||||||
|         # This is because the encode method always sets force=true for repeated fields |         # This is because the encode method always sets force=true for repeated fields | ||||||
|  |  | ||||||
|         if self._use_bitmask: |  | ||||||
|             # For bitmask fields, iterate through set bits and calculate size |  | ||||||
|             # Each set bit encodes one enum value (as varint) |  | ||||||
|             # Note: We iterate through all 32 bits to support the full range of enum_as_bitmask |  | ||||||
|             # (enums with up to 32 values). Specific uses may have fewer values, but the |  | ||||||
|             # generated code is general-purpose. |  | ||||||
|             o = f"if ({name} != 0) {{\n" |  | ||||||
|             o += "  for (uint8_t bit = 0; bit < 32; bit++) {\n" |  | ||||||
|             o += f"    if ({name} & (1U << bit)) {{\n" |  | ||||||
|             o += f"      {self._ti.get_size_calculation('bit', True)}\n" |  | ||||||
|             o += "    }\n" |  | ||||||
|             o += "  }\n" |  | ||||||
|             o += "}" |  | ||||||
|             return o |  | ||||||
|  |  | ||||||
|         # Handle message types separately as they use a dedicated helper |         # Handle message types separately as they use a dedicated helper | ||||||
|         if isinstance(self._ti, MessageType): |         if isinstance(self._ti, MessageType): | ||||||
|             field_id_size = self._ti.calculate_field_id_size() |             field_id_size = self._ti.calculate_field_id_size() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user