diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 799aa57eae..27845b559d 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3838,8 +3838,8 @@ uint8_t *DateTimeStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEB uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->missing_state); - if (this->epoch_seconds != 0) [[likely]] { - pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 29, this->epoch_seconds); + if (uint32_t raw = this->epoch_seconds; raw != 0) [[likely]] { + pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 29, raw); } #ifdef USE_DEVICES pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 8fb05cd6ef..a8eb84a4c2 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -457,10 +457,6 @@ class ProtoEncode { return pos; return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, string, len); } - static inline uint8_t *encode_string(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, - const std::string &value) { - return encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, value.data(), value.size()); - } static inline uint8_t *encode_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, const std::string &value) { return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, value.data(), value.size()); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index d79df9865e..3ef3af41df 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -324,6 +324,24 @@ class TypeInfo(ABC): f"pos = ProtoEncode::encode_raw(pos, {data_expr}, {len_expr});" ) + def _encode_fixed32_with_precomputed_tag(self, value_expr: str) -> str | None: + """Emit a fixed32 field through the shared tag+value writer when the tag is one byte. + + Returns None for multi-byte tags, which go through the generic helper. + """ + tag = self.calculate_tag() + if tag >= 128: + return None + if self.force: + return ( + f"pos = ProtoEncode::write_tag_and_fixed32(pos, {tag}, {value_expr});" + ) + return ( + f"if (uint32_t raw = {value_expr}; raw != 0) [[likely]] {{\n" + f" pos = ProtoEncode::write_tag_and_fixed32(pos, {tag}, raw);\n" + "}" + ) + @property def encode_content(self) -> str: if result := self._encode_with_precomputed_tag(f"this->{self.field_name}"): @@ -637,18 +655,11 @@ class FloatType(FixedSizeTypeMixin, TypeInfo): @property def encode_content(self) -> str: - tag = self.calculate_tag() - if tag >= 128: - return super().encode_content - # Single-byte tag: share the outlined tag+fixed32 writer instead of the generic helper - value = f"float_to_raw(this->{self.field_name})" - if self.force: - return f"pos = ProtoEncode::write_tag_and_fixed32(pos, {tag}, {value});" - return ( - f"if (uint32_t raw = {value}; raw != 0) [[likely]] {{\n" - f" pos = ProtoEncode::write_tag_and_fixed32(pos, {tag}, raw);\n" - "}" - ) + if result := self._encode_fixed32_with_precomputed_tag( + f"float_to_raw(this->{self.field_name})" + ): + return result + return super().encode_content def dump(self, name: str) -> str: o = f'snprintf(buffer, sizeof(buffer), "%g", {name});\n' @@ -786,17 +797,11 @@ class Fixed32Type(FixedSizeTypeMixin, TypeInfo): @property def encode_content(self) -> str: - tag = self.calculate_tag() - if tag >= 128: - return super().encode_content - # Single-byte tag: share the outlined tag+fixed32 writer instead of the generic helper - if self.force: - return f"pos = ProtoEncode::write_tag_and_fixed32(pos, {tag}, this->{self.field_name});" - return ( - f"if (this->{self.field_name} != 0) [[likely]] {{\n" - f" pos = ProtoEncode::write_tag_and_fixed32(pos, {tag}, this->{self.field_name});\n" - "}" - ) + if result := self._encode_fixed32_with_precomputed_tag( + f"this->{self.field_name}" + ): + return result + return super().encode_content def get_size_calculation(self, name: str, force: bool = False) -> str: field_id_size = self.calculate_field_id_size() diff --git a/tests/unit_tests/components/api/test_api_protobuf_generator.py b/tests/unit_tests/components/api/test_api_protobuf_generator.py index 531944fe8a..763a764894 100644 --- a/tests/unit_tests/components/api/test_api_protobuf_generator.py +++ b/tests/unit_tests/components/api/test_api_protobuf_generator.py @@ -45,7 +45,14 @@ UINT64 = descriptor_pb2.FieldDescriptorProto.TYPE_UINT64 INT64 = descriptor_pb2.FieldDescriptorProto.TYPE_INT64 SINT64 = descriptor_pb2.FieldDescriptorProto.TYPE_SINT64 UINT32 = descriptor_pb2.FieldDescriptorProto.TYPE_UINT32 +INT32 = descriptor_pb2.FieldDescriptorProto.TYPE_INT32 +SINT32 = descriptor_pb2.FieldDescriptorProto.TYPE_SINT32 FIXED64 = descriptor_pb2.FieldDescriptorProto.TYPE_FIXED64 +FIXED32 = descriptor_pb2.FieldDescriptorProto.TYPE_FIXED32 +FLOAT = descriptor_pb2.FieldDescriptorProto.TYPE_FLOAT +BOOL = descriptor_pb2.FieldDescriptorProto.TYPE_BOOL +STRING = descriptor_pb2.FieldDescriptorProto.TYPE_STRING +BYTES = descriptor_pb2.FieldDescriptorProto.TYPE_BYTES def test_no_varint64_fields() -> None: @@ -127,16 +134,16 @@ def _encode_field( SCALAR_TYPES = [ - descriptor_pb2.FieldDescriptorProto.TYPE_BOOL, - descriptor_pb2.FieldDescriptorProto.TYPE_UINT32, - descriptor_pb2.FieldDescriptorProto.TYPE_INT32, - descriptor_pb2.FieldDescriptorProto.TYPE_UINT64, - descriptor_pb2.FieldDescriptorProto.TYPE_INT64, - descriptor_pb2.FieldDescriptorProto.TYPE_SINT32, - descriptor_pb2.FieldDescriptorProto.TYPE_FLOAT, - descriptor_pb2.FieldDescriptorProto.TYPE_FIXED32, - descriptor_pb2.FieldDescriptorProto.TYPE_STRING, - descriptor_pb2.FieldDescriptorProto.TYPE_BYTES, + BOOL, + UINT32, + INT32, + UINT64, + INT64, + SINT32, + FLOAT, + FIXED32, + STRING, + BYTES, ] @@ -165,10 +172,6 @@ def test_forced_fields_use_the_force_overload_or_raw_writes(field_type: int) -> ), content -FLOAT = descriptor_pb2.FieldDescriptorProto.TYPE_FLOAT -FIXED32 = descriptor_pb2.FieldDescriptorProto.TYPE_FIXED32 - - @pytest.mark.parametrize("field_type", [FLOAT, FIXED32]) def test_single_byte_tag_fixed32_shares_the_outlined_writer(field_type: int) -> None: unconditional = _encode_field(field_type, force=True)