diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 6bdce2b7ff..0c110b8c8b 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2200,9 +2200,7 @@ void ExecuteServiceArgument::calculate_size(uint32_t &total_size) const { } } if (!this->float_array.empty()) { - for (const auto &it : this->float_array) { - ProtoSize::add_fixed_field_repeated<4>(total_size, 1); - } + total_size += this->float_array.size() * 5; } if (!this->string_array.empty()) { for (const auto &it : this->string_array) { diff --git a/esphome/components/api/api_pb2_size.h b/esphome/components/api/api_pb2_size.h index 94c707c17a..dfa1452fff 100644 --- a/esphome/components/api/api_pb2_size.h +++ b/esphome/components/api/api_pb2_size.h @@ -233,18 +233,6 @@ class ProtoSize { total_size += field_id_size + NumBytes; } - /** - * @brief Calculates and adds the size of a fixed field to the total message size (repeated field version) - * - * @tparam NumBytes The number of bytes for this fixed field (4 or 8) - */ - template - static inline void add_fixed_field_repeated(uint32_t &total_size, uint32_t field_id_size) { - // Always calculate size for repeated fields - // Fixed fields always take exactly NumBytes - total_size += field_id_size + NumBytes; - } - /** * @brief Calculates and adds the size of an enum field to the total message size * diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 9566e983df..bb04758a1b 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -277,15 +277,13 @@ class TypeInfo(ABC): zero_check: Expression to check for zero value (e.g., "!= 0.0f") """ field_id_size = self.calculate_field_id_size() - method = ( - f"add_fixed_field_repeated<{num_bytes}>" - if force - else f"add_fixed_field<{num_bytes}>" + # Fixed-size repeated fields are handled differently in RepeatedTypeInfo + # so we should never get force=True here + assert not force, ( + "Fixed-size repeated fields should be handled by RepeatedTypeInfo" ) - if force: - return f"ProtoSize::{method}(total_size, {field_id_size});" - else: - return f"ProtoSize::{method}(total_size, {field_id_size}, {name} {zero_check});" + method = f"add_fixed_field<{num_bytes}>" + return f"ProtoSize::{method}(total_size, {field_id_size}, {name} {zero_check});" @abstractmethod def get_size_calculation(self, name: str, force: bool = False) -> str: @@ -296,6 +294,14 @@ class TypeInfo(ABC): force: Whether to force encoding the field even if it has a default value """ + def get_fixed_size_bytes(self) -> int | None: + """Get the number of bytes for fixed-size fields (float, double, fixed32, etc). + + Returns: + The number of bytes (4 or 8) for fixed-size fields, None for variable-size fields. + """ + return None + @abstractmethod def get_estimated_size(self) -> int: """Get estimated size in bytes for this field with typical values. @@ -335,6 +341,9 @@ class DoubleType(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: return self._get_fixed_size_calculation(name, force, 8, "!= 0.0") + def get_fixed_size_bytes(self) -> int: + return 8 + def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes for double @@ -355,6 +364,9 @@ class FloatType(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: return self._get_fixed_size_calculation(name, force, 4, "!= 0.0f") + def get_fixed_size_bytes(self) -> int: + return 4 + def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 4 # field ID + 4 bytes for float @@ -435,6 +447,9 @@ class Fixed64Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: return self._get_fixed_size_calculation(name, force, 8, "!= 0") + def get_fixed_size_bytes(self) -> int: + return 8 + def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed @@ -455,6 +470,9 @@ class Fixed32Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: return self._get_fixed_size_calculation(name, force, 4, "!= 0") + def get_fixed_size_bytes(self) -> int: + return 4 + def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed @@ -628,6 +646,9 @@ class SFixed32Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: return self._get_fixed_size_calculation(name, force, 4, "!= 0") + def get_fixed_size_bytes(self) -> int: + return 4 + def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed @@ -648,6 +669,9 @@ class SFixed64Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: return self._get_fixed_size_calculation(name, force, 8, "!= 0") + def get_fixed_size_bytes(self) -> int: + return 8 + def get_estimated_size(self) -> int: return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed @@ -801,11 +825,23 @@ class RepeatedTypeInfo(TypeInfo): field_id_size = self._ti.calculate_field_id_size() o = f"ProtoSize::add_repeated_message(total_size, {field_id_size}, {name});" return o + # For other repeated types, use the underlying type's size calculation with force=True o = f"if (!{name}.empty()) {{\n" - o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n" - o += f" {self._ti.get_size_calculation('it', True)}\n" - o += " }\n" + + # Check if this is a fixed-size type by seeing if it has a fixed byte count + num_bytes = self._ti.get_fixed_size_bytes() + if num_bytes is not None: + # Fixed types have constant size per element, so we can multiply + field_id_size = self._ti.calculate_field_id_size() + # Pre-calculate the total bytes per element + bytes_per_element = field_id_size + num_bytes + o += f" total_size += {name}.size() * {bytes_per_element};\n" + else: + # Other types need the actual value + o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n" + o += f" {self._ti.get_size_calculation('it', True)}\n" + o += " }\n" o += "}" return o