diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 79df85ada3..0f456ecd0c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -132,8 +132,6 @@ APIConnection::APIConnection(std::unique_ptr sock, APIServer *pa #endif } -uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); } - void APIConnection::start() { this->last_traffic_ = App.get_loop_component_start_time(); @@ -2023,24 +2021,23 @@ uint16_t APIConnection::encode_to_buffer(uint32_t calculated_size, MessageEncode auto &shared_buf = conn->parent_->get_shared_buffer_ref(); + size_t to_add; if (conn->flags_.batch_first_message) { // First message - buffer already prepared by caller, just clear flag conn->flags_.batch_first_message = false; + to_add = calculated_size; } else { // Batch message second or later - // Add padding for previous message footer + this message header - size_t current_size = shared_buf.size(); - shared_buf.reserve_and_resize(current_size + total_calculated_size, current_size + footer_size + header_padding); + // Reserve for full message, resize to include footer gap + header padding + payload + to_add = total_calculated_size; } - // Pre-resize buffer to include payload, then encode through raw pointer - size_t write_start = shared_buf.size(); - shared_buf.resize(write_start + calculated_size); - ProtoWriteBuffer buffer{&shared_buf, write_start}; + 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(header_padding + calculated_size + footer_size); + return static_cast(total_calculated_size); } bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE); @@ -2072,37 +2069,9 @@ void APIConnection::on_fatal_error() { this->flags_.remove = true; } -void __attribute__((flatten)) APIConnection::DeferredBatch::push_item(const BatchItem &item) { items.push_back(item); } - -void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, - uint8_t aux_data_index) { - // Check if we already have a message of this type for this entity - // This provides deduplication per entity/message_type combination - // O(n) but optimized for RAM and not performance. - // Skip deduplication for events - they are edge-triggered, every occurrence matters -#ifdef USE_EVENT - if (message_type != EventResponse::MESSAGE_TYPE) -#endif - { - for (const auto &item : items) { - if (item.entity == entity && item.message_type == message_type) - return; // Already queued - } - } - // No existing item found (or event), add new one - this->push_item({entity, message_type, estimated_size, aux_data_index}); -} - -void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { - // Add high priority message and swap to front - // This avoids expensive vector::insert which shifts all elements - // Note: We only ever have one high-priority message at a time (ping OR disconnect) - // If we're disconnecting, pings are blocked, so this simple swap is sufficient - this->push_item({entity, message_type, estimated_size, AUX_DATA_UNUSED}); - if (items.size() > 1) { - // Swap the new high-priority item to the front - std::swap(items.front(), items.back()); - } +bool APIConnection::schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { + this->deferred_batch_.add_item_front(entity, message_type, estimated_size); + return this->schedule_batch_(); } bool APIConnection::send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 4ce1335650..13d5273ecb 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -644,11 +644,28 @@ class APIConnection final : public APIServerConnectionBase { // Add item to the batch (with deduplication) void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, - uint8_t aux_data_index = AUX_DATA_UNUSED); + uint8_t aux_data_index = AUX_DATA_UNUSED) { + // Dedup: O(n) scan but optimized for RAM over performance + // Skip deduplication for events - they are edge-triggered, every occurrence matters +#ifdef USE_EVENT + if (message_type != EventResponse::MESSAGE_TYPE) +#endif + { + for (const auto &item : this->items) { + if (item.entity == entity && item.message_type == message_type) + return; // Already queued + } + } + this->items.push_back({entity, message_type, estimated_size, aux_data_index}); + } // Add item to the front of the batch (for high priority messages like ping) - void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size); - // Single push_back site to avoid duplicate _M_realloc_insert instantiation - void push_item(const BatchItem &item); + void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { + // Swap to front avoids expensive vector::insert which shifts all elements + this->items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED}); + if (this->items.size() > 1) { + std::swap(this->items.front(), this->items.back()); + } + } // Clear all items void clear() { @@ -713,7 +730,7 @@ class APIConnection final : public APIServerConnectionBase { ActiveIterator active_iterator_{ActiveIterator::NONE}; // Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary - uint32_t get_batch_delay_ms_() const; + uint32_t get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); } // Message will use 8 more bytes than the minimum size, and typical // MTU is 1500. Sometimes users will see as low as 1460 MTU. // If its IPv6 the header is 40 bytes, and if its IPv4 @@ -780,10 +797,8 @@ class APIConnection final : public APIServerConnectionBase { } // Helper function to schedule a high priority message at the front of the batch - bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { - this->deferred_batch_.add_item_front(entity, message_type, estimated_size); - return this->schedule_batch_(); - } + // Out-of-line: callers (on_shutdown, check_keepalive_) are cold paths + bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size); // Helper function to log client messages with name and peername void log_client_(int level, const LogString *message); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index f2d60be641..c7f2319d69 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -249,7 +249,7 @@ void ESP32BLETracker::start_scan_(bool first) { return; } this->set_scanner_state_(ScannerState::STARTING); - ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING."); + ESP_LOGV(TAG, "Starting scan, set scanner state to STARTING."); if (!first) { #ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT for (auto *listener : this->listeners_) @@ -855,7 +855,7 @@ void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) { } void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) { - ESP_LOGD(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : ""); + ESP_LOGV(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : ""); #ifdef USE_ESP32_BLE_DEVICE this->already_discovered_.clear(); #endif