1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-23 04:03:52 +01:00
This commit is contained in:
J. Nick Koston
2025-10-18 12:35:13 -10:00
parent 753bebdde8
commit e27472b87d
8 changed files with 38 additions and 75 deletions

View File

@@ -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];

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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{};

View File

@@ -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) {

View File

@@ -132,6 +132,8 @@ class ColorModeMask {
return count;
}
constexpr bool empty() const { return this->mask_ == 0; }
/// Iterator support for API encoding
class Iterator {
public:

View File

@@ -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()