From 2243e349817c5825d12ba9fe47d919771e9737a7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 Apr 2026 09:51:37 -1000 Subject: [PATCH] [ota] Fix OTA handshake failure by switching to blocking before feature ACK write Switch to blocking mode before writing the feature ACK byte in the no-password path. lwIP may not transmit data written in non-blocking mode when the socket immediately switches to blocking and blocks in recv(). Writing the feature ACK in blocking mode ensures it is flushed to the wire before entering the blocking data transfer phase. --- esphome/components/esphome/ota/ota_esphome.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 13d67842aa..b39fe94adf 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -185,6 +185,16 @@ void ESPHomeOTAComponent::handle_handshake_() { } case OTAState::FEATURE_ACK: { + // No password: switch to blocking before writing so the feature ACK + // byte is flushed to the wire before entering the blocking data phase. + // lwIP may not transmit data written in non-blocking mode when the + // socket immediately switches to blocking and blocks in recv(). +#ifdef USE_OTA_PASSWORD + if (this->password_.empty()) +#endif + { + this->client_->setblocking(true); + } // Acknowledge header - 1 byte if (!this->try_write_(1, LOG_STR("ack feature"))) { return; @@ -283,11 +293,6 @@ void ESPHomeOTAComponent::handle_data_() { this->client_->setsockopt(SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); this->client_->setblocking(true); - // Yield to flush any pending non-blocking writes (e.g. the feature ACK byte) - // before entering the blocking data transfer. lwIP may not transmit data - // written in non-blocking mode until the task yields. - delay(0); - // Acknowledge auth OK - 1 byte this->write_byte_(ota::OTA_RESPONSE_AUTH_OK);