diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2f8c6f4abd..bddfe759e1 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1998,11 +1998,7 @@ bool APIConnection::send_message_(uint32_t payload_size, uint8_t message_type, M } // encode_to_buffer is defined inline in api_connection.h (ESPHOME_ALWAYS_INLINE) -// Noinline wrapper for zero-payload messages — single shared copy instead of -// duplicating encode_to_buffer at each cold call site. -uint16_t APIConnection::encode_empty_to_buffer(const void *msg, APIConnection *conn, uint32_t remaining_size) { - return encode_to_buffer(0, &encode_msg_noop, msg, conn, remaining_size); -} +// encode_empty_to_buffer is defined inline in api_connection.h bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 10972e0486..0fb7025608 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -410,53 +410,71 @@ class APIConnection final : public APIServerConnectionBase { uint32_t remaining_size) { #ifdef HAS_PROTO_MESSAGE_DUMP if (conn->flags_.log_only_mode) { - auto *proto_msg = static_cast(msg); - DumpBuffer dump_buf; - conn->log_send_message_(proto_msg->message_name(), proto_msg->dump_to(dump_buf)); + conn->log_send_message_(msg); return 1; } #endif - // Cache frame sizes to avoid repeated virtual calls - const uint8_t header_padding = conn->helper_->frame_header_padding(); const uint8_t footer_size = conn->helper_->frame_footer_size(); - // Calculate total size with padding for buffer allocation - size_t total_calculated_size = calculated_size + header_padding + footer_size; - - // Check if it fits - if (total_calculated_size > remaining_size) - return 0; // Doesn't fit - - auto &shared_buf = conn->parent_->get_shared_buffer_ref(); - - // First message: padding already in buffer, only add payload. - // Subsequent messages: use exact header size for gap-free packing. + // First message uses max padding (already in buffer), subsequent use exact header size size_t to_add; if (conn->flags_.batch_first_message) { conn->flags_.batch_first_message = false; - conn->batch_header_size_ = header_padding; + conn->batch_header_size_ = conn->helper_->frame_header_padding(); to_add = calculated_size; } else { conn->batch_header_size_ = conn->helper_->frame_header_size(calculated_size, conn->batch_message_type_); to_add = calculated_size + conn->batch_header_size_ + footer_size; } + // Check if it fits (using actual header size, not max padding) + uint16_t total_calculated_size = calculated_size + conn->batch_header_size_ + footer_size; + if (total_calculated_size > remaining_size) + return 0; + + auto &shared_buf = conn->parent_->get_shared_buffer_ref(); shared_buf.resize(shared_buf.size() + to_add); ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size}; encode_fn(msg, buffer); - // Return total size (header + payload + footer) - return static_cast(total_calculated_size); + return total_calculated_size; } - // Noinline path for zero-payload messages (ping, disconnect, list_info_done). - // Avoids duplicating encode_to_buffer at each cold call site. - static uint16_t encode_empty_to_buffer(const void *msg, APIConnection *conn, uint32_t remaining_size); + // Specialized path for zero-payload messages (ping, disconnect, list_info_done). + // No payload means no encode callback and no payload resize needed. + // Noinline — the three callers share a single copy via a call. + __attribute__((noinline)) static uint16_t encode_empty_to_buffer(const void *msg, uint8_t message_type, + APIConnection *conn, uint32_t remaining_size) { +#ifdef HAS_PROTO_MESSAGE_DUMP + if (conn->flags_.log_only_mode) { + conn->log_send_message_(msg); + return 1; + } +#endif + const uint8_t footer_size = conn->helper_->frame_footer_size(); + bool first = conn->flags_.batch_first_message; + if (first) { + conn->flags_.batch_first_message = false; + conn->batch_header_size_ = conn->helper_->frame_header_padding(); + } else { + conn->batch_header_size_ = conn->helper_->frame_header_size(0, message_type); + } + uint16_t total = conn->batch_header_size_ + footer_size; + if (total > remaining_size) + return 0; + if (!first) { + // Non-first: grow buffer by header + footer (no payload) + auto &shared_buf = conn->parent_->get_shared_buffer_ref(); + shared_buf.resize(shared_buf.size() + total); + } + // First message: padding already in buffer from prepare_first_message_buffer + return total; + } // Thin template wrapper — computes size, delegates buffer work to non-template helper template static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) { if constexpr (T::ESTIMATED_SIZE == 0) { - return encode_empty_to_buffer(&msg, conn, remaining_size); + return encode_empty_to_buffer(&msg, T::MESSAGE_TYPE, conn, remaining_size); } else { return encode_to_buffer(msg.calculate_size(), &proto_encode_msg, &msg, conn, remaining_size); } diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index b41233eddd..20b201fec1 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -12,6 +12,11 @@ static const char *const TAG = "api.service"; void APIServerConnectionBase::log_send_message_(const LogString *name, const char *dump) { ESP_LOGVV(TAG, "send_message %s: %s", LOG_STR_ARG(name), dump); } +void APIServerConnectionBase::log_send_message_(const void *msg) { + auto *proto_msg = static_cast(msg); + DumpBuffer dump_buf; + this->log_send_message_(proto_msg->message_name(), proto_msg->dump_to(dump_buf)); +} void APIServerConnectionBase::log_receive_message_(const LogString *name, const ProtoMessage &msg) { DumpBuffer dump_buf; ESP_LOGVV(TAG, "%s: %s", LOG_STR_ARG(name), msg.dump_to(dump_buf)); diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 6ff988902f..1ae085fb5e 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -13,6 +13,8 @@ class APIServerConnectionBase { #ifdef HAS_PROTO_MESSAGE_DUMP protected: void log_send_message_(const LogString *name, const char *dump); + // Log a message being sent from a void pointer (for use in encode_to_buffer paths) + void log_send_message_(const void *msg); void log_receive_message_(const LogString *name, const ProtoMessage &msg); void log_receive_message_(const LogString *name);