diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index c64fc038d6..d202486cfa 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -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]; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8be96c641b..c8a1d85ef1 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -476,9 +476,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c auto *light = static_cast(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(); diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index 450b5e83de..6b33408e2f 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -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; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index c7b88bb312..37bcf5d8a0 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -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(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(bit)); - } + if (!this->supported_color_modes->empty()) { + for (const auto &it : *this->supported_color_modes) { + size.add_uint32_force(1, static_cast(it)); } } size.add_float(1, this->min_mireds); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 5b86b7f276..ed49498176 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -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 effects{}; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 69143f50f8..e803125f53 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -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(it), 4); + } dump_field(out, "min_mireds", this->min_mireds); dump_field(out, "max_mireds", this->max_mireds); for (const auto &it : this->effects) { diff --git a/esphome/components/light/color_mode.h b/esphome/components/light/color_mode.h index 77be58bb3b..1241d59627 100644 --- a/esphome/components/light/color_mode.h +++ b/esphome/components/light/color_mode.h @@ -132,6 +132,8 @@ class ColorModeMask { return count; } + constexpr bool empty() const { return this->mask_ == 0; } + /// Iterator support for API encoding class Iterator { public: diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 075efe88f9..2f83b0bd79 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -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) - # use it as-is, otherwise append the element type + # Two cases: + # 1. "std::set" - 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()