diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 90c5051ebb..490912c56a 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -33,8 +33,6 @@ static const int ESP32_CAMERA_STOP_STREAM = 5000; APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { - this->proto_write_buffer_.reserve(64); - #if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE) auto noise_ctx = parent->get_noise_ctx(); if (noise_ctx->has_psk()) { @@ -1669,7 +1667,8 @@ void APIConnection::process_batch_() { // Let the creator calculate size and encode if it fits uint16_t payload_size = item.creator(item.entity, this, std::numeric_limits::max(), true); - if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&this->proto_write_buffer_}, item.message_type)) { + if (payload_size > 0 && + this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { this->deferred_batch_.clear(); } else if (payload_size == 0) { // Message too large @@ -1688,7 +1687,7 @@ void APIConnection::process_batch_() { const uint8_t footer_size = this->helper_->frame_footer_size(); // Initialize buffer and tracking variables - this->proto_write_buffer_.clear(); + this->parent_->get_shared_buffer_ref().clear(); // Pre-calculate exact buffer size needed based on message types uint32_t total_estimated_size = 0; @@ -1700,7 +1699,7 @@ void APIConnection::process_batch_() { uint32_t total_overhead = (header_padding + footer_size) * num_items; // Reserve based on estimated size (much more accurate than 24-byte worst-case) - this->proto_write_buffer_.reserve(total_estimated_size + total_overhead); + this->parent_->get_shared_buffer_ref().reserve(total_estimated_size + total_overhead); this->batch_first_message_ = true; size_t items_processed = 0; @@ -1732,7 +1731,7 @@ void APIConnection::process_batch_() { remaining_size -= payload_size; // Calculate where the next message's header padding will start // Current buffer size + footer space (that prepare_message_buffer will add for this message) - current_offset = this->proto_write_buffer_.size() + footer_size; + current_offset = this->parent_->get_shared_buffer_ref().size() + footer_size; items_processed++; } @@ -1743,11 +1742,13 @@ void APIConnection::process_batch_() { // Add footer space for the last message (for Noise protocol MAC) if (footer_size > 0) { - this->proto_write_buffer_.resize(this->proto_write_buffer_.size() + footer_size); + auto &shared_buf = this->parent_->get_shared_buffer_ref(); + shared_buf.resize(shared_buf.size() + footer_size); } // Send all collected packets - APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->proto_write_buffer_}, packet_info); + APIError err = + this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, packet_info); if (err != APIError::OK && err != APIError::WOULD_BLOCK) { on_fatal_error(); if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 702cdadc0d..fcc5833b8f 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -233,45 +233,49 @@ class APIConnection : public APIServerConnection { // Get header padding size - used for both reserve and insert uint8_t header_padding = this->helper_->frame_header_padding(); - this->proto_write_buffer_.clear(); + // Get shared buffer from parent server + std::vector &shared_buf = this->parent_->get_shared_buffer_ref(); + shared_buf.clear(); // Reserve space for header padding + message + footer // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext) // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext) - this->proto_write_buffer_.reserve(reserve_size + header_padding + this->helper_->frame_footer_size()); + shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size()); // Insert header padding bytes so message encoding starts at the correct position - this->proto_write_buffer_.insert(this->proto_write_buffer_.begin(), header_padding, 0); - return {&this->proto_write_buffer_}; + shared_buf.insert(shared_buf.begin(), header_padding, 0); + return {&shared_buf}; } // Prepare buffer for next message in batch ProtoWriteBuffer prepare_message_buffer(uint32_t message_size, bool is_first_message) { - size_t current_size = this->proto_write_buffer_.size(); + // Get reference to shared buffer (it maintains state between batch messages) + std::vector &shared_buf = this->parent_->get_shared_buffer_ref(); + size_t current_size = shared_buf.size(); if (is_first_message) { // For first message, initialize buffer with header padding uint8_t header_padding = this->helper_->frame_header_padding(); - this->proto_write_buffer_.clear(); - this->proto_write_buffer_.reserve(message_size + header_padding); - this->proto_write_buffer_.resize(header_padding); + shared_buf.clear(); + shared_buf.reserve(message_size + header_padding); + shared_buf.resize(header_padding); // Fill header padding with zeros - std::fill(this->proto_write_buffer_.begin(), this->proto_write_buffer_.end(), 0); + std::fill(shared_buf.begin(), shared_buf.end(), 0); } else { // For subsequent messages, add footer space for previous message and header for this message uint8_t footer_size = this->helper_->frame_footer_size(); uint8_t header_padding = this->helper_->frame_header_padding(); // Reserve additional space for everything - this->proto_write_buffer_.reserve(current_size + footer_size + header_padding + message_size); + shared_buf.reserve(current_size + footer_size + header_padding + message_size); // Single resize to add both footer and header padding size_t new_size = current_size + footer_size + header_padding; - this->proto_write_buffer_.resize(new_size); + shared_buf.resize(new_size); // Fill the newly added bytes with zeros (footer + header padding) - std::fill(this->proto_write_buffer_.begin() + current_size, this->proto_write_buffer_.end(), 0); + std::fill(shared_buf.begin() + current_size, shared_buf.end(), 0); } - return {&this->proto_write_buffer_}; + return {&shared_buf}; } bool try_to_clear_buffer(bool log_out_of_space); @@ -431,9 +435,6 @@ class APIConnection : public APIServerConnection { bool remove_{false}; - // Buffer used to encode proto messages - // Re-use to prevent allocations - std::vector proto_write_buffer_; std::unique_ptr helper_; std::string client_info_; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index dee9c40945..7f898df281 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -24,7 +24,11 @@ static const char *const TAG = "api"; // APIServer APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -APIServer::APIServer() { global_api_server = this; } +APIServer::APIServer() { + global_api_server = this; + // Pre-allocate shared write buffer + shared_write_buffer_.reserve(64); +} void APIServer::setup() { ESP_LOGCONFIG(TAG, "Running setup"); diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index fd2940a8ea..df9f70c270 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -42,6 +42,9 @@ class APIServer : public Component, public Controller { void set_batch_delay(uint32_t batch_delay); uint32_t get_batch_delay() const { return batch_delay_; } + // Get reference to shared buffer for API connections + std::vector &get_shared_buffer_ref() { return shared_write_buffer_; } + #ifdef USE_API_NOISE bool save_noise_psk(psk_t psk, bool make_active = true); void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); } @@ -145,6 +148,7 @@ class APIServer : public Component, public Controller { uint32_t last_connected_{0}; std::vector> clients_; std::string password_; + std::vector shared_write_buffer_; // Shared proto write buffer for all connections std::vector state_subs_; std::vector user_services_; Trigger *client_connected_trigger_ = new Trigger();