mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'light_bitmask' into memory_api
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 [(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 | ||||
|   // Deprecated in API version 1.6 | ||||
|   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); | ||||
|   ListEntitiesLightResponse msg; | ||||
|   auto traits = light->get_traits(); | ||||
|   // msg.supported_color_modes is uint32_t, but get_mask() returns uint16_t | ||||
|   // The upper 16 bits are zero-extended during assignment (ColorMode only has 10 values) | ||||
|   msg.supported_color_modes = traits.get_supported_color_modes().get_mask(); | ||||
|   // Pass pointer to ColorModeMask so the iterator can encode actual ColorMode enum values | ||||
|   msg.supported_color_modes = &traits.get_supported_color_modes(); | ||||
|   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(); | ||||
|   | ||||
| @@ -71,12 +71,13 @@ extend google.protobuf.FieldOptions { | ||||
|     // and is ideal when the exact size is known before populating the array. | ||||
|     optional bool fixed_vector = 50013 [default=false]; | ||||
|  | ||||
|     // enum_as_bitmask: Encode repeated enum fields as a uint32_t bitmask | ||||
|     // When set on a repeated enum field, the field will be stored as a single uint32_t | ||||
|     // where each bit represents whether that enum value is present. This is ideal for | ||||
|     // enums with ≤32 values and eliminates all vector template instantiation overhead. | ||||
|     // The enum values should be sequential starting from 0. | ||||
|     // Encoding: bit N set means enum value N is present in the set. | ||||
|     // Example: {ColorMode::RGB, ColorMode::WHITE} → bitmask with bits 5 and 6 set | ||||
|     optional bool enum_as_bitmask = 50014 [default=false]; | ||||
|     // container_pointer_no_template: Use a non-template container type for repeated fields | ||||
|     // Similar to container_pointer, but for containers that don't take template parameters. | ||||
|     // The container type is used as-is without appending element type. | ||||
|     // The container must have: | ||||
|     // - begin() and end() methods returning iterators | ||||
|     // - empty() method | ||||
|     // Example: [(container_pointer_no_template) = "light::ColorModeMask"] | ||||
|     //   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_fixed32(2, this->key); | ||||
|   buffer.encode_string(3, this->name_ref_); | ||||
|   for (uint8_t bit = 0; bit < 32; bit++) { | ||||
|     if (this->supported_color_modes & (1U << bit)) { | ||||
|       buffer.encode_uint32(12, bit, true); | ||||
|     } | ||||
|   for (const auto &it : *this->supported_color_modes) { | ||||
|     buffer.encode_uint32(12, static_cast<uint32_t>(it), true); | ||||
|   } | ||||
|   buffer.encode_float(9, this->min_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_fixed32(1, this->key); | ||||
|   size.add_length(1, this->name_ref_.size()); | ||||
|   if (this->supported_color_modes != 0) { | ||||
|     for (uint8_t bit = 0; bit < 32; bit++) { | ||||
|       if (this->supported_color_modes & (1U << bit)) { | ||||
|         size.add_uint32_force(1, static_cast<uint32_t>(bit)); | ||||
|       } | ||||
|   if (!this->supported_color_modes->empty()) { | ||||
|     for (const auto &it : *this->supported_color_modes) { | ||||
|       size.add_uint32_force(1, static_cast<uint32_t>(it)); | ||||
|     } | ||||
|   } | ||||
|   size.add_float(1, this->min_mireds); | ||||
|   | ||||
| @@ -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 | ||||
|   uint32_t supported_color_modes{}; | ||||
|   const light::ColorModeMask *supported_color_modes{}; | ||||
|   float min_mireds{0.0f}; | ||||
|   float max_mireds{0.0f}; | ||||
|   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, "key", this->key); | ||||
|   dump_field(out, "name", this->name_ref_); | ||||
|   char buffer[64]; | ||||
|   snprintf(buffer, sizeof(buffer), "  supported_color_modes: 0x%08" PRIX32 "\n", this->supported_color_modes); | ||||
|   out.append(buffer); | ||||
|   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); | ||||
|   dump_field(out, "max_mireds", this->max_mireds); | ||||
|   for (const auto &it : this->effects) { | ||||
|   | ||||
| @@ -132,6 +132,8 @@ class ColorModeMask { | ||||
|     return count; | ||||
|   } | ||||
|  | ||||
|   constexpr bool empty() const { return this->mask_ == 0; } | ||||
|  | ||||
|   /// Iterator support for API encoding | ||||
|   class Iterator { | ||||
|    public: | ||||
|   | ||||
| @@ -1415,11 +1415,15 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|         super().__init__(field) | ||||
|         # Check if this is a pointer field by looking for container_pointer option | ||||
|         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 | ||||
|         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 | ||||
|         # but we can't call create_field_type_info as it would cause recursion | ||||
| @@ -1436,15 +1440,18 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     def cpp_type(self) -> str: | ||||
|         if self._use_bitmask: | ||||
|             # For bitmask fields, store as a single uint32_t | ||||
|             return "uint32_t" | ||||
|         if self._container_no_template: | ||||
|             # Non-template container: use type as-is without appending template parameters | ||||
|             return f"const {self._container_no_template}*" | ||||
|         if self._use_pointer and self._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>) | ||||
|             # use it as-is, otherwise append the element type | ||||
|             # Two cases: | ||||
|             # 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: | ||||
|                 # Has template parameters specified, use as-is | ||||
|                 return f"const {self._container_type}*" | ||||
|             # No <> at all, append element type | ||||
|             return f"const {self._container_type}<{self._ti.cpp_type}>*" | ||||
|         if self._use_fixed_vector: | ||||
|             return f"FixedVector<{self._ti.cpp_type}>" | ||||
| @@ -1471,11 +1478,6 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|         # Pointer fields don't support decoding | ||||
|         if self._use_pointer: | ||||
|             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 | ||||
|         if content is None: | ||||
|             return None | ||||
| @@ -1529,21 +1531,6 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     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: | ||||
|             # 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" | ||||
| @@ -1563,13 +1550,6 @@ class RepeatedTypeInfo(TypeInfo): | ||||
|  | ||||
|     @property | ||||
|     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: | ||||
|             # For pointer fields, dereference and use the existing helper | ||||
|             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 | ||||
|         # 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 | ||||
|         if isinstance(self._ti, MessageType): | ||||
|             field_id_size = self._ti.calculate_field_id_size() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user