From 8046ff7e1e9a35094c2cb3e6fe96c1935bebb0ea Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Sun, 3 May 2026 10:40:09 +0200 Subject: [PATCH] [nextion] TFT upload no longer fails when the display sends a split `0x08` ack (#16205) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- .../nextion/nextion_upload_arduino.cpp | 27 +++++++++++++++++++ .../nextion/nextion_upload_esp32.cpp | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index 399f217a19..41379c2345 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -88,6 +88,33 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { this->write_array(buffer, buffer_size); App.feed_wdt(); this->recv_ret_string_(recv_string, NEXTION_UPLOAD_ACK_TIMEOUT_MS, true); + + // Some Nextion firmware variants (notably bootloader/recovery mode on panels + // with no installed TFT) emit the 5-byte 0x08+position fast-mode ack with a + // multi-second gap between the leading 0x08 byte and the 4 trailing position + // bytes. recv_ret_string_ returns after the first byte; manually drain the + // trailing bytes from the UART before continuing. + if (!recv_string.empty() && recv_string[0] == 0x08 && recv_string.size() < 5) { + const uint32_t deadline = millis() + NEXTION_UPLOAD_ACK_TIMEOUT_MS; + while (recv_string.size() < 5 && millis() < deadline) { + if (this->available()) { + uint8_t b = 0; + if (this->read_byte(&b)) { + recv_string.push_back(static_cast(b)); + } + } else { + delay(5); // NOLINT + App.feed_wdt(); + } + } + if (recv_string.size() < 5) { + ESP_LOGE(TAG, "Truncated 0x08 response: got %zu bytes within %" PRIu32 "ms", recv_string.size(), + NEXTION_UPLOAD_ACK_TIMEOUT_MS); + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return -1; + } + } this->content_length_ -= read_len; const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_; ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 ")", upload_percentage, this->content_length_, diff --git a/esphome/components/nextion/nextion_upload_esp32.cpp b/esphome/components/nextion/nextion_upload_esp32.cpp index db4558e2fe..cd8feab84f 100644 --- a/esphome/components/nextion/nextion_upload_esp32.cpp +++ b/esphome/components/nextion/nextion_upload_esp32.cpp @@ -104,6 +104,33 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r this->write_array(buffer, buffer_size); App.feed_wdt(); this->recv_ret_string_(recv_string, NEXTION_UPLOAD_ACK_TIMEOUT_MS, true); + + // Some Nextion firmware variants (notably bootloader/recovery mode on panels + // with no installed TFT) emit the 5-byte 0x08+position fast-mode ack with a + // multi-second gap between the leading 0x08 byte and the 4 trailing position + // bytes. recv_ret_string_ returns after the first byte; manually drain the + // trailing bytes from the UART before continuing. + if (!recv_string.empty() && recv_string[0] == 0x08 && recv_string.size() < 5) { + const uint32_t deadline = millis() + NEXTION_UPLOAD_ACK_TIMEOUT_MS; + while (recv_string.size() < 5 && millis() < deadline) { + if (this->available()) { + uint8_t b = 0; + if (this->read_byte(&b)) { + recv_string.push_back(static_cast(b)); + } + } else { + vTaskDelay(pdMS_TO_TICKS(5)); // NOLINT + App.feed_wdt(); + } + } + if (recv_string.size() < 5) { + ESP_LOGE(TAG, "Truncated 0x08 response: got %zu bytes within %" PRIu32 "ms", recv_string.size(), + NEXTION_UPLOAD_ACK_TIMEOUT_MS); + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return -1; + } + } this->content_length_ -= read_len; const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_; #ifdef USE_PSRAM