diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b624cace2c..12766ffe65 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1826,9 +1826,18 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { return false; } - // Toggle NODELAY based on message type: - // - Log messages: Enable Nagle (NODELAY=false) so they coalesce into fewer packets - // - All other messages: Disable Nagle (NODELAY=true) for immediate delivery + // Toggle Nagle's algorithm based on message type to prevent log messages from + // filling the TCP send buffer and crowding out important state updates. + // + // - Log messages: Enable Nagle (NODELAY=false) so small log packets coalesce + // into fewer, larger packets. They flush naturally via TCP delayed ACK timer + // (~200ms), buffer filling, or when a state update triggers a flush. + // + // - All other messages (state updates, responses): Disable Nagle (NODELAY=true) + // for immediate delivery. These are time-sensitive and should not be delayed. + // + // This must be done proactively BEFORE the buffer fills up - checking buffer + // state here would be too late since we'd already be in a degraded state. this->helper_->set_nodelay(!is_log_message); APIError err = this->helper_->write_protobuf_packet(message_type, buffer); diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 6fdda64c3e..1c4c045a86 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -111,7 +111,15 @@ class APIFrameHelper { } return APIError::OK; } - /// Set TCP_NODELAY option. Only calls setsockopt when state changes. + /// Toggle TCP_NODELAY socket option to control Nagle's algorithm. + /// + /// This is used to allow log messages to coalesce (Nagle enabled) while keeping + /// state updates low-latency (NODELAY enabled). Without this, many small log + /// packets fill the TCP send buffer, crowding out important state updates. + /// + /// State is tracked to minimize setsockopt() overhead - on lwip_raw (ESP8266/RP2040) + /// this is just a boolean assignment; on other platforms it's a lightweight syscall. + /// /// @param enable true to enable NODELAY (disable Nagle), false to enable Nagle /// @return true if successful or already in desired state bool set_nodelay(bool enable) { @@ -211,7 +219,10 @@ class APIFrameHelper { uint8_t tx_buf_head_{0}; uint8_t tx_buf_tail_{0}; uint8_t tx_buf_count_{0}; - bool nodelay_enabled_{true}; // Tracks current TCP_NODELAY state + // Tracks TCP_NODELAY state to minimize setsockopt() calls. Initialized to true + // since init_common_() enables NODELAY. Used by set_nodelay() to allow log + // messages to coalesce while keeping state updates low-latency. + bool nodelay_enabled_{true}; // Common initialization for both plaintext and noise protocols APIError init_common_();