diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 618fd1b83c..257a7aaf82 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1604,11 +1604,11 @@ message BluetoothLEAdvertisementResponse { } message BluetoothLERawAdvertisement { - uint64 address = 1; - sint32 rssi = 2; + uint64 address = 1 [(force) = true]; + sint32 rssi = 2 [(force) = true]; uint32 address_type = 3; - bytes data = 4 [(fixed_array_size) = 62]; + bytes data = 4 [(fixed_array_size) = 62, (force) = true]; } message BluetoothLERawAdvertisementsResponse { diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index a863f2c7a8..02600f0977 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -90,4 +90,10 @@ extend google.protobuf.FieldOptions { // - uint16_t _length_{0}; // - uint16_t _count_{0}; optional bool packed_buffer = 50015 [default=false]; + + // force: Always encode this field, even when its value equals the proto3 default. + // Skips the zero/empty check in calculate_size() and encode(), using the _force + // variant of the calc_ method. Use on fields that are almost always non-default + // to eliminate dead branches on hot paths. + optional bool force = 50016 [default=false]; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1944aac6e8..38ebfb9464 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -139,7 +139,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer &buffer) const { #endif #ifdef USE_SERIAL_PROXY for (const auto &it : this->serial_proxies) { - buffer.encode_message(25, it); + buffer.encode_sub_message(25, it); } #endif } @@ -2249,17 +2249,17 @@ bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id, return true; } void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_sint32(2, this->rssi); + buffer.encode_uint64(1, this->address, true); + buffer.encode_sint32(2, this->rssi, true); buffer.encode_uint32(3, this->address_type); - buffer.encode_bytes(4, this->data, this->data_len); + buffer.encode_bytes(4, this->data, this->data_len, true); } uint32_t BluetoothLERawAdvertisement::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_uint64(1, this->address); - size += ProtoSize::calc_sint32(1, this->rssi); + size += ProtoSize::calc_uint64_force(1, this->address); + size += ProtoSize::calc_sint32_force(1, this->rssi); size += ProtoSize::calc_uint32(1, this->address_type); - size += ProtoSize::calc_length(1, this->data_len); + size += ProtoSize::calc_length_force(1, this->data_len); return size; } void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer &buffer) const { diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 7ae7063a41..206f8f558b 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -151,6 +151,11 @@ class TypeInfo(ABC): """Check if the field is repeated.""" return self._field.label == FieldDescriptorProto.LABEL_REPEATED + @property + def force(self) -> bool: + """Check if this field should always be encoded (skip zero/empty check).""" + return get_field_opt(self._field, pb.force, False) + @property def wire_type(self) -> WireType: """Get the wire type for the field.""" @@ -218,6 +223,8 @@ class TypeInfo(ABC): @property def encode_content(self) -> str: + if self.force: + return f"buffer.{self.encode_func}({self.number}, this->{self.field_name}, true);" return f"buffer.{self.encode_func}({self.number}, this->{self.field_name});" encode_func = None @@ -413,6 +420,8 @@ class DoubleType(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() + if force: + return f"size += {field_id_size + self.get_fixed_size_bytes()};" return f"size += ProtoSize::calc_fixed64({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: @@ -437,6 +446,8 @@ class FloatType(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() + if force: + return f"size += {field_id_size + self.get_fixed_size_bytes()};" return f"size += ProtoSize::calc_float({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: @@ -521,6 +532,8 @@ class Fixed64Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() + if force: + return f"size += {field_id_size + self.get_fixed_size_bytes()};" return f"size += ProtoSize::calc_fixed64({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: @@ -545,6 +558,8 @@ class Fixed32Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() + if force: + return f"size += {field_id_size + self.get_fixed_size_bytes()};" return f"size += ProtoSize::calc_fixed32({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: @@ -607,6 +622,8 @@ class StringType(TypeInfo): @property def encode_content(self) -> str: # Use the StringRef + if self.force: + return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_, true);" return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_);" def dump(self, name): @@ -694,7 +711,7 @@ class MessageType(TypeInfo): @property def encode_content(self) -> str: - # Singular message fields skip encoding when empty + # encode_sub_message always encodes (uses backpatch), no force needed return f"buffer.{self.encode_func}({self.number}, this->{self.field_name});" @property @@ -771,6 +788,8 @@ class BytesType(TypeInfo): @property def encode_content(self) -> str: + if self.force: + return f"buffer.encode_bytes({self.number}, this->{self.field_name}_ptr_, this->{self.field_name}_len_, true);" return f"buffer.encode_bytes({self.number}, this->{self.field_name}_ptr_, this->{self.field_name}_len_);" def dump(self, name: str) -> str: @@ -876,6 +895,8 @@ class PointerToBytesBufferType(PointerToBufferTypeBase): @property def encode_content(self) -> str: + if self.force: + return f"buffer.encode_bytes({self.number}, this->{self.field_name}, this->{self.field_name}_len, true);" return f"buffer.encode_bytes({self.number}, this->{self.field_name}, this->{self.field_name}_len);" @property @@ -923,6 +944,10 @@ class PointerToStringBufferType(PointerToBufferTypeBase): @property def encode_content(self) -> str: + if self.force: + return ( + f"buffer.encode_string({self.number}, this->{self.field_name}, true);" + ) return f"buffer.encode_string({self.number}, this->{self.field_name});" @property @@ -1086,6 +1111,8 @@ class FixedArrayBytesType(TypeInfo): @property def encode_content(self) -> str: + if self.force: + return f"buffer.encode_bytes({self.number}, this->{self.field_name}, this->{self.field_name}_len, true);" return f"buffer.encode_bytes({self.number}, this->{self.field_name}, this->{self.field_name}_len);" def dump(self, name: str) -> str: @@ -1159,6 +1186,8 @@ class EnumType(TypeInfo): @property def encode_content(self) -> str: + if self.force: + return f"buffer.{self.encode_func}({self.number}, static_cast(this->{self.field_name}), true);" return f"buffer.{self.encode_func}({self.number}, static_cast(this->{self.field_name}));" def dump(self, name: str) -> str: @@ -1192,6 +1221,8 @@ class SFixed32Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() + if force: + return f"size += {field_id_size + self.get_fixed_size_bytes()};" return f"size += ProtoSize::calc_sfixed32({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: @@ -1216,6 +1247,8 @@ class SFixed64Type(TypeInfo): def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() + if force: + return f"size += {field_id_size + self.get_fixed_size_bytes()};" return f"size += ProtoSize::calc_sfixed64({field_id_size}, {name});" def get_fixed_size_bytes(self) -> int: @@ -2134,7 +2167,8 @@ def build_message_type( encode.extend(wrap_with_ifdef(ti.encode_content, field_ifdef)) size_calc.extend( wrap_with_ifdef( - ti.get_size_calculation(f"this->{ti.field_name}"), field_ifdef + ti.get_size_calculation(f"this->{ti.field_name}", ti.force), + field_ifdef, ) )