From 28c4a4c96c203380aa966b5e6751588aa12f3b44 Mon Sep 17 00:00:00 2001 From: olicooper <22296715+olicooper@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:28:24 +0100 Subject: [PATCH] Update IDF TFT upload script This improves (but still doesn't fix) the esp crashing during the upload process. It appears like the esp_http_client_read() function is causing a watchdog reset while waiting for something. Maybe due to not handling unexpected connection close? The connection reliability may be a factor. ``` [D][nspanel_lovelace_upload:142]: Uploaded 42.20%, remaining 4741340 bytes, free heap: 35812 (DRAM) + 0 (PSRAM) bytes [D][nspanel_lovelace_upload:039]: HTTP_EVENT_ON_DATA, len=96 [D][nspanel_lovelace_upload:039]: HTTP_EVENT_ON_DATA, len=512 [D][nspanel_lovelace_upload:039]: HTTP_EVENT_ON_DATA, len=512 [D][nspanel_lovelace_upload:039]: HTTP_EVENT_ON_DATA, len=416 E (138838) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time: E (138838) task_wdt: - loopTask (CPU 1) E (138838) task_wdt: Tasks currently running: E (138838) task_wdt: CPU 0: IDLE0 E (138838) task_wdt: CPU 1: IDLE1 E (138838) task_wdt: Aborting. abort() was called at PC 0x40105c20 on core 0 Backtrace: 0x40082b4d:0x3ffb0870 0x4008e4c9:0x3ffb0890 0x400947b5:0x3ffb08b0 0x40105c20:0x3ffb0920 0x400834f5:0x3ffb0940 0x401858fb:0x3ffcbeb0 0x401063bd:0x3ffcbed0 0x4008f954:0x3ffcbef0 WARNING Found stack trace! Trying to decode it WARNING Decoded 0x40082b4d: panic_abort at D:/.platformio/packages/framework-espidf@3.40407.240606/components/esp_system/panic.c:408 WARNING Decoded 0x4008e4c9: esp_system_abort at D:/.platformio/packages/framework-espidf@3.40407.240606/components/esp_system/esp_system.c:137 WARNING Decoded 0x400947b5: abort at D:\.platformio\packages\framework-espidf@3.40407.240606\components\newlib/abort.c:46 WARNING Decoded 0x40105c20: task_wdt_isr at D:/.platformio/packages/framework-espidf@3.40407.240606/components/esp_system/task_wdt.c:176 (discriminator 3) WARNING Decoded 0x400834f5: _xt_lowint1 at D:\.platformio\packages\framework-espidf@3.40407.240606\components\freertos\port\xtensa/xtensa_vectors.S:1118 WARNING Decoded 0x401858fb: cpu_ll_waiti at D:\.platformio\packages\framework-espidf@3.40407.240606\components\hal\esp32\include/hal/cpu_ll.h:183 (inlined by) esp_pm_impl_waiti at D:\.platformio\packages\framework-espidf@3.40407.240606\components\esp_pm/pm_impl.c:853 WARNING Decoded 0x401063bd: esp_vApplicationIdleHook at D:/.platformio/packages/framework-espidf@3.40407.240606/components/esp_system/freertos_hooks.c:63 WARNING Decoded 0x4008f954: prvIdleTask at D:\.platformio\packages\framework-espidf@3.40407.240606\components\freertos/tasks.c:4099 ``` --- .../nspanel_lovelace/nspanel_lovelace.cpp | 22 +- .../nspanel_lovelace/nspanel_lovelace.h | 18 +- .../nspanel_lovelace_upload_arduino.cpp | 8 +- .../nspanel_lovelace_upload_idf.cpp | 461 +++++++++++------- 4 files changed, 298 insertions(+), 211 deletions(-) diff --git a/components/nspanel_lovelace/nspanel_lovelace.cpp b/components/nspanel_lovelace/nspanel_lovelace.cpp index 548174e..b7e5335 100644 --- a/components/nspanel_lovelace/nspanel_lovelace.cpp +++ b/components/nspanel_lovelace/nspanel_lovelace.cpp @@ -1261,17 +1261,19 @@ uint16_t NSPanelLovelace::recv_ret_string_(std::string &response, uint32_t timeo #endif } -void NSPanelLovelace::start_reparse_mode_() { - this->send_nextion_command_("DRAKJHSUYDGBNCJHGJKSHBDN"); - this->send_nextion_command_("recmod=0"); - this->send_nextion_command_("recmod=0"); - this->send_nextion_command_("connect"); - reparse_mode_ = true; -} +void NSPanelLovelace::set_reparse_mode_(bool active) { + if (this->reparse_mode_ == active) return; + + if (active) { + this->send_nextion_command_("recmod=1"); + } else { + this->send_nextion_command_("DRAKJHSUYDGBNCJHGJKSHBDN"); + this->send_nextion_command_("recmod=0"); + this->send_nextion_command_("recmod=0"); + this->send_nextion_command_("connect"); + } -void NSPanelLovelace::exit_reparse_mode_() { - this->send_nextion_command_("recmod=1"); - reparse_mode_ = false; + this->reparse_mode_ = active; } #endif // USE_NSPANEL_TFT_UPLOAD diff --git a/components/nspanel_lovelace/nspanel_lovelace.h b/components/nspanel_lovelace/nspanel_lovelace.h index d63b852..b3a0893 100644 --- a/components/nspanel_lovelace/nspanel_lovelace.h +++ b/components/nspanel_lovelace/nspanel_lovelace.h @@ -19,6 +19,9 @@ #ifdef USE_ESP_IDF #include "esphome/components/uart/uart_component_esp_idf.h" +#ifdef USE_NSPANEL_TFT_UPLOAD +#include +#endif #else #ifdef USE_NSPANEL_TFT_UPLOAD #include @@ -148,8 +151,7 @@ class NSPanelLovelace : public Component, public uart::UARTDevice, protected api void init_display_(int baud_rate); #ifdef USE_NSPANEL_TFT_UPLOAD uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); - void start_reparse_mode_(); - void exit_reparse_mode_(); + void set_reparse_mode_(bool active); #endif void send_nextion_command_(const std::string &command); @@ -230,7 +232,6 @@ class NSPanelLovelace : public Component, public uart::UARTDevice, protected api std::queue command_queue_; unsigned long command_last_sent_ = 0; - unsigned int update_baud_rate_ = 921600; bool button_press_timeout_set_ = false; std::string button_press_uuid_; @@ -254,17 +255,18 @@ class NSPanelLovelace : public Component, public uart::UARTDevice, protected api std::string command_buffer_; #ifdef USE_NSPANEL_TFT_UPLOAD + unsigned int update_baud_rate_ = 921600; bool is_updating_ = false; bool reparse_mode_ = false; - int content_length_ = 0; - int tft_size_ = 0; + uint32_t content_length_ = 0; + size_t tft_size_ = 0; + bool upload_first_chunk_sent_ = false; #ifdef USE_ESP_IDF - int upload_range_(const std::string &url, int range_start); + int upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start); #else // USE_ARDUINO uint8_t *transfer_buffer_ = nullptr; size_t transfer_buffer_size_; - bool upload_first_chunk_sent_ = false; - int upload_by_chunks_(HTTPClient *http, const std::string &url, int range_start); + int upload_by_chunks_(HTTPClient *http, const std::string &url, uint32_t &range_start); #endif bool upload_end_(bool successful); #endif // USE_NSPANEL_TFT_UPLOAD diff --git a/components/nspanel_lovelace/nspanel_lovelace_upload_arduino.cpp b/components/nspanel_lovelace/nspanel_lovelace_upload_arduino.cpp index 419662f..30b72ec 100644 --- a/components/nspanel_lovelace/nspanel_lovelace_upload_arduino.cpp +++ b/components/nspanel_lovelace/nspanel_lovelace_upload_arduino.cpp @@ -19,7 +19,7 @@ static const char *const TAG = "nspanel_lovelace_upload"; // Originally from: https://github.com/sairon/esphome-nspanel-lovelace-ui/blob/dev/components/nspanel_lovelace/nspanel_lovelace_upload.cpp // Followed guide: https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 -int NSPanelLovelace::upload_by_chunks_(HTTPClient *http, const std::string &url, int range_start) { +int NSPanelLovelace::upload_by_chunks_(HTTPClient *http, const std::string &url, uint32_t &range_start) { int range_end; if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip @@ -139,9 +139,7 @@ bool NSPanelLovelace::upload_tft(const std::string &url) { return false; } - if (!this->reparse_mode_) { - this->start_reparse_mode_(); - } + this->set_reparse_mode_(false); this->is_updating_ = true; @@ -266,7 +264,7 @@ bool NSPanelLovelace::upload_tft(const std::string &url) { ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", url.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap()); - int result = 0; + uint32_t result = 0; while (this->content_length_ > 0) { result = this->upload_by_chunks_(&http, url, result); if (result < 0) { diff --git a/components/nspanel_lovelace/nspanel_lovelace_upload_idf.cpp b/components/nspanel_lovelace/nspanel_lovelace_upload_idf.cpp index 976ed45..88f4b20 100644 --- a/components/nspanel_lovelace/nspanel_lovelace_upload_idf.cpp +++ b/components/nspanel_lovelace/nspanel_lovelace_upload_idf.cpp @@ -1,7 +1,7 @@ #ifdef USE_NSPANEL_TFT_UPLOAD #ifdef USE_ESP_IDF -// Adapted from: https://github.com/esphome/esphome/blob/dc0a7b1e205f5fa4e25fd1cadd507c27173636e1/esphome/components/nextion/nextion_upload_idf.cpp +// Adapted from: https://github.com/esphome/esphome/blob/5b6b7c0d15098f7477bae68329fe76a1d8993cf5/esphome/components/nextion/nextion_upload_idf.cpp #include "nspanel_lovelace.h" @@ -19,153 +19,183 @@ namespace esphome { namespace nspanel_lovelace { static const char *const TAG = "nspanel_lovelace_upload"; -int NSPanelLovelace::upload_range_(const std::string &url, int range_start) { - ESP_LOGVV(TAG, "url: %s", url.c_str()); - uint range_size = this->tft_size_ - range_start; - ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - int range_end = (range_start == 0) ? std::min(this->tft_size_, 16383) : this->tft_size_; - if (range_size <= 0 or range_end <= range_start) { +// esp_err_t _http_event_handler(esp_http_client_event_t *evt) { +// switch (evt->event_id) { +// case HTTP_EVENT_ERROR: +// ESP_LOGD(TAG, "HTTP_EVENT_ERROR"); +// break; +// case HTTP_EVENT_ON_CONNECTED: +// ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED"); +// break; +// case HTTP_EVENT_HEADER_SENT: +// ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT"); +// break; +// case HTTP_EVENT_ON_HEADER: +// ESP_LOGD( +// TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, +// evt->header_value); +// break; +// case HTTP_EVENT_ON_DATA: +// ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len); +// break; +// case HTTP_EVENT_ON_FINISH: +// ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH"); +// break; +// case HTTP_EVENT_DISCONNECTED: +// ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED"); +// break; +// // case HTTP_EVENT_REDIRECT: +// // ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT"); +// // break; +// } +// return ESP_OK; +// } + +int NSPanelLovelace::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start) { + uint32_t range_size = this->tft_size_ - range_start; + ESP_LOGD(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); + uint32_t range_end = ((this->upload_first_chunk_sent_ || this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1; + ESP_LOGD(TAG, "Range start: %" PRIu32, range_start); + if (range_size <= 0 || range_end <= range_start) { + ESP_LOGD(TAG, "Range end: %" PRIu32, range_end); + ESP_LOGD(TAG, "Range size: %" PRIu32, range_size); ESP_LOGE(TAG, "Invalid range"); - ESP_LOGD(TAG, "Range start: %i", range_start); - ESP_LOGD(TAG, "Range end: %i", range_end); - ESP_LOGD(TAG, "Range size: %i", range_size); return -1; } - esp_http_client_config_t config = {}; - config.url = url.c_str(); - config.host = nullptr; - config.cert_pem = nullptr; - config.disable_auto_redirect = false; - config.max_redirection_count = 10; - - esp_http_client_handle_t client = esp_http_client_init(&config); - - char range_header[64]; - sprintf(range_header, "bytes=%d-%d", range_start, range_end); - ESP_LOGV(TAG, "Requesting range: %s", range_header); - esp_http_client_set_header(client, "Range", range_header); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - - ESP_LOGV(TAG, "Opening http connetion"); + // todo: if this is included, only thr efirst 4096 bytes are fetched. + // esp_http_client_read looks like it automatically handles the range request so this isn't required. + // char range_header[32]; + // sprintf(range_header, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end); + // ESP_LOGD(TAG, "Requesting range: %s", range_header); + // esp_http_client_set_header(http_client, "Range", range_header); + ESP_LOGD(TAG, "Opening HTTP connetion"); esp_err_t err; - if ((err = esp_http_client_open(client, 0)) != ESP_OK) { + if ((err = esp_http_client_open(http_client, 0)) != ESP_OK) { ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); - esp_http_client_cleanup(client); return -1; } - ESP_LOGV(TAG, "Fetch content length"); - int content_length = esp_http_client_fetch_headers(client); - ESP_LOGV(TAG, "content_length = %d", content_length); - if (content_length <= 0) { - ESP_LOGE(TAG, "Failed to get content length: %d", content_length); - esp_http_client_cleanup(client); + ESP_LOGD(TAG, "Fetch content length"); + const int chunk_size = esp_http_client_fetch_headers(http_client); + ESP_LOGD(TAG, "content_length = %d", chunk_size); + if (chunk_size <= 0) { + ESP_LOGE(TAG, "Failed to get chunk's content length: %d", chunk_size); return -1; } - int total_read_len = 0, read_len; + // Allocate the buffer dynamically + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + uint8_t *buffer = allocator.allocate(4096); + if (!buffer) { + ESP_LOGE(TAG, "Failed to allocate upload buffer"); + return -1; + } - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - ESP_LOGV(TAG, "Allocate buffer"); - uint8_t *buffer = new uint8_t[4096]; std::string recv_string; - if (buffer == nullptr) { - ESP_LOGE(TAG, "Failed to allocate memory for buffer"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - } else { - ESP_LOGV(TAG, "Memory for buffer allocated successfully"); - - while (true) { - App.feed_wdt(); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - int read_len = esp_http_client_read(client, reinterpret_cast(buffer), 4096); - ESP_LOGVV(TAG, "Read %d bytes from HTTP client, writing to UART", read_len); - if (read_len > 0) { - this->write_array(buffer, read_len); - ESP_LOGVV(TAG, "Write to UART successful"); - this->recv_ret_string_(recv_string, 5000, true); - this->content_length_ -= read_len; - ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes, heap is %" PRIu32 " bytes", - 100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_, - esp_get_free_heap_size()); - - if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request - ESP_LOGD( - TAG, "recv_string [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); - uint32_t result = 0; - for (int j = 0; j < 4; ++j) { - result += static_cast(recv_string[j + 1]) << (8 * j); - } - if (result > 0) { - ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result); - this->content_length_ = this->tft_size_ - result; - // Deallocate the buffer when done - ESP_LOGV(TAG, "Deallocate buffer"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - delete[] buffer; - ESP_LOGVV(TAG, "Memory for buffer deallocated"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - ESP_LOGV(TAG, "Close http client"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - esp_http_client_close(client); - esp_http_client_cleanup(client); - ESP_LOGVV(TAG, "Client closed"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - return result; - } - } else if (recv_string[0] != 0x05) { // 0x05 == "ok" - ESP_LOGE( - TAG, "Invalid response from Nextion: [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); - ESP_LOGV(TAG, "Deallocate buffer"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - delete[] buffer; - ESP_LOGVV(TAG, "Memory for buffer deallocated"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - ESP_LOGV(TAG, "Close http client"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - esp_http_client_close(client); - esp_http_client_cleanup(client); - ESP_LOGVV(TAG, "Client closed"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - return -1; - } - - recv_string.clear(); - } else if (read_len == 0) { - ESP_LOGV(TAG, "End of HTTP response reached"); - break; // Exit the loop if there is no more data to read + while (true) { + App.feed_wdt(); + yield(); + const uint16_t buffer_size = + this->content_length_ < 4096 ? this->content_length_ : 4096; // Limits buffer to the remaining data + ESP_LOGV(TAG, "Fetching %" PRIu16 " bytes from HTTP", buffer_size); + uint16_t read_len = 0; + int partial_read_len = 0; + uint8_t retries = 0; + // Attempt to read the chunk with retries. + while (retries < 5 && read_len < buffer_size) { + partial_read_len = + esp_http_client_read(http_client, reinterpret_cast(buffer) + read_len, buffer_size - read_len); + if (partial_read_len > 0) { + read_len += partial_read_len; // Accumulate the total read length. + ESP_LOGV(TAG, "Read data rlen %" PRIu16 ", b 0x%" PRIx8, read_len, buffer[0]); + // Reset retries on successful read. + retries = 0; } else { - ESP_LOGE(TAG, "Failed to read from HTTP client, error code: %d", read_len); - break; // Exit the loop on error + ESP_LOGW(TAG, "Failed to read data err %" PRIi16 ", rl %" PRIu16, partial_read_len, read_len); + // If no data was read, increment retries. + retries++; + vTaskDelay(pdMS_TO_TICKS(2)); // NOLINT } + App.feed_wdt(); // Feed the watchdog timer. + } + if (read_len != buffer_size) { + // Did not receive the full package within the timeout period + ESP_LOGE(TAG, "Failed to read full package, received only %" PRIu16 " of %" PRIu16 " bytes, retries %" PRIi16 ", errno %" PRIi16 ", done %" PRIu8, read_len, + buffer_size, retries, esp_http_client_get_errno(http_client), esp_http_client_is_complete_data_received(http_client)); + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return -1; } + ESP_LOGV(TAG, "%d bytes fetched, writing it to UART", read_len); + if (read_len > 0) { + recv_string.clear(); + this->write_array(buffer, buffer_size); + App.feed_wdt(); + this->recv_ret_string_(recv_string, this->upload_first_chunk_sent_ ? 500 : 5000, true); + this->content_length_ -= read_len; + const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_; +#ifdef USE_PSRAM + ESP_LOGD( + TAG, + "Uploaded %0.2f%%, remaining %" PRIu32 " bytes, free heap: %" PRIu32 " (DRAM) + %" PRIu32 " (PSRAM) bytes", + upload_percentage, this->content_length_, static_cast(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)), + static_cast(heap_caps_get_free_size(MALLOC_CAP_SPIRAM))); +#else + ESP_LOGD(TAG, "Uploaded %0.2f%%, remaining %" PRIu32 " bytes, free heap: %" PRIu32 " bytes", upload_percentage, + this->content_length_, static_cast(esp_get_free_heap_size())); +#endif + this->upload_first_chunk_sent_ = true; + if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request + ESP_LOGD(TAG, "recv_string [%s]", + format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + uint32_t result = 0; + for (int j = 0; j < 4; ++j) { + result += static_cast(recv_string[j + 1]) << (8 * j); + } + if (result > 0) { + ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result); + this->content_length_ = this->tft_size_ - result; + range_start = result; + } else { + range_start = range_end + 1; + } + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return range_end + 1; + } else if (recv_string[0] != 0x05 && recv_string[0] != 0x08) { // 0x05 == "ok" + ESP_LOGE(TAG, "Invalid response from Nextion: [%s]", + format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; + return -1; + } - // Deallocate the buffer when done - ESP_LOGV(TAG, "Deallocate buffer"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - delete[] buffer; - ESP_LOGVV(TAG, "Memory for buffer deallocated"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + recv_string.clear(); + } else if (read_len == 0) { + ESP_LOGD(TAG, "End of HTTP response reached"); + break; // Exit the loop if there is no more data to read + } else { + ESP_LOGE(TAG, "Failed to read from HTTP client, error code: %" PRIu16, read_len); + break; // Exit the loop on error + } } - ESP_LOGV(TAG, "Close http client"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - esp_http_client_close(client); - esp_http_client_cleanup(client); - ESP_LOGVV(TAG, "Client closed"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + range_start = range_end + 1; + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; return range_end + 1; } -bool NSPanelLovelace::upload_tft(const std::string &url) { +bool NSPanelLovelace::upload_tft(const std::string &url /*, uint32_t baud_rate*/) { ESP_LOGD(TAG, "Nextion TFT upload requested"); - ESP_LOGD(TAG, "url: %s", url.c_str()); + ESP_LOGD(TAG, "URL: %s", url.c_str()); if (this->is_updating_) { - ESP_LOGW(TAG, "Currently updating"); + ESP_LOGW(TAG, "Currently uploading"); return false; } @@ -174,142 +204,197 @@ bool NSPanelLovelace::upload_tft(const std::string &url) { return false; } - if (!this->reparse_mode_) { - this->start_reparse_mode_(); - } - this->is_updating_ = true; - // Define the configuration for the HTTP client - ESP_LOGV(TAG, "Establishing connection to HTTP server"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGD(TAG, "Exiting Nextion reparse mode"); + this->set_reparse_mode_(false); + + // Check if baud rate is supported + // this->original_baud_rate_ = this->parent_->get_baud_rate(); + // static const std::vector SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600, + // 115200, 230400, 250000, 256000, 512000, 921600}; + // if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) { + // baud_rate = this->original_baud_rate_; + // } + // ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); + // Define the configuration for the HTTP client + ESP_LOGD(TAG, "Initializing HTTP client"); + ESP_LOGD(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); esp_http_client_config_t config = {}; config.url = url.c_str(); + // config.host = nullptr; config.cert_pem = nullptr; config.method = HTTP_METHOD_HEAD; config.timeout_ms = 15000; config.disable_auto_redirect = false; config.max_redirection_count = 10; + config.is_async = false; + config.keep_alive_enable = true; + // config.event_handler = _http_event_handler; // Initialize the HTTP client with the configuration - ESP_LOGV(TAG, "Initializing HTTP client"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - esp_http_client_handle_t http = esp_http_client_init(&config); - if (!http) { + esp_http_client_handle_t http_client = esp_http_client_init(&config); + if (!http_client) { ESP_LOGE(TAG, "Failed to initialize HTTP client."); return this->upload_end_(false); } - + + esp_err_t err; // Perform the HTTP request - ESP_LOGV(TAG, "Check if the client could connect"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - esp_err_t err = esp_http_client_perform(http); + ESP_LOGD(TAG, "Check if the client could connect"); + ESP_LOGD(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); + err = esp_http_client_perform(http_client); if (err != ESP_OK) { ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err)); - esp_http_client_cleanup(http); + esp_http_client_cleanup(http_client); return this->upload_end_(false); } // Check the HTTP Status Code - ESP_LOGV(TAG, "Check the HTTP Status Code"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - int status_code = esp_http_client_get_status_code(http); - ESP_LOGV(TAG, "HTTP Status Code: %d", status_code); - size_t tft_file_size = esp_http_client_get_content_length(http); - ESP_LOGD(TAG, "TFT file size: %zu", tft_file_size); + ESP_LOGD(TAG, "Check the HTTP Status Code"); + ESP_LOGD(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); + int status_code = esp_http_client_get_status_code(http_client); + if (status_code != 200 && status_code != 206) { + return this->upload_end_(false); + } - ESP_LOGD(TAG, "Close HTTP connection"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - esp_http_client_close(http); - esp_http_client_cleanup(http); - ESP_LOGVV(TAG, "Connection closed"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - - if (tft_file_size < 4096) { - ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size); + this->tft_size_ = esp_http_client_get_content_length(http_client); + + ESP_LOGD(TAG, "TFT file size: %zu bytes", this->tft_size_); + if (this->tft_size_ < 4096 || this->tft_size_ > 134217728) { + ESP_LOGE(TAG, "File size check failed."); + ESP_LOGD(TAG, "Close HTTP connection"); + esp_http_client_close(http_client); + esp_http_client_cleanup(http_client); + ESP_LOGD(TAG, "Connection closed"); return this->upload_end_(false); } else { - ESP_LOGV(TAG, "File size check passed. Proceeding..."); + ESP_LOGD(TAG, "File size check passed. Proceeding..."); } - this->content_length_ = tft_file_size; - this->tft_size_ = tft_file_size; + this->content_length_ = this->tft_size_; - ESP_LOGD(TAG, "Updating Nextion"); + ESP_LOGD(TAG, "Uploading Nextion"); - // The Nextion will ignore the update command if it is sleeping - ESP_LOGV(TAG, "Wake-up Nextion"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - // TODO: review this - // this->send_nextion_command_("sleep=0"); // is this a valid command? + // The Nextion will ignore the upload command if it is sleeping + ESP_LOGD(TAG, "Wake-up Nextion"); + // todo: are these valid commands? + // this->send_nextion_command_("sleep=0"); + // this->send_nextion_command_("dim=100"); this->send_nextion_command_("dimmode~100~100"); vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT + ESP_LOGD(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); App.feed_wdt(); char command[128]; // Tells the Nextion the content length of the tft file and baud rate it will be sent at // Once the Nextion accepts the command it will wait until the file is successfully uploaded // If it fails for any reason a power cycle of the display will be needed - sprintf(command, "whmi-wris %d,%" PRIu32 ",1", this->content_length_, this->parent_->get_baud_rate()); + sprintf(command, "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, /*baud_rate*/ this->parent_->get_baud_rate()); // Clear serial receive buffer - ESP_LOGV(TAG, "Clear serial receive buffer"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGD(TAG, "Clear serial receive buffer"); uint8_t d; while (this->available()) { + App.feed_wdt(); this->read_byte(&d); }; + vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT + ESP_LOGD(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); - ESP_LOGV(TAG, "Send update instruction: %s", command); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGD(TAG, "Send upload instruction: %s", command); this->send_nextion_command_(command); + if (this->update_baud_rate_ != 115200) { + this->init_display_(this->update_baud_rate_); + } + + // if (baud_rate != this->original_baud_rate_) { + // ESP_LOGD(TAG, "Changing baud rate from %" PRIu32 " to %" PRIu32 " bps", this->original_baud_rate_, baud_rate); + // this->parent_->set_baud_rate(baud_rate); + // this->parent_->load_settings(); + // } + std::string response; - ESP_LOGV(TAG, "Waiting for upgrade response"); + ESP_LOGD(TAG, "Waiting for upgrade response"); this->recv_ret_string_(response, 5000, true); // This can take some time to return // The Nextion display will, if it's ready to accept data, send a 0x05 byte. - ESP_LOGD(TAG, "Upgrade response is [%s] - %zu bytes", + ESP_LOGD(TAG, "Upgrade response is [%s] - %zu byte(s)", format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str(), response.length()); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGD(TAG, "Free heap: %" PRIu32, esp_get_free_heap_size()); if (response.find(0x05) != std::string::npos) { - ESP_LOGV(TAG, "Preparation for tft update done"); + ESP_LOGD(TAG, "Preparation for TFT upload done"); } else { - ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str()); + ESP_LOGE(TAG, "Preparation for TFT upload failed %d \"%s\"", response[0], response.c_str()); + ESP_LOGD(TAG, "Close HTTP connection"); + esp_http_client_close(http_client); + esp_http_client_cleanup(http_client); + ESP_LOGD(TAG, "Connection closed"); + return this->upload_end_(false); + } + + ESP_LOGD(TAG, "Change the method to GET before starting the download"); + esp_err_t set_method_result = esp_http_client_set_method(http_client, HTTP_METHOD_GET); + if (set_method_result != ESP_OK) { + ESP_LOGE(TAG, "Failed to set HTTP method to GET: %s", esp_err_to_name(set_method_result)); return this->upload_end_(false); } - ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d, Heap Size %" PRIu32, url.c_str(), - content_length_, esp_get_free_heap_size()); + ESP_LOGD(TAG, "Uploading TFT to Nextion:"); + ESP_LOGD(TAG, " URL: %s", url.c_str()); + ESP_LOGD(TAG, " File size: %" PRIu32 " bytes", this->content_length_); + ESP_LOGD(TAG, " Free heap: %" PRIu32, esp_get_free_heap_size()); + + // Proceed with the content download as before - ESP_LOGV(TAG, "Starting transfer by chunks loop"); - ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); - int result = 0; - while (content_length_ > 0) { - result = upload_range_(url.c_str(), result); - if (result < 0) { - ESP_LOGE(TAG, "Error updating Nextion!"); + ESP_LOGD(TAG, "Starting transfer by chunks loop"); + + uint32_t position = 0; + while (this->content_length_ > 0) { + int upload_result = upload_by_chunks_(http_client, position); + if (upload_result < 0) { + ESP_LOGE(TAG, "Error uploading TFT to Nextion!"); + ESP_LOGD(TAG, "Close HTTP connection"); + esp_http_client_close(http_client); + esp_http_client_cleanup(http_client); + ESP_LOGD(TAG, "Connection closed"); return this->upload_end_(false); } App.feed_wdt(); - ESP_LOGV(TAG, "Heap Size %" PRIu32 ", Bytes left %d", esp_get_free_heap_size(), content_length_); + yield(); + ESP_LOGD(TAG, "Free heap: %" PRIu32 ", Bytes left: %" PRIu32, esp_get_free_heap_size(), this->content_length_); } - ESP_LOGD(TAG, "Successfully updated Nextion!"); + ESP_LOGD(TAG, "Successfully uploaded TFT to Nextion!"); - return upload_end_(true); + ESP_LOGD(TAG, "Close HTTP connection"); + esp_http_client_close(http_client); + esp_http_client_cleanup(http_client); + ESP_LOGD(TAG, "Connection closed"); + return this->upload_end_(true); } bool NSPanelLovelace::upload_end_(bool successful) { + ESP_LOGD(TAG, "Nextion TFT upload finished: %s", YESNO(successful)); this->is_updating_ = false; - ESP_LOGD(TAG, "Restarting Nextion"); + + // uint32_t baud_rate = this->parent_->get_baud_rate(); + // if (baud_rate != this->original_baud_rate_) { + // ESP_LOGD(TAG, "Changing baud rate back from %" PRIu32 " to %" PRIu32 " bps", baud_rate, this->original_baud_rate_); + // this->parent_->set_baud_rate(this->original_baud_rate_); + // this->parent_->load_settings(); + // } + this->soft_reset_display(); - vTaskDelay(pdMS_TO_TICKS(1500)); // NOLINT if (successful) { ESP_LOGD(TAG, "Restarting ESPHome"); - esp_restart(); // NOLINT(readability-static-accessed-through-instance) + delay(1500); // NOLINT + App.safe_reboot(); + } else { + ESP_LOGE(TAG, "Nextion TFT upload failed"); } return successful; }