From 74fd52e05fdc6f63ef64ada77f8a7b8d63de8c45 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:29:57 +0200 Subject: [PATCH 1/3] [nextion] Set alternative TFT update baud rate (#6587) --- esphome/components/nextion/nextion.h | 16 +++++++++-- .../nextion/nextion_upload_arduino.cpp | 27 +++++++++++++++++-- .../components/nextion/nextion_upload_idf.cpp | 27 +++++++++++++++++-- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 833db675fc..b6a6204cd4 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -992,14 +992,26 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe #endif /** - * Upload the tft file and soft reset Nextion + * @brief Uploads the TFT file to the Nextion display. + * + * This function initiates the upload of a TFT file to the Nextion display. Users can specify a target baud rate for + * the transfer. If the provided baud rate is not supported by Nextion, the function defaults to using the current + * baud rate set for the display. If no baud rate is specified (or if 0 is passed), the current baud rate is used. + * + * Supported baud rates are: 2400, 4800, 9600, 19200, 31250, 38400, 57600, 115200, 230400, 250000, 256000, 512000 + * and 921600. Selecting a baud rate supported by both the Nextion display and the host hardware is essential for + * ensuring a successful upload process. + * + * @param baud_rate The desired baud rate for the TFT file transfer, specified as an unsigned 32-bit integer. + * If the specified baud rate is not supported, or if 0 is passed, the function will use the current baud rate. + * The default value is 0, which implies using the current baud rate. * @param exit_reparse If true, the function exits reparse mode before uploading the TFT file. This parameter * defaults to true, ensuring that the display is ready to receive and apply the new TFT file without needing * to manually reset or reconfigure. Exiting reparse mode is recommended for most upload scenarios to ensure * the display properly processes the uploaded file command. * @return bool True: Transfer completed successfuly, False: Transfer failed. */ - bool upload_tft(bool exit_reparse = true); + bool upload_tft(uint32_t baud_rate = 0, bool exit_reparse = true); void dump_config() override; diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index 4f8df67006..6e7c8b9563 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -141,7 +141,7 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { return range_end + 1; } -bool Nextion::upload_tft(bool exit_reparse) { +bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGD(TAG, "Nextion TFT upload requested"); ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse)); ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str()); @@ -166,6 +166,15 @@ bool Nextion::upload_tft(bool exit_reparse) { } } + // 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); + HTTPClient http; http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along bool begin_status = false; @@ -244,7 +253,7 @@ bool Nextion::upload_tft(bool exit_reparse) { // 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,%d,1", this->content_length_, this->parent_->get_baud_rate()); + sprintf(command, "whmi-wris %d,%d,1", this->content_length_, baud_rate); // Clear serial receive buffer uint8_t d; @@ -254,6 +263,12 @@ bool Nextion::upload_tft(bool exit_reparse) { this->send_command_(command); + 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(); + } + App.feed_wdt(); std::string response; @@ -335,6 +350,14 @@ bool Nextion::upload_tft(bool exit_reparse) { bool Nextion::upload_end_(bool successful) { this->is_updating_ = false; + + 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(); + } + ESP_LOGD(TAG, "Restarting Nextion"); this->soft_reset(); if (successful) { diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_idf.cpp index d08f970551..163b497d3b 100644 --- a/esphome/components/nextion/nextion_upload_idf.cpp +++ b/esphome/components/nextion/nextion_upload_idf.cpp @@ -160,7 +160,7 @@ int Nextion::upload_range(const std::string &url, int range_start) { return range_end + 1; } -bool Nextion::upload_tft(bool exit_reparse) { +bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGD(TAG, "Nextion TFT upload requested"); ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse)); ESP_LOGD(TAG, "url: %s", this->tft_url_.c_str()); @@ -185,6 +185,15 @@ bool Nextion::upload_tft(bool exit_reparse) { } } + // 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_LOGV(TAG, "Establishing connection to HTTP server"); ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); @@ -254,7 +263,7 @@ bool Nextion::upload_tft(bool exit_reparse) { // 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 %d,%" PRIu32 ",1", this->content_length_, baud_rate); // Clear serial receive buffer ESP_LOGV(TAG, "Clear serial receive buffer"); @@ -268,6 +277,12 @@ bool Nextion::upload_tft(bool exit_reparse) { ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); this->send_command_(command); + 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"); this->recv_ret_string_(response, 5000, true); // This can take some time to return @@ -308,6 +323,14 @@ bool Nextion::upload_tft(bool exit_reparse) { bool Nextion::upload_end(bool successful) { this->is_updating_ = false; + + 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(); + } + ESP_LOGD(TAG, "Restarting Nextion"); this->soft_reset(); vTaskDelay(pdMS_TO_TICKS(1500)); // NOLINT From 6fe328ef2bb9d50efe612d2176df8f562b8bd1a5 Mon Sep 17 00:00:00 2001 From: Anton Sergunov Date: Tue, 30 Apr 2024 16:35:41 +0600 Subject: [PATCH 2/3] [TM1637] Let turn off the display (#6656) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/tm1637/tm1637.cpp | 2 +- esphome/components/tm1637/tm1637.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index 8d7630bd1d..2f2d4b707a 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -225,7 +225,7 @@ void TM1637Display::display() { // Write display CTRL CMND + brightness this->start_(); - this->send_byte_(TM1637_CMD_CTRL + ((this->intensity_ & 0x7) | 0x08)); + this->send_byte_(TM1637_CMD_CTRL + ((this->intensity_ & 0x7) | (this->on_ ? 0x08 : 0x00))); this->stop_(); } bool TM1637Display::send_byte_(uint8_t b) { diff --git a/esphome/components/tm1637/tm1637.h b/esphome/components/tm1637/tm1637.h index aba0071b12..d44680c623 100644 --- a/esphome/components/tm1637/tm1637.h +++ b/esphome/components/tm1637/tm1637.h @@ -49,6 +49,7 @@ class TM1637Display : public PollingComponent { void set_intensity(uint8_t intensity) { this->intensity_ = intensity; } void set_inverted(bool inverted) { this->inverted_ = inverted; } void set_length(uint8_t length) { this->length_ = length; } + void set_on(bool on) { this->on_ = on; } void display(); @@ -76,6 +77,7 @@ class TM1637Display : public PollingComponent { uint8_t intensity_; uint8_t length_; bool inverted_; + bool on_{true}; optional writer_{}; uint8_t buffer_[6] = {0}; #ifdef USE_BINARY_SENSOR From c299dff124937f4a2253956f72bac77f6c4c79ee Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Tue, 30 Apr 2024 23:43:49 +0200 Subject: [PATCH 3/3] [nextion] Use persistent http connection for TFT upload (Arduino) (#6582) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/nextion/nextion.h | 40 +- .../nextion/nextion_upload_arduino.cpp | 397 +++++++++--------- 2 files changed, 213 insertions(+), 224 deletions(-) diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index b6a6204cd4..7938af0ea5 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -976,6 +976,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @return Whether the send was successful. */ bool send_command(const char *command); + /** * Manually send a raw formatted command to the display. * @param format The printf-style command format, like "vis %s,0" @@ -989,7 +990,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * Set the tft file URL. https seems problematic with arduino.. */ void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; } -#endif /** * @brief Uploads the TFT file to the Nextion display. @@ -1013,6 +1013,8 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ bool upload_tft(uint32_t baud_rate = 0, bool exit_reparse = true); +#endif + void dump_config() override; /** @@ -1134,6 +1136,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void all_components_send_state_(bool force_update = false); uint64_t comok_sent_ = 0; bool remove_from_q_(bool report_empty = true); + /** * @brief * Sends commands ignoring of the Nextion has been setup. @@ -1175,20 +1178,16 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void check_pending_waveform_(); #ifdef USE_NEXTION_TFT_UPLOAD - uint32_t content_length_ = 0; - int tft_size_ = 0; - uint32_t original_baud_rate_ = 0; - bool upload_first_chunk_sent_ = false; - - std::string tft_url_; - uint8_t *transfer_buffer_{nullptr}; - size_t transfer_buffer_size_; - #ifdef USE_ESP8266 WiFiClient *wifi_client_{nullptr}; BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; WiFiClient *get_wifi_client_(); #endif + std::string tft_url_; + uint32_t content_length_ = 0; + int tft_size_ = 0; + uint32_t original_baud_rate_ = 0; + bool upload_first_chunk_sent_ = false; #ifdef ARDUINO /** @@ -1198,18 +1197,8 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @param int range_start Position of next byte to transfer. * @return position of last byte transferred, -1 for failure. */ - int upload_by_chunks_(HTTPClient *http, int range_start); + int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start); - bool upload_with_range_(uint32_t range_start, uint32_t range_end); - - /** - * start update tft file to nextion. - * - * @param const uint8_t *file_buf - * @param size_t buf_size - * @return true if success, false for failure. - */ - bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size); /** * Ends the upload process, restart Nextion and, if successful, * restarts ESP @@ -1217,6 +1206,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @return bool True: Transfer completed successfuly, False: Transfer failed. */ bool upload_end_(bool successful); + #elif defined(USE_ESP_IDF) /** * will request 4096 bytes chunks from the web server @@ -1226,6 +1216,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @return position of last byte transferred, -1 for failure. */ int upload_range(const std::string &url, int range_start); + /** * Ends the upload process, restart Nextion and, if successful, * restarts ESP @@ -1233,7 +1224,12 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @return bool True: Transfer completed successfuly, False: Transfer failed. */ bool upload_end(bool successful); -#endif // ARDUINO vs ESP-IDF +#endif // ARDUINO vs USE_ESP_IDF + /** + * Returns the ESP Free Heap memory. This is framework independent. + * @return Free Heap in bytes. + */ + uint32_t get_free_heap_(); #endif // USE_NEXTION_TFT_UPLOAD diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index 6e7c8b9563..b199db91b7 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -1,13 +1,14 @@ #include "nextion.h" -#ifdef ARDUINO #ifdef USE_NEXTION_TFT_UPLOAD +#ifdef ARDUINO #include "esphome/core/application.h" #include "esphome/core/defines.h" #include "esphome/core/util.h" #include "esphome/core/log.h" #include "esphome/components/network/util.h" +#include #ifdef USE_ESP32 #include @@ -20,124 +21,132 @@ static const char *const TAG = "nextion.upload.arduino"; // Followed guide // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 -int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { - int range_end = 0; +inline uint32_t Nextion::get_free_heap_() { +#if defined(USE_ESP32) + return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#elif defined(USE_ESP8266) + return EspClass::getFreeHeap(); +#endif // USE_ESP32 vs USE_ESP8266 +} - if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip - range_end = 16384 - 1; - } else { - range_end = range_start + this->transfer_buffer_size_ - 1; +int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { + uint32_t range_size = this->tft_size_ - range_start; + ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_()); + uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1; + ESP_LOGD(TAG, "Range start: %" PRIu32, range_start); + if (range_size <= 0 or 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"); + return -1; } - if (range_end > this->tft_size_) - range_end = this->tft_size_; - -#ifdef USE_ESP8266 -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) - http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - http->setFollowRedirects(true); -#endif -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - http->setRedirectLimit(3); -#endif -#endif // USE_ESP8266 - - char range_header[64]; - sprintf(range_header, "bytes=%d-%d", range_start, range_end); - - ESP_LOGD(TAG, "Requesting range: %s", range_header); - - int tries = 1; - int code = 0; - bool begin_status = false; - while (tries <= 5) { -#ifdef USE_ESP32 - begin_status = http->begin(this->tft_url_.c_str()); -#endif -#ifdef USE_ESP8266 - begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); -#endif - - ++tries; - if (!begin_status) { - ESP_LOGD(TAG, "upload_by_chunks_: connection failed"); - delay(500); // NOLINT - continue; - } - - http->addHeader("Range", range_header); - - code = http->GET(); - if (code == 200 || code == 206) { - break; - } - ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(), - HTTPClient::errorToString(code).c_str(), tries); - http->end(); - App.feed_wdt(); - delay(500); // NOLINT + char range_header[32]; + sprintf(range_header, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end); + ESP_LOGV(TAG, "Requesting range: %s", range_header); + http_client.addHeader("Range", range_header); + int code = http_client.GET(); + if (code != HTTP_CODE_OK and code != HTTP_CODE_PARTIAL_CONTENT) { + ESP_LOGW(TAG, "HTTP Request failed; Error: %s", HTTPClient::errorToString(code).c_str()); + return -1; } - if (tries > 5) { + // 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; } std::string recv_string; - size_t size = 0; - int fetched = 0; - int range = range_end - range_start; - - while (fetched < range) { - size = http->getStreamPtr()->available(); - if (!size) { - App.feed_wdt(); - delay(0); - continue; - } - int c = http->getStreamPtr()->readBytes( - &this->transfer_buffer_[fetched], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size)); - fetched += c; - } - http->end(); - ESP_LOGN(TAG, "Fetched %d of %d bytes", fetched, this->content_length_); - - // upload fetched segments to the display in 4KB chunks - int write_len; - for (int i = 0; i < range; i += 4096) { + while (true) { App.feed_wdt(); - write_len = this->content_length_ < 4096 ? this->content_length_ : 4096; - this->write_array(&this->transfer_buffer_[i], write_len); - this->content_length_ -= write_len; - ESP_LOGD(TAG, "Uploaded %0.2f %%; %d bytes remaining", - 100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_); - - if (!this->upload_first_chunk_sent_) { - this->upload_first_chunk_sent_ = true; - delay(500); // NOLINT - } - - this->recv_ret_string_(recv_string, 4096, true); - if (recv_string[0] != 0x05) { // 0x05 == "ok" - ESP_LOGD(TAG, "recv_string [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); - } - - // handle partial upload request - if (recv_string[0] == 0x08 && recv_string.size() == 5) { - uint32_t result = 0; - for (int j = 0; j < 4; ++j) { - result += static_cast(recv_string[j + 1]) << (8 * j); - } - if (result > 0) { - ESP_LOGD(TAG, "Nextion reported new range %d", result); - this->content_length_ = this->tft_size_ - result; - return result; + 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; + const uint32_t start_time = millis(); + while (read_len < buffer_size && millis() - start_time < 5000) { + if (http_client.getStreamPtr()->available() > 0) { + partial_read_len = + http_client.getStreamPtr()->readBytes(reinterpret_cast(buffer) + read_len, buffer_size - read_len); + read_len += partial_read_len; + if (partial_read_len > 0) { + App.feed_wdt(); + delay(2); + } } } - recv_string.clear(); + 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", read_len, + buffer_size); + // 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, 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_; +#if defined(USE_ESP32) && defined(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_, this->get_free_heap_()); +#endif + 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 and 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; + } + + 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 + } else { + ESP_LOGE(TAG, "Failed to read from HTTP client, error code: %d", read_len); + break; // Exit the loop on error + } } - + range_start = range_end + 1; + // Deallocate buffer + allocator.deallocate(buffer, 4096); + buffer = nullptr; return range_end + 1; } @@ -147,12 +156,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str()); if (this->is_updating_) { - ESP_LOGD(TAG, "Currently updating"); + ESP_LOGW(TAG, "Currently uploading"); return false; } if (!network::is_connected()) { - ESP_LOGD(TAG, "network is not connected"); + ESP_LOGE(TAG, "Network is not connected"); return false; } @@ -175,43 +184,42 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { } ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); - HTTPClient http; - http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along + // Define the configuration for the HTTP client + ESP_LOGV(TAG, "Initializing HTTP client"); + ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_()); + HTTPClient http_client; + http_client.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along + bool begin_status = false; #ifdef USE_ESP32 - begin_status = http.begin(this->tft_url_.c_str()); + begin_status = http_client.begin(this->tft_url_.c_str()); #endif #ifdef USE_ESP8266 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http_client.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); #elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - http.setFollowRedirects(true); + http_client.setFollowRedirects(true); #endif #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - http.setRedirectLimit(3); + http_client.setRedirectLimit(3); #endif - begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); -#endif - + begin_status = http_client.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif // USE_ESP8266 if (!begin_status) { this->is_updating_ = false; ESP_LOGD(TAG, "Connection failed"); - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); - allocator.deallocate(this->transfer_buffer_, this->transfer_buffer_size_); return false; } else { ESP_LOGD(TAG, "Connected"); } - - http.addHeader("Range", "bytes=0-255"); + http_client.addHeader("Range", "bytes=0-255"); const char *header_names[] = {"Content-Range"}; - http.collectHeaders(header_names, 1); + http_client.collectHeaders(header_names, 1); ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str()); - - http.setReuse(true); + http_client.setReuse(true); // try up to 5 times. DNS sometimes needs a second try or so int tries = 1; - int code = http.GET(); + int code = http_client.GET(); delay(100); // NOLINT App.feed_wdt(); @@ -221,34 +229,41 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { delay(250); // NOLINT App.feed_wdt(); - code = http.GET(); + code = http_client.GET(); ++tries; } - if ((code != 200 && code != 206) || tries > 5) { + if (code != 200 and code != 206) { return this->upload_end_(false); } - String content_range_string = http.header("Content-Range"); + String content_range_string = http_client.header("Content-Range"); content_range_string.remove(0, 12); - this->content_length_ = content_range_string.toInt(); - this->tft_size_ = content_length_; - http.end(); + this->tft_size_ = content_range_string.toInt(); - if (this->content_length_ < 4096) { - ESP_LOGE(TAG, "Failed to get file size"); + ESP_LOGD(TAG, "TFT file size: %zu bytes", this->tft_size_); + if (this->tft_size_ < 4096) { + ESP_LOGE(TAG, "File size check failed."); + ESP_LOGD(TAG, "Close HTTP connection"); + http_client.end(); + ESP_LOGV(TAG, "Connection closed"); return this->upload_end_(false); + } else { + ESP_LOGV(TAG, "File size check passed. Proceeding..."); } + this->content_length_ = this->tft_size_; - ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str()); - // The Nextion will ignore the update command if it is sleeping + ESP_LOGD(TAG, "Uploading Nextion"); + // The Nextion will ignore the upload command if it is sleeping + ESP_LOGV(TAG, "Wake-up Nextion"); + this->ignore_is_setup_ = true; this->send_command_("sleep=0"); - this->set_backlight_brightness(1.0); + this->send_command_("dim=100"); delay(250); // NOLINT + ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_()); 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 @@ -256,11 +271,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { sprintf(command, "whmi-wris %d,%d,1", this->content_length_, baud_rate); // Clear serial receive buffer - uint8_t d; - while (this->available()) { - this->read_byte(&d); - }; + ESP_LOGV(TAG, "Clear serial receive buffer"); + this->reset_(false); + delay(250); // NOLINT + ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGV(TAG, "Send upload instruction: %s", command); this->send_command_(command); if (baud_rate != this->original_baud_rate_) { @@ -272,84 +288,60 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { App.feed_wdt(); std::string response; - ESP_LOGD(TAG, "Waiting for upgrade response"); - this->recv_ret_string_(response, 2000, true); // This can take some time to return + ESP_LOGV(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()); - - for (size_t i = 0; i < response.length(); i++) { - ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); - } + ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_()); if (response.find(0x05) != std::string::npos) { - ESP_LOGD(TAG, "preparation for tft update done"); + ESP_LOGV(TAG, "Preparation for TFT upload done"); } else { - ESP_LOGD(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"); + http_client.end(); + ESP_LOGV(TAG, "Connection closed"); return this->upload_end_(false); } - // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 -#ifdef USE_ESP32 - uint32_t chunk_size = 8192; - if (heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0) { - chunk_size = this->content_length_; - } else { - if (ESP.getFreeHeap() > 81920) { // Ensure some FreeHeap to other things and limit chunk size - chunk_size = ESP.getFreeHeap() - 65536; - chunk_size = int(chunk_size / 4096) * 4096; - chunk_size = chunk_size > 65536 ? 65536 : chunk_size; - } else if (ESP.getFreeHeap() < 32768) { - chunk_size = 4096; - } - } -#else - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - uint32_t chunk_size = ESP.getFreeHeap() < 16384 ? 4096 : 8192; -#endif + ESP_LOGD(TAG, "Uploading TFT to Nextion:"); + ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str()); + ESP_LOGD(TAG, " File size: %d bytes", this->content_length_); + ESP_LOGD(TAG, " Free heap: %" PRIu32, this->get_free_heap_()); - if (this->transfer_buffer_ == nullptr) { - ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); - this->transfer_buffer_ = allocator.allocate(chunk_size); - if (this->transfer_buffer_ == nullptr) { // Try a smaller size - ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); - chunk_size = 4096; - ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); - this->transfer_buffer_ = allocator.allocate(chunk_size); + // Proceed with the content download as before - if (!this->transfer_buffer_) - return this->upload_end_(false); - } + ESP_LOGV(TAG, "Starting transfer by chunks loop"); - this->transfer_buffer_size_ = chunk_size; - } - - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", - this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap()); - - int result = 0; + uint32_t position = 0; while (this->content_length_ > 0) { - result = this->upload_by_chunks_(&http, result); - if (result < 0) { - ESP_LOGD(TAG, "Error updating Nextion!"); + 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"); + http_client.end(); + ESP_LOGV(TAG, "Connection closed"); return this->upload_end_(false); } App.feed_wdt(); - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_); + ESP_LOGV(TAG, "Free heap: %" PRIu32 ", Bytes left: %" PRIu32, this->get_free_heap_(), this->content_length_); } - ESP_LOGD(TAG, "Successfully updated Nextion!"); - return this->upload_end_(true); + ESP_LOGD(TAG, "Successfully uploaded TFT to Nextion!"); + + ESP_LOGD(TAG, "Close HTTP connection"); + http_client.end(); + ESP_LOGV(TAG, "Connection closed"); + return upload_end_(true); } bool Nextion::upload_end_(bool successful) { + ESP_LOGD(TAG, "Nextion TFT upload finished: %s", YESNO(successful)); this->is_updating_ = false; + this->ignore_is_setup_ = false; uint32_t baud_rate = this->parent_->get_baud_rate(); if (baud_rate != this->original_baud_rate_) { @@ -358,12 +350,12 @@ bool Nextion::upload_end_(bool successful) { this->parent_->load_settings(); } - ESP_LOGD(TAG, "Restarting Nextion"); - this->soft_reset(); if (successful) { + ESP_LOGD(TAG, "Restarting ESPHome"); delay(1500); // NOLINT - ESP_LOGD(TAG, "Restarting esphome"); - ESP.restart(); // NOLINT(readability-static-accessed-through-instance) + arch_restart(); + } else { + ESP_LOGE(TAG, "Nextion TFT upload failed"); } return successful; } @@ -386,9 +378,10 @@ WiFiClient *Nextion::get_wifi_client_() { } return this->wifi_client_; } -#endif +#endif // USE_ESP8266 + } // namespace nextion } // namespace esphome -#endif // USE_NEXTION_TFT_UPLOAD #endif // ARDUINO +#endif // USE_NEXTION_TFT_UPLOAD