diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5149d6d9ac..6c3db30858 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -331,8 +331,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess std::vector &shared_buf = conn->parent_->get_shared_buffer_ref(); if (conn->flags_.batch_first_message) { - // Single message or first batch message - conn->prepare_first_message_buffer(shared_buf, header_padding, total_calculated_size); + // First message - buffer already prepared by caller, just clear the flag conn->flags_.batch_first_message = false; } else { // Batch message second or later @@ -1860,9 +1859,10 @@ void APIConnection::process_batch_() { // Fast path for single message - allocate exact size needed if (num_items == 1) { const auto &item = this->deferred_batch_[0]; + this->prepare_first_message_buffer(shared_buf, item.estimated_size); // Let dispatch_message_ calculate size and encode if it fits - uint16_t payload_size = this->dispatch_message_(item, std::numeric_limits::max(), true); + uint16_t payload_size = this->dispatch_message_(item, std::numeric_limits::max()); if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) { #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1889,19 +1889,16 @@ void APIConnection::process_batch_() { const uint8_t header_padding = this->helper_->frame_header_padding(); const uint8_t footer_size = this->helper_->frame_footer_size(); - // Initialize buffer and tracking variables - shared_buf.clear(); - - // Pre-calculate exact buffer size needed based on message types + // Calculate total overhead for all messages + // Reserve based on estimated size (much more accurate than 24-byte worst-case) uint32_t total_estimated_size = num_items * (header_padding + footer_size); for (size_t i = 0; i < this->deferred_batch_.size(); i++) { const auto &item = this->deferred_batch_[i]; total_estimated_size += item.estimated_size; } - // Calculate total overhead for all messages - // Reserve based on estimated size (much more accurate than 24-byte worst-case) - shared_buf.reserve(total_estimated_size); + // Initialize buffer with total estimated size and set batch_first_message flag + this->prepare_first_message_buffer(shared_buf, header_padding, total_estimated_size); size_t items_processed = 0; uint16_t remaining_size = std::numeric_limits::max(); @@ -1917,7 +1914,7 @@ void APIConnection::process_batch_() { const auto &item = this->deferred_batch_[i]; // Try to encode message via dispatch // The dispatch function calculates overhead to determine if the message fits - uint16_t payload_size = this->dispatch_message_(item, remaining_size, i == 0); + uint16_t payload_size = this->dispatch_message_(item, remaining_size); if (payload_size == 0) { // Message won't fit, stop processing @@ -1985,9 +1982,7 @@ void APIConnection::process_batch_() { // Dispatch message encoding based on message_type // Switch assigns function pointer, single call site for smaller code size -uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, - bool batch_first) { - this->flags_.batch_first_message = batch_first; +uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size) { #ifdef USE_EVENT // Events need aux_data_index to look up event type from entity if (item.message_type == EventResponse::MESSAGE_TYPE) { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 2c9202b561..9b5717c71a 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -275,6 +275,15 @@ class APIConnection final : public APIServerConnection { shared_buf.reserve(total_size); // Resize to add header padding so message encoding starts at the correct position shared_buf.resize(header_padding); + // Mark that the next encode should use fresh buffer positioning + this->flags_.batch_first_message = true; + } + + // Convenience overload - computes frame overhead internally + void prepare_first_message_buffer(std::vector &shared_buf, size_t payload_size) { + const uint8_t header_padding = this->helper_->frame_header_padding(); + const uint8_t footer_size = this->helper_->frame_footer_size(); + this->prepare_first_message_buffer(shared_buf, header_padding, payload_size + header_padding + footer_size); } bool try_to_clear_buffer(bool log_out_of_space); @@ -619,12 +628,12 @@ class APIConnection final : public APIServerConnection { // Dispatch message encoding based on message_type - replaces function pointer storage // Switch assigns pointer, single call site for smaller code size - uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool batch_first); + uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size); #ifdef HAS_PROTO_MESSAGE_DUMP void log_batch_item_(const DeferredBatch::BatchItem &item) { this->flags_.log_only_mode = true; - this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true); + this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE); this->flags_.log_only_mode = false; } #endif @@ -653,9 +662,11 @@ class APIConnection final : public APIServerConnection { bool send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) { if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) { + auto &shared_buf = this->parent_->get_shared_buffer_ref(); + this->prepare_first_message_buffer(shared_buf, estimated_size); DeferredBatch::BatchItem item{entity, message_type, estimated_size, aux_data_index}; - if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true) && - this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { + if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE) && + this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type)) { #ifdef HAS_PROTO_MESSAGE_DUMP this->log_batch_item_(item); #endif