From e9f25a55b01fd019849cd0667c908d285b8197f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 8 Sep 2026 15:39:00 +0200 Subject: [PATCH] [esphome][core] Give a lost OTA chunk ack time to be retransmitted --- esphome/components/esphome/ota/ota_esphome.cpp | 5 ++++- esphome/espota2.py | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 1005ed214b..1e751de869 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -41,7 +41,10 @@ const noise::NoiseContext &ESPHomeOTAComponent::noise_context_() const { #endif static constexpr uint16_t OTA_BLOCK_SIZE = 8192; static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake -static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer +// Milliseconds for data transfer. Outlasts lwIP retransmitting a lost chunk ack +// six times (1.5 + 3 + 6 + 12 + 24 + 48 s); the CLI waits longer (160 s) so the +// device is always free again before the CLI retries +static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 105000; // Single-instance pointer — multi-port configs are rejected in final_validate. // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/espota2.py b/esphome/espota2.py index ce403c398d..e72d07d810 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -694,8 +694,10 @@ def perform_ota( _LOGGER.info("Handshake complete") - # Timeout must match device-side OTA_SOCKET_TIMEOUT_DATA to prevent premature failures - sock.settimeout(90.0) + # Longer than the device's OTA_SOCKET_TIMEOUT_DATA (105 s) so a stalled + # session is gone before a retry, and long enough for lwIP to get a lost + # chunk ack through after six retransmissions + sock.settimeout(160.0) if extended_proto: send_check(sock, ota_type, "ota type") @@ -854,7 +856,7 @@ def run_ota_impl_( # clean up a half-open connection (its handshake watchdog runs at 20s); # moving on to the next address family stays immediate. Known limitation: # a silent mid-transfer drop with no reset can wedge the device until its - # 90s data timeout, which outlasts this budget; the retries target the + # 105s data timeout, which outlasts this budget; the retries target the # common failures where the device resets or closes the link promptly. total_attempts = len(res) + EXTRA_UPLOAD_ATTEMPTS last_error = ""