From bf39dfef6a4bf6022f934c293d71b8a46b8d1ba3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 4 Apr 2026 17:46:58 -1000 Subject: [PATCH 1/2] [ota] Fix OTA handshake failure by flushing pending writes before blocking read Add delay(0) after switching to blocking mode in handle_data_() to flush any pending non-blocking writes (the feature ACK byte) before entering the blocking read loop. lwIP may not transmit data written in non-blocking mode until the task yields. --- esphome/components/esphome/ota/ota_esphome.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 972d2b2b8d..13d67842aa 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -283,6 +283,11 @@ 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); From 2243e349817c5825d12ba9fe47d919771e9737a7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 Apr 2026 09:51:37 -1000 Subject: [PATCH 2/2] [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);