diff --git a/esphome/components/nextion/nextion_upload_esp32.cpp b/esphome/components/nextion/nextion_upload_esp32.cpp index cd8feab84f..e2d5ae8ad7 100644 --- a/esphome/components/nextion/nextion_upload_esp32.cpp +++ b/esphome/components/nextion/nextion_upload_esp32.cpp @@ -44,17 +44,60 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r ESP_LOGV(TAG, "Range: %s", range_header); esp_http_client_set_header(http_client, "Range", range_header); ESP_LOGV(TAG, "Open HTTP"); - esp_err_t err = esp_http_client_open(http_client, 0); - if (err != ESP_OK) { - ESP_LOGE(TAG, "HTTP open failed: %s", esp_err_to_name(err)); - return -1; + int chunk_size = -1; + int status_code = -1; + esp_err_t last_err = ESP_FAIL; + for (uint8_t attempt = 0; attempt < this->tft_upload_http_retries_; attempt++) { + status_code = -1; + last_err = esp_http_client_open(http_client, 0); + if (last_err == ESP_OK) { + ESP_LOGV(TAG, "Fetch length"); + chunk_size = esp_http_client_fetch_headers(http_client); + ESP_LOGV(TAG, "Length: %d", chunk_size); + if (chunk_size >= 0) { + status_code = esp_http_client_get_status_code(http_client); + // Accept the requested range (206 with exact length) or a full-body 200 + // only from offset 0; elsewhere a 200 replays the file and corrupts the display. + if (chunk_size > 0 && + ((status_code == 206 && chunk_size == static_cast(range_end - range_start + 1)) || + (status_code == 200 && range_start == 0 && chunk_size == static_cast(this->tft_size_)))) { + break; + } + if (status_code == 200) { + if (range_start == 0) { + ESP_LOGE(TAG, "Unexpected length for 200 response: %d (expected %d)", chunk_size, + static_cast(this->tft_size_)); + } else { + // A server that ignored the range once will ignore it again + ESP_LOGE(TAG, "Server does not support range requests (got 200 at offset %" PRIu32 ")", range_start); + } + chunk_size = -1; + last_err = ESP_FAIL; + break; + } + ESP_LOGW(TAG, "Bad response: status %d, length %d (expected %" PRIu32 ")", status_code, chunk_size, + range_end - range_start + 1); + chunk_size = -1; + last_err = ESP_FAIL; + // A 4xx (except timeout/rate-limit) won't improve on retry + if (status_code >= 400 && status_code < 500 && status_code != 408 && status_code != 429) { + break; + } + } else { + ESP_LOGW(TAG, "Get length failed: %d", chunk_size); + last_err = ESP_FAIL; + } + } else { + ESP_LOGW(TAG, "HTTP open failed: %s", esp_err_to_name(last_err)); + } + // The server may have dropped the keep-alive connection while the display + // was busy processing a chunk; close so the next attempt reconnects. + esp_http_client_close(http_client); + vTaskDelay(pdMS_TO_TICKS(2)); // NOLINT + App.feed_wdt(); } - - ESP_LOGV(TAG, "Fetch length"); - const int chunk_size = esp_http_client_fetch_headers(http_client); - ESP_LOGV(TAG, "Length: %d", chunk_size); if (chunk_size <= 0) { - ESP_LOGE(TAG, "Get length failed: %d", chunk_size); + ESP_LOGE(TAG, "HTTP request failed, last status: %d, last error: %s", status_code, esp_err_to_name(last_err)); return -1; } @@ -164,6 +207,9 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r } else { range_start = range_end + 1; } + // The response body may be only partially read; close so the next + // range request starts on a clean connection. + esp_http_client_close(http_client); // Deallocate buffer allocator.deallocate(buffer, 4096); buffer = nullptr;