From 305667b06dcdc254fdfb93a6ef3354bc4cf34a5d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 18:59:48 -1000 Subject: [PATCH 01/14] [api] Sync uses_password field_ifdef optimization from aioesphomeapi (#9756) --- esphome/components/api/api.proto | 2 +- esphome/components/api/api_connection.cpp | 2 -- esphome/components/api/api_pb2.cpp | 4 ++++ esphome/components/api/api_pb2.h | 2 ++ esphome/components/api/api_pb2_dump.cpp | 2 ++ 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 546c498ff3..fd08e87bbf 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -203,7 +203,7 @@ message DeviceInfoResponse { option (id) = 10; option (source) = SOURCE_SERVER; - bool uses_password = 1; + bool uses_password = 1 [(field_ifdef) = "USE_API_PASSWORD"]; // The name of the node, given by "App.set_name()" string name = 2; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 602a0256cf..bc0afd49eb 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1432,8 +1432,6 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { DeviceInfoResponse resp{}; #ifdef USE_API_PASSWORD resp.uses_password = true; -#else - resp.uses_password = false; #endif resp.name = App.get_name(); resp.friendly_name = App.get_friendly_name(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 4cf4b63269..528c581ad7 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -80,7 +80,9 @@ void DeviceInfo::calculate_size(uint32_t &total_size) const { } #endif void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { +#ifdef USE_API_PASSWORD buffer.encode_bool(1, this->uses_password); +#endif buffer.encode_string(2, this->name); buffer.encode_string(3, this->mac_address); buffer.encode_string(4, this->esphome_version); @@ -130,7 +132,9 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { #endif } void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { +#ifdef USE_API_PASSWORD ProtoSize::add_bool_field(total_size, 1, this->uses_password); +#endif ProtoSize::add_string_field(total_size, 1, this->name); ProtoSize::add_string_field(total_size, 1, this->mac_address); ProtoSize::add_string_field(total_size, 1, this->esphome_version); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e241451ec8..7b64bd889f 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -474,7 +474,9 @@ class DeviceInfoResponse : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "device_info_response"; } #endif +#ifdef USE_API_PASSWORD bool uses_password{false}; +#endif std::string name{}; std::string mac_address{}; std::string esphome_version{}; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index bda5ec5764..4951c6cebf 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -647,10 +647,12 @@ void DeviceInfo::dump_to(std::string &out) const { void DeviceInfoResponse::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("DeviceInfoResponse {\n"); +#ifdef USE_API_PASSWORD out.append(" uses_password: "); out.append(YESNO(this->uses_password)); out.append("\n"); +#endif out.append(" name: "); out.append("'").append(this->name).append("'"); out.append("\n"); From 54bbde61832731ffbfa40a6f4b303dee80ed2dc2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:01:41 -1000 Subject: [PATCH 02/14] zero copy cleanup --- esphome/components/api/api.proto | 4 +-- esphome/components/api/api_connection.cpp | 43 ++++++----------------- esphome/components/api/api_options.proto | 1 + esphome/components/api/api_pb2.cpp | 8 ++--- esphome/components/api/api_pb2.h | 14 ++++++-- esphome/components/api/api_pb2_dump.cpp | 4 +-- esphome/components/api/proto.h | 32 +++++++---------- script/api_protobuf/api_protobuf.py | 43 +++++++++++++++++++++++ 8 files changed, 87 insertions(+), 62 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 546c498ff3..4dec686c68 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -731,7 +731,7 @@ message SubscribeLogsResponse { option (no_delay) = false; LogLevel level = 1; - bytes message = 3; + bytes message = 3 [(zero_copy) = true]; bool send_failed = 4; } @@ -888,7 +888,7 @@ message CameraImageResponse { option (ifdef) = "USE_CAMERA"; fixed32 key = 1; - bytes data = 2; + bytes data = 2 [(zero_copy) = true]; bool done = 3; uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"]; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 602a0256cf..2fd3d4e1ea 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -225,22 +225,13 @@ void APIConnection::loop() { if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) { uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available()); bool done = this->image_reader_->available() == to_send; - uint32_t msg_size = 0; - ProtoSize::add_fixed_field<4>(msg_size, 1, true); - // partial message size calculated manually since its a special case - // 1 for the data field, varint for the data size, and the data itself - msg_size += 1 + ProtoSize::varint(to_send) + to_send; - ProtoSize::add_bool_field(msg_size, 1, done); - auto buffer = this->create_buffer(msg_size); - // fixed32 key = 1; - buffer.encode_fixed32(1, camera::Camera::instance()->get_object_id_hash()); - // bytes data = 2; - buffer.encode_bytes(2, this->image_reader_->peek_data_buffer(), to_send); - // bool done = 3; - buffer.encode_bool(3, done); + CameraImageResponse msg; + msg.key = camera::Camera::instance()->get_object_id_hash(); + msg.set_data(this->image_reader_->peek_data_buffer(), to_send); + msg.done = done; - bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE); + bool success = this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE); if (success) { this->image_reader_->consume_data(to_send); @@ -1350,26 +1341,12 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) { #endif bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) { - // Pre-calculate message size to avoid reallocations - uint32_t msg_size = 0; + SubscribeLogsResponse msg; + msg.level = static_cast(level); + msg.set_message(reinterpret_cast(line), message_len); + msg.send_failed = false; - // Add size for level field (field ID 1, varint type) - // 1 byte for field tag + size of the level varint - msg_size += 1 + api::ProtoSize::varint(static_cast(level)); - - // Add size for string field (field ID 3, string type) - // 1 byte for field tag + size of length varint + string length - msg_size += 1 + api::ProtoSize::varint(static_cast(message_len)) + message_len; - - // Create a pre-sized buffer - auto buffer = this->create_buffer(msg_size); - - // Encode the message (SubscribeLogsResponse) - buffer.encode_uint32(1, static_cast(level)); // LogLevel level = 1 - buffer.encode_string(3, line, message_len); // string message = 3 - - // SubscribeLogsResponse - 29 - return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE); + return this->send_message_(msg, SubscribeLogsResponse::MESSAGE_TYPE); } void APIConnection::complete_authentication_() { diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index bb3947e8a3..d5e3df6593 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -27,4 +27,5 @@ extend google.protobuf.MessageOptions { extend google.protobuf.FieldOptions { optional string field_ifdef = 1042; optional uint32 fixed_array_size = 50007; + optional bool zero_copy = 1043 [default=false]; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 4cf4b63269..25156abf03 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -818,12 +818,12 @@ bool SubscribeLogsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, static_cast(this->level)); - buffer.encode_bytes(3, reinterpret_cast(this->message.data()), this->message.size()); + buffer.encode_bytes(3, this->message_ptr_, this->message_len_); buffer.encode_bool(4, this->send_failed); } void SubscribeLogsResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_enum_field(total_size, 1, static_cast(this->level)); - ProtoSize::add_string_field(total_size, 1, this->message); + ProtoSize::add_bytes_field(total_size, 1, this->message_len_); ProtoSize::add_bool_field(total_size, 1, this->send_failed); } #ifdef USE_API_NOISE @@ -1030,7 +1030,7 @@ void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { } void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_bytes(2, reinterpret_cast(this->data.data()), this->data.size()); + buffer.encode_bytes(2, this->data_ptr_, this->data_len_); buffer.encode_bool(3, this->done); #ifdef USE_DEVICES buffer.encode_uint32(4, this->device_id); @@ -1038,7 +1038,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { } void CameraImageResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_fixed32_field(total_size, 1, this->key); - ProtoSize::add_string_field(total_size, 1, this->data); + ProtoSize::add_bytes_field(total_size, 1, this->data_len_); ProtoSize::add_bool_field(total_size, 1, this->done); #ifdef USE_DEVICES ProtoSize::add_uint32_field(total_size, 1, this->device_id); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e241451ec8..7512431ccf 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -968,7 +968,12 @@ class SubscribeLogsResponse : public ProtoMessage { const char *message_name() const override { return "subscribe_logs_response"; } #endif enums::LogLevel level{}; - std::string message{}; + const uint8_t *message_ptr_{nullptr}; + size_t message_len_{0}; + void set_message(const uint8_t *data, size_t len) { + this->message_ptr_ = data; + this->message_len_ = len; + } bool send_failed{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; @@ -1226,7 +1231,12 @@ class CameraImageResponse : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "camera_image_response"; } #endif - std::string data{}; + const uint8_t *data_ptr_{nullptr}; + size_t data_len_{0}; + void set_data(const uint8_t *data, size_t len) { + this->data_ptr_ = data; + this->data_len_ = len; + } bool done{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index bda5ec5764..59d9aff209 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1666,7 +1666,7 @@ void SubscribeLogsResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" message: "); - out.append(format_hex_pretty(this->message)); + out.append(format_hex_pretty(this->message_ptr_, this->message_len_)); out.append("\n"); out.append(" send_failed: "); @@ -1932,7 +1932,7 @@ void CameraImageResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append(format_hex_pretty(this->data)); + out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); out.append("\n"); out.append(" done: "); diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index a2c31100bf..0ba8df84da 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -527,25 +527,6 @@ class ProtoSize { total_size += field_id_size + 1; } - /** - * @brief Calculates and adds the size of a fixed field to the total message size - * - * Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double). - * - * @tparam NumBytes The number of bytes for this fixed field (4 or 8) - * @param is_nonzero Whether the value is non-zero - */ - template - static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero) { - // Skip calculation if value is zero - if (!is_nonzero) { - return; // No need to update total_size - } - - // Fixed fields always take exactly NumBytes - total_size += field_id_size + NumBytes; - } - /** * @brief Calculates and adds the size of a float field to the total message size */ @@ -704,6 +685,19 @@ class ProtoSize { total_size += field_id_size + varint(str_size) + str_size; } + /** + * @brief Calculates and adds the size of a bytes field to the total message size + */ + static inline void add_bytes_field(uint32_t &total_size, uint32_t field_id_size, size_t len) { + // Skip calculation if bytes is empty + if (len == 0) { + return; // No need to update total_size + } + + // Field ID + length varint + data bytes + total_size += field_id_size + varint(static_cast(len)) + static_cast(len); + } + /** * @brief Calculates and adds the size of a nested message field to the total message size * diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index ad6c3c3ed2..5569f29818 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -325,6 +325,10 @@ def create_field_type_info(field: descriptor.FieldDescriptorProto) -> TypeInfo: ): return FixedArrayBytesType(field, fixed_size) + # Check for zero_copy option on bytes fields + if field.type == 12 and get_field_opt(field, pb.zero_copy, default=False): + return ZeroCopyBytesType(field) + validate_field_type(field.type, field.name) return TYPE_INFO[field.type](field) @@ -608,6 +612,45 @@ class BytesType(TypeInfo): return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes +class ZeroCopyBytesType(TypeInfo): + """Special type for zero-copy bytes fields that only accepts const uint8_t* data.""" + + cpp_type = "std::string" # Still store as string for compatibility + default_value = "" + reference_type = "std::string &" + const_reference_type = "const std::string &" + encode_func = "encode_bytes" + wire_type = WireType.LENGTH_DELIMITED + decode_length = "value.as_string()" + + @property + def public_content(self) -> list[str]: + # Store both pointer and length for zero-copy encoding, plus setter method + return [ + f"const uint8_t* {self.field_name}_ptr_{{nullptr}};", + f"size_t {self.field_name}_len_{{0}};", + f"void set_{self.field_name}(const uint8_t* data, size_t len) {{", + f" this->{self.field_name}_ptr_ = data;", + f" this->{self.field_name}_len_ = len;", + "}", + ] + + @property + def encode_content(self) -> str: + # Encode directly from pointer without nullptr check (like original) + return f"buffer.encode_bytes({self.number}, this->{self.field_name}_ptr_, this->{self.field_name}_len_);" + + def dump(self, name: str) -> str: + return f"out.append(format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_));" + + def get_size_calculation(self, name: str, force: bool = False) -> str: + # Use the new add_bytes_field helper + return f"ProtoSize::add_bytes_field(total_size, {self.calculate_field_id_size()}, this->{self.field_name}_len_);" + + def get_estimated_size(self) -> int: + return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes + + class FixedArrayBytesType(TypeInfo): """Special type for fixed-size byte arrays.""" From 7de63d06708104bb6b7673fb7063dae741e8addd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:18:25 -1000 Subject: [PATCH 03/14] fixes --- esphome/components/api/api.proto | 4 +- esphome/components/api/api_connection.cpp | 4 +- esphome/components/api/api_options.proto | 1 - esphome/components/api/api_pb2.cpp | 12 +++--- esphome/components/api/api_pb2.h | 42 ++++++++++++++++--- esphome/components/api/api_pb2_dump.cpp | 12 +++--- .../bluetooth_proxy/bluetooth_connection.cpp | 17 ++++---- .../bluetooth_proxy/bluetooth_connection.h | 4 +- .../bluetooth_proxy/bluetooth_proxy.cpp | 4 +- .../voice_assistant/voice_assistant.cpp | 14 +++---- script/api_protobuf/api_protobuf.py | 32 -------------- 11 files changed, 71 insertions(+), 75 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 4dec686c68..546c498ff3 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -731,7 +731,7 @@ message SubscribeLogsResponse { option (no_delay) = false; LogLevel level = 1; - bytes message = 3 [(zero_copy) = true]; + bytes message = 3; bool send_failed = 4; } @@ -888,7 +888,7 @@ message CameraImageResponse { option (ifdef) = "USE_CAMERA"; fixed32 key = 1; - bytes data = 2 [(zero_copy) = true]; + bytes data = 2; bool done = 3; uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"]; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2fd3d4e1ea..c3cee7d80d 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1497,7 +1497,9 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) { psk_t psk{}; NoiseEncryptionSetKeyResponse resp; - if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) { + // Create temporary string from pointer/length for base64_decode + std::string key_str(reinterpret_cast(msg.key_ptr_), msg.key_len_); + if (base64_decode(key_str, psk.data(), key_str.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); resp.success = false; return resp; diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index d5e3df6593..bb3947e8a3 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -27,5 +27,4 @@ extend google.protobuf.MessageOptions { extend google.protobuf.FieldOptions { optional string field_ifdef = 1042; optional uint32 fixed_array_size = 50007; - optional bool zero_copy = 1043 [default=false]; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 25156abf03..3b1f8d201f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1972,12 +1972,12 @@ bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt valu void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_uint32(2, this->handle); - buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size()); + buffer.encode_bytes(3, this->data_ptr_, this->data_len_); } void BluetoothGATTReadResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_uint64_field(total_size, 1, this->address); ProtoSize::add_uint32_field(total_size, 1, this->handle); - ProtoSize::add_string_field(total_size, 1, this->data); + ProtoSize::add_bytes_field(total_size, 1, this->data_len_); } bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2060,12 +2060,12 @@ bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt va void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); buffer.encode_uint32(2, this->handle); - buffer.encode_bytes(3, reinterpret_cast(this->data.data()), this->data.size()); + buffer.encode_bytes(3, this->data_ptr_, this->data_len_); } void BluetoothGATTNotifyDataResponse::calculate_size(uint32_t &total_size) const { ProtoSize::add_uint64_field(total_size, 1, this->address); ProtoSize::add_uint32_field(total_size, 1, this->handle); - ProtoSize::add_string_field(total_size, 1, this->data); + ProtoSize::add_bytes_field(total_size, 1, this->data_len_); } void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->free); @@ -2264,11 +2264,11 @@ bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited return true; } void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const { - buffer.encode_bytes(1, reinterpret_cast(this->data.data()), this->data.size()); + buffer.encode_bytes(1, this->data_ptr_, this->data_len_); buffer.encode_bool(2, this->end); } void VoiceAssistantAudio::calculate_size(uint32_t &total_size) const { - ProtoSize::add_string_field(total_size, 1, this->data); + ProtoSize::add_bytes_field(total_size, 1, this->data_len_); ProtoSize::add_bool_field(total_size, 1, this->end); } bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 7512431ccf..f116cfe4f9 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -991,7 +991,12 @@ class NoiseEncryptionSetKeyRequest : public ProtoDecodableMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_request"; } #endif - std::string key{}; + const uint8_t *key_ptr_{nullptr}; + size_t key_len_{0}; + void set_key(const uint8_t *data, size_t len) { + this->key_ptr_ = data; + this->key_len_ = len; + } #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1890,7 +1895,12 @@ class BluetoothGATTReadResponse : public ProtoMessage { #endif uint64_t address{0}; uint32_t handle{0}; - std::string data{}; + const uint8_t *data_ptr_{nullptr}; + size_t data_len_{0}; + void set_data(const uint8_t *data, size_t len) { + this->data_ptr_ = data; + this->data_len_ = len; + } void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1909,7 +1919,12 @@ class BluetoothGATTWriteRequest : public ProtoDecodableMessage { uint64_t address{0}; uint32_t handle{0}; bool response{false}; - std::string data{}; + const uint8_t *data_ptr_{nullptr}; + size_t data_len_{0}; + void set_data(const uint8_t *data, size_t len) { + this->data_ptr_ = data; + this->data_len_ = len; + } #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1943,7 +1958,12 @@ class BluetoothGATTWriteDescriptorRequest : public ProtoDecodableMessage { #endif uint64_t address{0}; uint32_t handle{0}; - std::string data{}; + const uint8_t *data_ptr_{nullptr}; + size_t data_len_{0}; + void set_data(const uint8_t *data, size_t len) { + this->data_ptr_ = data; + this->data_len_ = len; + } #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1978,7 +1998,12 @@ class BluetoothGATTNotifyDataResponse : public ProtoMessage { #endif uint64_t address{0}; uint32_t handle{0}; - std::string data{}; + const uint8_t *data_ptr_{nullptr}; + size_t data_len_{0}; + void set_data(const uint8_t *data, size_t len) { + this->data_ptr_ = data; + this->data_len_ = len; + } void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2271,7 +2296,12 @@ class VoiceAssistantAudio : public ProtoDecodableMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_audio"; } #endif - std::string data{}; + const uint8_t *data_ptr_{nullptr}; + size_t data_len_{0}; + void set_data(const uint8_t *data, size_t len) { + this->data_ptr_ = data; + this->data_len_ = len; + } bool end{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(uint32_t &total_size) const override; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 59d9aff209..7eb0da6a6f 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1679,7 +1679,7 @@ void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("NoiseEncryptionSetKeyRequest {\n"); out.append(" key: "); - out.append(format_hex_pretty(this->key)); + out.append(format_hex_pretty(this->key_ptr_, this->key_len_)); out.append("\n"); out.append("}"); } @@ -3141,7 +3141,7 @@ void BluetoothGATTReadResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append(format_hex_pretty(this->data)); + out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); out.append("\n"); out.append("}"); } @@ -3163,7 +3163,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append(format_hex_pretty(this->data)); + out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); out.append("\n"); out.append("}"); } @@ -3195,7 +3195,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append(format_hex_pretty(this->data)); + out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); out.append("\n"); out.append("}"); } @@ -3231,7 +3231,7 @@ void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append(format_hex_pretty(this->data)); + out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); out.append("\n"); out.append("}"); } @@ -3485,7 +3485,7 @@ void VoiceAssistantAudio::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("VoiceAssistantAudio {\n"); out.append(" data: "); - out.append(format_hex_pretty(this->data)); + out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); out.append("\n"); out.append(" end: "); diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index dae6e521bb..8e08d62504 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -235,9 +235,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga api::BluetoothGATTReadResponse resp; resp.address = this->address_; resp.handle = param->read.handle; - resp.data.reserve(param->read.value_len); - // Use bulk insert instead of individual push_backs - resp.data.insert(resp.data.end(), param->read.value, param->read.value + param->read.value_len); + resp.set_data(param->read.value, param->read.value_len); this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTReadResponse::MESSAGE_TYPE); break; } @@ -288,9 +286,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga api::BluetoothGATTNotifyDataResponse resp; resp.address = this->address_; resp.handle = param->notify.handle; - resp.data.reserve(param->notify.value_len); - // Use bulk insert instead of individual push_backs - resp.data.insert(resp.data.end(), param->notify.value, param->notify.value + param->notify.value_len); + resp.set_data(param->notify.value, param->notify.value_len); this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTNotifyDataResponse::MESSAGE_TYPE); break; } @@ -337,7 +333,8 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { return ESP_OK; } -esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) { +esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8_t *data, size_t data_len, + bool response) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_, this->address_str_.c_str()); @@ -347,7 +344,7 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std:: handle); esp_err_t err = - esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), + esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data_len, const_cast(data), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_, @@ -375,7 +372,7 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { return ESP_OK; } -esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) { +esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *data, size_t data_len, bool response) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_, this->address_str_.c_str()); @@ -385,7 +382,7 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri handle); esp_err_t err = esp_ble_gattc_write_char_descr( - this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), + this->gattc_if_, this->conn_id_, handle, data_len, const_cast(data), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_, diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index 2673238fba..c9c4b7c901 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -19,9 +19,9 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; esp_err_t read_characteristic(uint16_t handle); - esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); + esp_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t data_len, bool response); esp_err_t read_descriptor(uint16_t handle); - esp_err_t write_descriptor(uint16_t handle, const std::string &data, bool response); + esp_err_t write_descriptor(uint16_t handle, const uint8_t *data, size_t data_len, bool response); esp_err_t notify_characteristic(uint16_t handle, bool enable); diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 8a1a2bff6a..66423d201d 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -344,7 +344,7 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest & return; } - auto err = connection->write_characteristic(msg.handle, msg.data, msg.response); + auto err = connection->write_characteristic(msg.handle, msg.data_ptr_, msg.data_len_, msg.response); if (err != ESP_OK) { this->send_gatt_error(msg.address, msg.handle, err); } @@ -372,7 +372,7 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri return; } - auto err = connection->write_descriptor(msg.handle, msg.data, true); + auto err = connection->write_descriptor(msg.handle, msg.data_ptr_, msg.data_len_, true); if (err != ESP_OK) { this->send_gatt_error(msg.address, msg.handle, err); } diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 3c69dafa43..01e93dec5e 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -273,7 +273,7 @@ void VoiceAssistant::loop() { size_t read_bytes = this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0); if (this->audio_mode_ == AUDIO_MODE_API) { api::VoiceAssistantAudio msg; - msg.data.assign((char *) this->send_buffer_, read_bytes); + msg.set_data(this->send_buffer_, read_bytes); this->api_client_->send_message(msg, api::VoiceAssistantAudio::MESSAGE_TYPE); } else { if (!this->udp_socket_running_) { @@ -839,12 +839,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) { #ifdef USE_SPEAKER // We should never get to this function if there is no speaker anyway if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { - if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) { - memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length()); - this->speaker_buffer_index_ += msg.data.length(); - this->speaker_buffer_size_ += msg.data.length(); - this->speaker_bytes_received_ += msg.data.length(); - ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length()); + if (this->speaker_buffer_index_ + msg.data_len_ < SPEAKER_BUFFER_SIZE) { + memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data_ptr_, msg.data_len_); + this->speaker_buffer_index_ += msg.data_len_; + this->speaker_buffer_size_ += msg.data_len_; + this->speaker_bytes_received_ += msg.data_len_; + ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data_len_); } else { ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); } diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 5569f29818..213b6fd332 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -325,10 +325,6 @@ def create_field_type_info(field: descriptor.FieldDescriptorProto) -> TypeInfo: ): return FixedArrayBytesType(field, fixed_size) - # Check for zero_copy option on bytes fields - if field.type == 12 and get_field_opt(field, pb.zero_copy, default=False): - return ZeroCopyBytesType(field) - validate_field_type(field.type, field.name) return TYPE_INFO[field.type](field) @@ -597,32 +593,6 @@ class BytesType(TypeInfo): encode_func = "encode_bytes" wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2 - @property - def encode_content(self) -> str: - return f"buffer.encode_bytes({self.number}, reinterpret_cast(this->{self.field_name}.data()), this->{self.field_name}.size());" - - def dump(self, name: str) -> str: - o = f"out.append(format_hex_pretty({name}));" - return o - - def get_size_calculation(self, name: str, force: bool = False) -> str: - return self._get_simple_size_calculation(name, force, "add_string_field") - - def get_estimated_size(self) -> int: - return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes - - -class ZeroCopyBytesType(TypeInfo): - """Special type for zero-copy bytes fields that only accepts const uint8_t* data.""" - - cpp_type = "std::string" # Still store as string for compatibility - default_value = "" - reference_type = "std::string &" - const_reference_type = "const std::string &" - encode_func = "encode_bytes" - wire_type = WireType.LENGTH_DELIMITED - decode_length = "value.as_string()" - @property def public_content(self) -> list[str]: # Store both pointer and length for zero-copy encoding, plus setter method @@ -637,14 +607,12 @@ class ZeroCopyBytesType(TypeInfo): @property def encode_content(self) -> str: - # Encode directly from pointer without nullptr check (like original) return f"buffer.encode_bytes({self.number}, this->{self.field_name}_ptr_, this->{self.field_name}_len_);" def dump(self, name: str) -> str: return f"out.append(format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_));" def get_size_calculation(self, name: str, force: bool = False) -> str: - # Use the new add_bytes_field helper return f"ProtoSize::add_bytes_field(total_size, {self.calculate_field_id_size()}, this->{self.field_name}_len_);" def get_estimated_size(self) -> int: From 1dc736e27aa033e432465cbb02a20e6dd8ff850c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:28:07 -1000 Subject: [PATCH 04/14] preen --- esphome/components/api/api_connection.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index c3cee7d80d..3031ac58f7 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -231,9 +231,7 @@ void APIConnection::loop() { msg.set_data(this->image_reader_->peek_data_buffer(), to_send); msg.done = done; - bool success = this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE); - - if (success) { + if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) { this->image_reader_->consume_data(to_send); if (done) { this->image_reader_->return_image(); From 9cb86241b93d73adcadc2dd09a6c056df8109563 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:40:21 -1000 Subject: [PATCH 05/14] cleanup --- esphome/components/api/api_pb2.cpp | 8 ++++ esphome/components/api/api_pb2.h | 4 ++ script/api_protobuf/api_protobuf.py | 68 ++++++++++++++++++++++------- 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 3b1f8d201f..c3814e8089 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -831,6 +831,8 @@ bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthD switch (field_id) { case 1: this->key = value.as_string(); + this->key_ptr_ = reinterpret_cast(this->key.data()); + this->key_len_ = this->key.size(); break; default: return false; @@ -1999,6 +2001,8 @@ bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDeli switch (field_id) { case 4: this->data = value.as_string(); + this->data_ptr_ = reinterpret_cast(this->data.data()); + this->data_len_ = this->data.size(); break; default: return false; @@ -2035,6 +2039,8 @@ bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, Proto switch (field_id) { case 3: this->data = value.as_string(); + this->data_ptr_ = reinterpret_cast(this->data.data()); + this->data_len_ = this->data.size(); break; default: return false; @@ -2257,6 +2263,8 @@ bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited switch (field_id) { case 1: this->data = value.as_string(); + this->data_ptr_ = reinterpret_cast(this->data.data()); + this->data_len_ = this->data.size(); break; default: return false; diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index f116cfe4f9..b357b4cf54 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -993,6 +993,7 @@ class NoiseEncryptionSetKeyRequest : public ProtoDecodableMessage { #endif const uint8_t *key_ptr_{nullptr}; size_t key_len_{0}; + std::string key{}; // Storage for decoded data void set_key(const uint8_t *data, size_t len) { this->key_ptr_ = data; this->key_len_ = len; @@ -1921,6 +1922,7 @@ class BluetoothGATTWriteRequest : public ProtoDecodableMessage { bool response{false}; const uint8_t *data_ptr_{nullptr}; size_t data_len_{0}; + std::string data{}; // Storage for decoded data void set_data(const uint8_t *data, size_t len) { this->data_ptr_ = data; this->data_len_ = len; @@ -1960,6 +1962,7 @@ class BluetoothGATTWriteDescriptorRequest : public ProtoDecodableMessage { uint32_t handle{0}; const uint8_t *data_ptr_{nullptr}; size_t data_len_{0}; + std::string data{}; // Storage for decoded data void set_data(const uint8_t *data, size_t len) { this->data_ptr_ = data; this->data_len_ = len; @@ -2298,6 +2301,7 @@ class VoiceAssistantAudio : public ProtoDecodableMessage { #endif const uint8_t *data_ptr_{nullptr}; size_t data_len_{0}; + std::string data{}; // Storage for decoded data void set_data(const uint8_t *data, size_t len) { this->data_ptr_ = data; this->data_len_ = len; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 213b6fd332..a51fdc233b 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -313,7 +313,9 @@ def validate_field_type(field_type: int, field_name: str = "") -> None: ) -def create_field_type_info(field: descriptor.FieldDescriptorProto) -> TypeInfo: +def create_field_type_info( + field: descriptor.FieldDescriptorProto, needs_decode: bool = True +) -> TypeInfo: """Create the appropriate TypeInfo instance for a field, handling repeated fields and custom options.""" if field.label == 3: # repeated return RepeatedTypeInfo(field) @@ -325,6 +327,10 @@ def create_field_type_info(field: descriptor.FieldDescriptorProto) -> TypeInfo: ): return FixedArrayBytesType(field, fixed_size) + # Special handling for bytes fields + if field.type == 12: + return BytesType(field, needs_decode) + validate_field_type(field.type, field.name) return TYPE_INFO[field.type](field) @@ -589,22 +595,54 @@ class BytesType(TypeInfo): default_value = "" reference_type = "std::string &" const_reference_type = "const std::string &" - decode_length = "value.as_string()" encode_func = "encode_bytes" wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2 + def __init__( + self, field: descriptor.FieldDescriptorProto, needs_decode: bool = True + ) -> None: + super().__init__(field) + self.needs_decode = needs_decode + @property def public_content(self) -> list[str]: # Store both pointer and length for zero-copy encoding, plus setter method - return [ + content = [ f"const uint8_t* {self.field_name}_ptr_{{nullptr}};", f"size_t {self.field_name}_len_{{0}};", - f"void set_{self.field_name}(const uint8_t* data, size_t len) {{", - f" this->{self.field_name}_ptr_ = data;", - f" this->{self.field_name}_len_ = len;", - "}", ] + # Only add storage if message needs decoding + if self.needs_decode: + content.append( + f"std::string {self.field_name}{{}}; // Storage for decoded data" + ) + + content.extend( + [ + f"void set_{self.field_name}(const uint8_t* data, size_t len) {{", + f" this->{self.field_name}_ptr_ = data;", + f" this->{self.field_name}_len_ = len;", + "}", + ] + ) + + return content + + @property + def decode_length_content(self) -> str: + if not self.needs_decode: + return "" # No decode needed for SOURCE_SERVER messages + + # Decode into storage and update pointer/length + return ( + f"case {self.number}:\n" + f" this->{self.field_name} = value.as_string();\n" + f" this->{self.field_name}_ptr_ = reinterpret_cast(this->{self.field_name}.data());\n" + f" this->{self.field_name}_len_ = this->{self.field_name}.size();\n" + f" break;" + ) + @property def encode_content(self) -> str: return f"buffer.encode_bytes({self.number}, this->{self.field_name}_ptr_, this->{self.field_name}_len_);" @@ -1268,7 +1306,7 @@ def build_message_type( if field.options.deprecated: continue - ti = create_field_type_info(field) + ti = create_field_type_info(field, needs_decode) # Skip field declarations for fields that are in the base class # but include their encode/decode logic @@ -1583,10 +1621,16 @@ def build_base_class( public_content = [] protected_content = [] + # Determine if any message using this base class needs decoding + needs_decode = any( + message_source_map.get(msg.name, SOURCE_BOTH) in (SOURCE_BOTH, SOURCE_CLIENT) + for msg in messages + ) + # For base classes, we only declare the fields but don't handle encode/decode # The derived classes will handle encoding/decoding with their specific field numbers for field in common_fields: - ti = create_field_type_info(field) + ti = create_field_type_info(field, needs_decode) # Get field_ifdef if it's consistent across all messages field_ifdef = get_common_field_ifdef(field.name, messages) @@ -1597,12 +1641,6 @@ def build_base_class( if ti.public_content: public_content.extend(wrap_with_ifdef(ti.public_content, field_ifdef)) - # Determine if any message using this base class needs decoding - needs_decode = any( - message_source_map.get(msg.name, SOURCE_BOTH) in (SOURCE_BOTH, SOURCE_CLIENT) - for msg in messages - ) - # Build header parent_class = "ProtoDecodableMessage" if needs_decode else "ProtoMessage" out = f"class {base_class_name} : public {parent_class} {{\n" From ae7aa4c0efefd257832a9b7bf772afcb3f8ab591 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:46:41 -1000 Subject: [PATCH 06/14] preen --- esphome/components/api/api_pb2.cpp | 8 -------- script/api_protobuf/api_protobuf.py | 4 +--- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index c3814e8089..3b1f8d201f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -831,8 +831,6 @@ bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthD switch (field_id) { case 1: this->key = value.as_string(); - this->key_ptr_ = reinterpret_cast(this->key.data()); - this->key_len_ = this->key.size(); break; default: return false; @@ -2001,8 +1999,6 @@ bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDeli switch (field_id) { case 4: this->data = value.as_string(); - this->data_ptr_ = reinterpret_cast(this->data.data()); - this->data_len_ = this->data.size(); break; default: return false; @@ -2039,8 +2035,6 @@ bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, Proto switch (field_id) { case 3: this->data = value.as_string(); - this->data_ptr_ = reinterpret_cast(this->data.data()); - this->data_len_ = this->data.size(); break; default: return false; @@ -2263,8 +2257,6 @@ bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited switch (field_id) { case 1: this->data = value.as_string(); - this->data_ptr_ = reinterpret_cast(this->data.data()); - this->data_len_ = this->data.size(); break; default: return false; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index a51fdc233b..adb103b665 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -634,12 +634,10 @@ class BytesType(TypeInfo): if not self.needs_decode: return "" # No decode needed for SOURCE_SERVER messages - # Decode into storage and update pointer/length + # Decode into storage only - pointer/length are only needed for encoding return ( f"case {self.number}:\n" f" this->{self.field_name} = value.as_string();\n" - f" this->{self.field_name}_ptr_ = reinterpret_cast(this->{self.field_name}.data());\n" - f" this->{self.field_name}_len_ = this->{self.field_name}.size();\n" f" break;" ) From 8b09a5259e56c3ffc76bc5c78d3e4c42cfd4d07c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:48:19 -1000 Subject: [PATCH 07/14] preen --- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 66423d201d..e5bc626d1b 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -344,7 +344,8 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest & return; } - auto err = connection->write_characteristic(msg.handle, msg.data_ptr_, msg.data_len_, msg.response); + auto err = connection->write_characteristic(msg.handle, reinterpret_cast(msg.data.data()), + msg.data.size(), msg.response); if (err != ESP_OK) { this->send_gatt_error(msg.address, msg.handle, err); } @@ -372,7 +373,8 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri return; } - auto err = connection->write_descriptor(msg.handle, msg.data_ptr_, msg.data_len_, true); + auto err = connection->write_descriptor(msg.handle, reinterpret_cast(msg.data.data()), + msg.data.size(), true); if (err != ESP_OK) { this->send_gatt_error(msg.address, msg.handle, err); } From 5fb97e8e3ce720e2431aea1b36799548247367fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:50:55 -1000 Subject: [PATCH 08/14] preen --- esphome/components/api/api_connection.cpp | 4 +--- .../bluetooth_proxy/bluetooth_connection.cpp | 9 ++++----- .../bluetooth_proxy/bluetooth_connection.h | 4 ++-- .../components/bluetooth_proxy/bluetooth_proxy.cpp | 6 ++---- .../components/voice_assistant/voice_assistant.cpp | 12 ++++++------ 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 3031ac58f7..61fff064b2 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1495,9 +1495,7 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) { psk_t psk{}; NoiseEncryptionSetKeyResponse resp; - // Create temporary string from pointer/length for base64_decode - std::string key_str(reinterpret_cast(msg.key_ptr_), msg.key_len_); - if (base64_decode(key_str, psk.data(), key_str.size()) != psk.size()) { + if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); resp.success = false; return resp; diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 8e08d62504..b3b271a027 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -333,8 +333,7 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { return ESP_OK; } -esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8_t *data, size_t data_len, - bool response) { +esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_, this->address_str_.c_str()); @@ -344,7 +343,7 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8 handle); esp_err_t err = - esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data_len, const_cast(data), + esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_, @@ -372,7 +371,7 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { return ESP_OK; } -esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *data, size_t data_len, bool response) { +esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_, this->address_str_.c_str()); @@ -382,7 +381,7 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t * handle); esp_err_t err = esp_ble_gattc_write_char_descr( - this->gattc_if_, this->conn_id_, handle, data_len, const_cast(data), + this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_, diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index c9c4b7c901..2673238fba 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -19,9 +19,9 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; esp_err_t read_characteristic(uint16_t handle); - esp_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t data_len, bool response); + esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); esp_err_t read_descriptor(uint16_t handle); - esp_err_t write_descriptor(uint16_t handle, const uint8_t *data, size_t data_len, bool response); + esp_err_t write_descriptor(uint16_t handle, const std::string &data, bool response); esp_err_t notify_characteristic(uint16_t handle, bool enable); diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index e5bc626d1b..8a1a2bff6a 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -344,8 +344,7 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest & return; } - auto err = connection->write_characteristic(msg.handle, reinterpret_cast(msg.data.data()), - msg.data.size(), msg.response); + auto err = connection->write_characteristic(msg.handle, msg.data, msg.response); if (err != ESP_OK) { this->send_gatt_error(msg.address, msg.handle, err); } @@ -373,8 +372,7 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri return; } - auto err = connection->write_descriptor(msg.handle, reinterpret_cast(msg.data.data()), - msg.data.size(), true); + auto err = connection->write_descriptor(msg.handle, msg.data, true); if (err != ESP_OK) { this->send_gatt_error(msg.address, msg.handle, err); } diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 01e93dec5e..c35a0814b8 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -839,12 +839,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) { #ifdef USE_SPEAKER // We should never get to this function if there is no speaker anyway if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { - if (this->speaker_buffer_index_ + msg.data_len_ < SPEAKER_BUFFER_SIZE) { - memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data_ptr_, msg.data_len_); - this->speaker_buffer_index_ += msg.data_len_; - this->speaker_buffer_size_ += msg.data_len_; - this->speaker_bytes_received_ += msg.data_len_; - ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data_len_); + if (this->speaker_buffer_index_ + msg.data.size() < SPEAKER_BUFFER_SIZE) { + memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.size()); + this->speaker_buffer_index_ += msg.data.size(); + this->speaker_buffer_size_ += msg.data.size(); + this->speaker_bytes_received_ += msg.data.size(); + ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.size()); } else { ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); } From 8b74333e8b66ddfbf435aca652f20adf28a4b512 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:52:26 -1000 Subject: [PATCH 09/14] preen --- .../components/voice_assistant/voice_assistant.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index c35a0814b8..481a4efa62 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -839,12 +839,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) { #ifdef USE_SPEAKER // We should never get to this function if there is no speaker anyway if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { - if (this->speaker_buffer_index_ + msg.data.size() < SPEAKER_BUFFER_SIZE) { - memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.size()); - this->speaker_buffer_index_ += msg.data.size(); - this->speaker_buffer_size_ += msg.data.size(); - this->speaker_bytes_received_ += msg.data.size(); - ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.size()); + if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) { + memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length()); + this->speaker_buffer_index_ += msg.data.length(); + this->speaker_buffer_size_ += msg.data.length(); + this->speaker_bytes_received_ += msg.data.length(); + ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length()); } else { ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); } From 04953db51e3b0d1bca762591c0ba4f50a5955e2c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:56:42 -1000 Subject: [PATCH 10/14] cleanup --- esphome/components/api/api_pb2.h | 18 --------- script/api_protobuf/api_protobuf.py | 57 +++++++++++++++++++---------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index b357b4cf54..728250baf4 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -991,13 +991,7 @@ class NoiseEncryptionSetKeyRequest : public ProtoDecodableMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_request"; } #endif - const uint8_t *key_ptr_{nullptr}; - size_t key_len_{0}; std::string key{}; // Storage for decoded data - void set_key(const uint8_t *data, size_t len) { - this->key_ptr_ = data; - this->key_len_ = len; - } #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1920,13 +1914,7 @@ class BluetoothGATTWriteRequest : public ProtoDecodableMessage { uint64_t address{0}; uint32_t handle{0}; bool response{false}; - const uint8_t *data_ptr_{nullptr}; - size_t data_len_{0}; std::string data{}; // Storage for decoded data - void set_data(const uint8_t *data, size_t len) { - this->data_ptr_ = data; - this->data_len_ = len; - } #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1960,13 +1948,7 @@ class BluetoothGATTWriteDescriptorRequest : public ProtoDecodableMessage { #endif uint64_t address{0}; uint32_t handle{0}; - const uint8_t *data_ptr_{nullptr}; - size_t data_len_{0}; std::string data{}; // Storage for decoded data - void set_data(const uint8_t *data, size_t len) { - this->data_ptr_ = data; - this->data_len_ = len; - } #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index adb103b665..4b6ed891a8 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -314,7 +314,9 @@ def validate_field_type(field_type: int, field_name: str = "") -> None: def create_field_type_info( - field: descriptor.FieldDescriptorProto, needs_decode: bool = True + field: descriptor.FieldDescriptorProto, + needs_decode: bool = True, + needs_encode: bool = True, ) -> TypeInfo: """Create the appropriate TypeInfo instance for a field, handling repeated fields and custom options.""" if field.label == 3: # repeated @@ -329,7 +331,7 @@ def create_field_type_info( # Special handling for bytes fields if field.type == 12: - return BytesType(field, needs_decode) + return BytesType(field, needs_decode, needs_encode) validate_field_type(field.type, field.name) return TYPE_INFO[field.type](field) @@ -599,33 +601,44 @@ class BytesType(TypeInfo): wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2 def __init__( - self, field: descriptor.FieldDescriptorProto, needs_decode: bool = True + self, + field: descriptor.FieldDescriptorProto, + needs_decode: bool = True, + needs_encode: bool = True, ) -> None: super().__init__(field) self.needs_decode = needs_decode + self.needs_encode = needs_encode @property def public_content(self) -> list[str]: - # Store both pointer and length for zero-copy encoding, plus setter method - content = [ - f"const uint8_t* {self.field_name}_ptr_{{nullptr}};", - f"size_t {self.field_name}_len_{{0}};", - ] + content = [] - # Only add storage if message needs decoding + # Add pointer/length fields if message needs encoding + if self.needs_encode: + content.extend( + [ + f"const uint8_t* {self.field_name}_ptr_{{nullptr}};", + f"size_t {self.field_name}_len_{{0}};", + ] + ) + + # Add std::string storage if message needs decoding if self.needs_decode: content.append( f"std::string {self.field_name}{{}}; // Storage for decoded data" ) - content.extend( - [ - f"void set_{self.field_name}(const uint8_t* data, size_t len) {{", - f" this->{self.field_name}_ptr_ = data;", - f" this->{self.field_name}_len_ = len;", - "}", - ] - ) + # Add setter method if message needs encoding + if self.needs_encode: + content.extend( + [ + f"void set_{self.field_name}(const uint8_t* data, size_t len) {{", + f" this->{self.field_name}_ptr_ = data;", + f" this->{self.field_name}_len_ = len;", + "}", + ] + ) return content @@ -1304,7 +1317,7 @@ def build_message_type( if field.options.deprecated: continue - ti = create_field_type_info(field, needs_decode) + ti = create_field_type_info(field, needs_decode, needs_encode) # Skip field declarations for fields that are in the base class # but include their encode/decode logic @@ -1619,16 +1632,20 @@ def build_base_class( public_content = [] protected_content = [] - # Determine if any message using this base class needs decoding + # Determine if any message using this base class needs decoding/encoding needs_decode = any( message_source_map.get(msg.name, SOURCE_BOTH) in (SOURCE_BOTH, SOURCE_CLIENT) for msg in messages ) + needs_encode = any( + message_source_map.get(msg.name, SOURCE_BOTH) in (SOURCE_BOTH, SOURCE_SERVER) + for msg in messages + ) # For base classes, we only declare the fields but don't handle encode/decode # The derived classes will handle encoding/decoding with their specific field numbers for field in common_fields: - ti = create_field_type_info(field, needs_decode) + ti = create_field_type_info(field, needs_decode, needs_encode) # Get field_ifdef if it's consistent across all messages field_ifdef = get_common_field_ifdef(field.name, messages) From ad52d80281239da9aea108e1de256b909ef3bddf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 19:57:32 -1000 Subject: [PATCH 11/14] cleanup --- esphome/components/api/api_pb2.h | 8 ++++---- script/api_protobuf/api_protobuf.py | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 728250baf4..1f91c5c179 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -991,7 +991,7 @@ class NoiseEncryptionSetKeyRequest : public ProtoDecodableMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_request"; } #endif - std::string key{}; // Storage for decoded data + std::string key{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1914,7 +1914,7 @@ class BluetoothGATTWriteRequest : public ProtoDecodableMessage { uint64_t address{0}; uint32_t handle{0}; bool response{false}; - std::string data{}; // Storage for decoded data + std::string data{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -1948,7 +1948,7 @@ class BluetoothGATTWriteDescriptorRequest : public ProtoDecodableMessage { #endif uint64_t address{0}; uint32_t handle{0}; - std::string data{}; // Storage for decoded data + std::string data{}; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif @@ -2283,7 +2283,7 @@ class VoiceAssistantAudio : public ProtoDecodableMessage { #endif const uint8_t *data_ptr_{nullptr}; size_t data_len_{0}; - std::string data{}; // Storage for decoded data + std::string data{}; void set_data(const uint8_t *data, size_t len) { this->data_ptr_ = data; this->data_len_ = len; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 4b6ed891a8..b23f788256 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -625,9 +625,7 @@ class BytesType(TypeInfo): # Add std::string storage if message needs decoding if self.needs_decode: - content.append( - f"std::string {self.field_name}{{}}; // Storage for decoded data" - ) + content.append(f"std::string {self.field_name}{{}};") # Add setter method if message needs encoding if self.needs_encode: From 5e906b1dd935fd8ffefade1e46aebc1568022863 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 20:06:50 -1000 Subject: [PATCH 12/14] cleanup --- esphome/components/api/api_pb2_dump.cpp | 6 +++--- script/api_protobuf/api_protobuf.py | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 7eb0da6a6f..bf4ca5e402 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1679,7 +1679,7 @@ void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("NoiseEncryptionSetKeyRequest {\n"); out.append(" key: "); - out.append(format_hex_pretty(this->key_ptr_, this->key_len_)); + out.append(format_hex_pretty(reinterpret_cast(this->key.data()), this->key.size())); out.append("\n"); out.append("}"); } @@ -3163,7 +3163,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); + out.append(format_hex_pretty(reinterpret_cast(this->data.data()), this->data.size())); out.append("\n"); out.append("}"); } @@ -3195,7 +3195,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { out.append("\n"); out.append(" data: "); - out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); + out.append(format_hex_pretty(reinterpret_cast(this->data.data()), this->data.size())); out.append("\n"); out.append("}"); } diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index b23f788256..298f0a6d18 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -657,7 +657,11 @@ class BytesType(TypeInfo): return f"buffer.encode_bytes({self.number}, this->{self.field_name}_ptr_, this->{self.field_name}_len_);" def dump(self, name: str) -> str: - return f"out.append(format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_));" + # Use pointer/length if available (SOURCE_SERVER/SOURCE_BOTH), otherwise use std::string + if self.needs_encode: + return f"out.append(format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_));" + else: + return f"out.append(format_hex_pretty(reinterpret_cast(this->{self.field_name}.data()), this->{self.field_name}.size()));" def get_size_calculation(self, name: str, force: bool = False) -> str: return f"ProtoSize::add_bytes_field(total_size, {self.calculate_field_id_size()}, this->{self.field_name}_len_);" From b24ff7236e3010c64b81b8405ab142d718ba9775 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 20:07:30 -1000 Subject: [PATCH 13/14] cleanup --- esphome/components/api/api_pb2_dump.cpp | 6 +++++- script/api_protobuf/api_protobuf.py | 19 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index bf4ca5e402..e5120007ae 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -3485,7 +3485,11 @@ void VoiceAssistantAudio::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("VoiceAssistantAudio {\n"); out.append(" data: "); - out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); + if (this->data_ptr_ != nullptr) { + out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); + } else { + out.append(format_hex_pretty(reinterpret_cast(this->data.data()), this->data.size())); + } out.append("\n"); out.append(" end: "); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 298f0a6d18..0cfa7fc344 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -657,12 +657,23 @@ class BytesType(TypeInfo): return f"buffer.encode_bytes({self.number}, this->{self.field_name}_ptr_, this->{self.field_name}_len_);" def dump(self, name: str) -> str: - # Use pointer/length if available (SOURCE_SERVER/SOURCE_BOTH), otherwise use std::string - if self.needs_encode: - return f"out.append(format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_));" - else: + # For SOURCE_CLIENT only, always use std::string + if not self.needs_encode: return f"out.append(format_hex_pretty(reinterpret_cast(this->{self.field_name}.data()), this->{self.field_name}.size()));" + # For SOURCE_SERVER, always use pointer/length + if not self.needs_decode: + return f"out.append(format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_));" + + # For SOURCE_BOTH, check if pointer is set (sending) or use string (received) + return ( + f"if (this->{self.field_name}_ptr_ != nullptr) {{\n" + f" out.append(format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_));\n" + f" }} else {{\n" + f" out.append(format_hex_pretty(reinterpret_cast(this->{self.field_name}.data()), this->{self.field_name}.size()));\n" + f" }}" + ) + def get_size_calculation(self, name: str, force: bool = False) -> str: return f"ProtoSize::add_bytes_field(total_size, {self.calculate_field_id_size()}, this->{self.field_name}_len_);" From 8c11241af03010d967dbaaf9e4774352035bee73 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Jul 2025 20:08:57 -1000 Subject: [PATCH 14/14] cleanup --- script/api_protobuf/api_protobuf.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 0cfa7fc344..96471d33b2 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -657,20 +657,23 @@ class BytesType(TypeInfo): return f"buffer.encode_bytes({self.number}, this->{self.field_name}_ptr_, this->{self.field_name}_len_);" def dump(self, name: str) -> str: + ptr_dump = f"format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_)" + str_dump = f"format_hex_pretty(reinterpret_cast(this->{self.field_name}.data()), this->{self.field_name}.size())" + # For SOURCE_CLIENT only, always use std::string if not self.needs_encode: - return f"out.append(format_hex_pretty(reinterpret_cast(this->{self.field_name}.data()), this->{self.field_name}.size()));" + return f"out.append({str_dump});" # For SOURCE_SERVER, always use pointer/length if not self.needs_decode: - return f"out.append(format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_));" + return f"out.append({ptr_dump});" # For SOURCE_BOTH, check if pointer is set (sending) or use string (received) return ( f"if (this->{self.field_name}_ptr_ != nullptr) {{\n" - f" out.append(format_hex_pretty(this->{self.field_name}_ptr_, this->{self.field_name}_len_));\n" + f" out.append({ptr_dump});\n" f" }} else {{\n" - f" out.append(format_hex_pretty(reinterpret_cast(this->{self.field_name}.data()), this->{self.field_name}.size()));\n" + f" out.append({str_dump});\n" f" }}" )