[nextion] Use persistent http connection for TFT upload (Arduino) (#6582)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Edward Firmo 2024-04-30 23:43:49 +02:00 committed by GitHub
parent 6fe328ef2b
commit c299dff124
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 213 additions and 224 deletions

View file

@ -976,6 +976,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
* @return Whether the send was successful. * @return Whether the send was successful.
*/ */
bool send_command(const char *command); bool send_command(const char *command);
/** /**
* Manually send a raw formatted command to the display. * Manually send a raw formatted command to the display.
* @param format The printf-style command format, like "vis %s,0" * @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.. * Set the tft file URL. https seems problematic with arduino..
*/ */
void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; } void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; }
#endif
/** /**
* @brief Uploads the TFT file to the Nextion display. * @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); bool upload_tft(uint32_t baud_rate = 0, bool exit_reparse = true);
#endif
void dump_config() override; 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); void all_components_send_state_(bool force_update = false);
uint64_t comok_sent_ = 0; uint64_t comok_sent_ = 0;
bool remove_from_q_(bool report_empty = true); bool remove_from_q_(bool report_empty = true);
/** /**
* @brief * @brief
* Sends commands ignoring of the Nextion has been setup. * 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_(); void check_pending_waveform_();
#ifdef USE_NEXTION_TFT_UPLOAD #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 #ifdef USE_ESP8266
WiFiClient *wifi_client_{nullptr}; WiFiClient *wifi_client_{nullptr};
BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr};
WiFiClient *get_wifi_client_(); WiFiClient *get_wifi_client_();
#endif #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 #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. * @param int range_start Position of next byte to transfer.
* @return position of last byte transferred, -1 for failure. * @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, * Ends the upload process, restart Nextion and, if successful,
* restarts ESP * restarts ESP
@ -1217,6 +1206,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
* @return bool True: Transfer completed successfuly, False: Transfer failed. * @return bool True: Transfer completed successfuly, False: Transfer failed.
*/ */
bool upload_end_(bool successful); bool upload_end_(bool successful);
#elif defined(USE_ESP_IDF) #elif defined(USE_ESP_IDF)
/** /**
* will request 4096 bytes chunks from the web server * 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. * @return position of last byte transferred, -1 for failure.
*/ */
int upload_range(const std::string &url, int range_start); int upload_range(const std::string &url, int range_start);
/** /**
* Ends the upload process, restart Nextion and, if successful, * Ends the upload process, restart Nextion and, if successful,
* restarts ESP * restarts ESP
@ -1233,7 +1224,12 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
* @return bool True: Transfer completed successfuly, False: Transfer failed. * @return bool True: Transfer completed successfuly, False: Transfer failed.
*/ */
bool upload_end(bool successful); 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 #endif // USE_NEXTION_TFT_UPLOAD

View file

@ -1,13 +1,14 @@
#include "nextion.h" #include "nextion.h"
#ifdef ARDUINO
#ifdef USE_NEXTION_TFT_UPLOAD #ifdef USE_NEXTION_TFT_UPLOAD
#ifdef ARDUINO
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/util.h" #include "esphome/core/util.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/components/network/util.h" #include "esphome/components/network/util.h"
#include <cinttypes>
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_heap_caps.h> #include <esp_heap_caps.h>
@ -20,124 +21,132 @@ static const char *const TAG = "nextion.upload.arduino";
// Followed guide // Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { inline uint32_t Nextion::get_free_heap_() {
int range_end = 0; #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 int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
range_end = 16384 - 1; uint32_t range_size = this->tft_size_ - range_start;
} else { ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_());
range_end = range_start + this->transfer_buffer_size_ - 1; 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_) char range_header[32];
range_end = this->tft_size_; sprintf(range_header, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end);
ESP_LOGV(TAG, "Requesting range: %s", range_header);
#ifdef USE_ESP8266 http_client.addHeader("Range", range_header);
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) int code = http_client.GET();
http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); if (code != HTTP_CODE_OK and code != HTTP_CODE_PARTIAL_CONTENT) {
#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) ESP_LOGW(TAG, "HTTP Request failed; Error: %s", HTTPClient::errorToString(code).c_str());
http->setFollowRedirects(true); return -1;
#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); // Allocate the buffer dynamically
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
code = http->GET(); uint8_t *buffer = allocator.allocate(4096);
if (code == 200 || code == 206) { if (!buffer) {
break; ESP_LOGE(TAG, "Failed to allocate upload buffer");
}
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
}
if (tries > 5) {
return -1; return -1;
} }
std::string recv_string; std::string recv_string;
size_t size = 0; while (true) {
int fetched = 0;
int range = range_end - range_start;
while (fetched < range) {
size = http->getStreamPtr()->available();
if (!size) {
App.feed_wdt(); App.feed_wdt();
delay(0); const uint16_t buffer_size =
continue; this->content_length_ < 4096 ? this->content_length_ : 4096; // Limits buffer to the remaining data
} ESP_LOGV(TAG, "Fetching %" PRIu16 " bytes from HTTP", buffer_size);
int c = http->getStreamPtr()->readBytes( uint16_t read_len = 0;
&this->transfer_buffer_[fetched], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size)); int partial_read_len = 0;
fetched += c; const uint32_t start_time = millis();
} while (read_len < buffer_size && millis() - start_time < 5000) {
http->end(); if (http_client.getStreamPtr()->available() > 0) {
ESP_LOGN(TAG, "Fetched %d of %d bytes", fetched, this->content_length_); partial_read_len =
http_client.getStreamPtr()->readBytes(reinterpret_cast<char *>(buffer) + read_len, buffer_size - read_len);
// upload fetched segments to the display in 4KB chunks read_len += partial_read_len;
int write_len; if (partial_read_len > 0) {
for (int i = 0; i < range; i += 4096) {
App.feed_wdt(); App.feed_wdt();
write_len = this->content_length_ < 4096 ? this->content_length_ : 4096; delay(2);
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" 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<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)),
static_cast<uint32_t>(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]", ESP_LOGD(TAG, "recv_string [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str()); format_hex_pretty(reinterpret_cast<const uint8_t *>(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; uint32_t result = 0;
for (int j = 0; j < 4; ++j) { for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j); result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
} }
if (result > 0) { if (result > 0) {
ESP_LOGD(TAG, "Nextion reported new range %d", result); ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result);
this->content_length_ = this->tft_size_ - result; this->content_length_ = this->tft_size_ - result;
return result; range_start = result;
} else {
range_start = range_end + 1;
} }
} // Deallocate buffer
recv_string.clear(); 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<const uint8_t *>(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; 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()); ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str());
if (this->is_updating_) { if (this->is_updating_) {
ESP_LOGD(TAG, "Currently updating"); ESP_LOGW(TAG, "Currently uploading");
return false; return false;
} }
if (!network::is_connected()) { if (!network::is_connected()) {
ESP_LOGD(TAG, "network is not connected"); ESP_LOGE(TAG, "Network is not connected");
return false; 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); ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
HTTPClient http; // Define the configuration for the HTTP client
http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along 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; bool begin_status = false;
#ifdef USE_ESP32 #ifdef USE_ESP32
begin_status = http.begin(this->tft_url_.c_str()); begin_status = http_client.begin(this->tft_url_.c_str());
#endif #endif
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) #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) #elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
http.setFollowRedirects(true); http_client.setFollowRedirects(true);
#endif #endif
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
http.setRedirectLimit(3); http_client.setRedirectLimit(3);
#endif #endif
begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); begin_status = http_client.begin(*this->get_wifi_client_(), this->tft_url_.c_str());
#endif #endif // USE_ESP8266
if (!begin_status) { if (!begin_status) {
this->is_updating_ = false; this->is_updating_ = false;
ESP_LOGD(TAG, "Connection failed"); ESP_LOGD(TAG, "Connection failed");
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
allocator.deallocate(this->transfer_buffer_, this->transfer_buffer_size_);
return false; return false;
} else { } else {
ESP_LOGD(TAG, "Connected"); ESP_LOGD(TAG, "Connected");
} }
http_client.addHeader("Range", "bytes=0-255");
http.addHeader("Range", "bytes=0-255");
const char *header_names[] = {"Content-Range"}; 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()); ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str());
http_client.setReuse(true);
http.setReuse(true);
// try up to 5 times. DNS sometimes needs a second try or so // try up to 5 times. DNS sometimes needs a second try or so
int tries = 1; int tries = 1;
int code = http.GET(); int code = http_client.GET();
delay(100); // NOLINT delay(100); // NOLINT
App.feed_wdt(); App.feed_wdt();
@ -221,34 +229,41 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
delay(250); // NOLINT delay(250); // NOLINT
App.feed_wdt(); App.feed_wdt();
code = http.GET(); code = http_client.GET();
++tries; ++tries;
} }
if ((code != 200 && code != 206) || tries > 5) { if (code != 200 and code != 206) {
return this->upload_end_(false); 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); content_range_string.remove(0, 12);
this->content_length_ = content_range_string.toInt(); this->tft_size_ = content_range_string.toInt();
this->tft_size_ = content_length_;
http.end();
if (this->content_length_ < 4096) { ESP_LOGD(TAG, "TFT file size: %zu bytes", this->tft_size_);
ESP_LOGE(TAG, "Failed to get file 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); 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()); ESP_LOGD(TAG, "Uploading Nextion");
// The Nextion will ignore the update command if it is sleeping
// 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->send_command_("sleep=0");
this->set_backlight_brightness(1.0); this->send_command_("dim=100");
delay(250); // NOLINT delay(250); // NOLINT
ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_());
App.feed_wdt(); App.feed_wdt();
char command[128]; char command[128];
// Tells the Nextion the content length of the tft file and baud rate it will be sent at // 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 // 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); sprintf(command, "whmi-wris %d,%d,1", this->content_length_, baud_rate);
// Clear serial receive buffer // Clear serial receive buffer
uint8_t d; ESP_LOGV(TAG, "Clear serial receive buffer");
while (this->available()) { this->reset_(false);
this->read_byte(&d); 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); this->send_command_(command);
if (baud_rate != this->original_baud_rate_) { 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(); App.feed_wdt();
std::string response; std::string response;
ESP_LOGD(TAG, "Waiting for upgrade response"); ESP_LOGV(TAG, "Waiting for upgrade response");
this->recv_ret_string_(response, 2000, true); // This can take some time to return 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. // 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<const uint8_t *>(response.data()), response.size()).c_str(), format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
response.length()); response.length());
ESP_LOGV(TAG, "Free heap: %" PRIu32, this->get_free_heap_());
for (size_t i = 0; i < response.length(); i++) {
ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]);
}
if (response.find(0x05) != std::string::npos) { if (response.find(0x05) != std::string::npos) {
ESP_LOGD(TAG, "preparation for tft update done"); ESP_LOGV(TAG, "Preparation for TFT upload done");
} else { } 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); return this->upload_end_(false);
} }
// Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 ESP_LOGD(TAG, "Uploading TFT to Nextion:");
#ifdef USE_ESP32 ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str());
uint32_t chunk_size = 8192; ESP_LOGD(TAG, " File size: %d bytes", this->content_length_);
if (heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0) { ESP_LOGD(TAG, " Free heap: %" PRIu32, this->get_free_heap_());
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
if (this->transfer_buffer_ == nullptr) { // Proceed with the content download as before
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::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);
if (!this->transfer_buffer_) ESP_LOGV(TAG, "Starting transfer by chunks loop");
return this->upload_end_(false);
}
this->transfer_buffer_size_ = chunk_size; uint32_t position = 0;
}
// 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;
while (this->content_length_ > 0) { while (this->content_length_ > 0) {
result = this->upload_by_chunks_(&http, result); int upload_result = upload_by_chunks_(http_client, position);
if (result < 0) { if (upload_result < 0) {
ESP_LOGD(TAG, "Error updating Nextion!"); 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); return this->upload_end_(false);
} }
App.feed_wdt(); App.feed_wdt();
// NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGV(TAG, "Free heap: %" PRIu32 ", Bytes left: %" PRIu32, this->get_free_heap_(), this->content_length_);
ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), 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) { bool Nextion::upload_end_(bool successful) {
ESP_LOGD(TAG, "Nextion TFT upload finished: %s", YESNO(successful));
this->is_updating_ = false; this->is_updating_ = false;
this->ignore_is_setup_ = false;
uint32_t baud_rate = this->parent_->get_baud_rate(); uint32_t baud_rate = this->parent_->get_baud_rate();
if (baud_rate != this->original_baud_rate_) { if (baud_rate != this->original_baud_rate_) {
@ -358,12 +350,12 @@ bool Nextion::upload_end_(bool successful) {
this->parent_->load_settings(); this->parent_->load_settings();
} }
ESP_LOGD(TAG, "Restarting Nextion");
this->soft_reset();
if (successful) { if (successful) {
ESP_LOGD(TAG, "Restarting ESPHome");
delay(1500); // NOLINT delay(1500); // NOLINT
ESP_LOGD(TAG, "Restarting esphome"); arch_restart();
ESP.restart(); // NOLINT(readability-static-accessed-through-instance) } else {
ESP_LOGE(TAG, "Nextion TFT upload failed");
} }
return successful; return successful;
} }
@ -386,9 +378,10 @@ WiFiClient *Nextion::get_wifi_client_() {
} }
return this->wifi_client_; return this->wifi_client_;
} }
#endif #endif // USE_ESP8266
} // namespace nextion } // namespace nextion
} // namespace esphome } // namespace esphome
#endif // USE_NEXTION_TFT_UPLOAD
#endif // ARDUINO #endif // ARDUINO
#endif // USE_NEXTION_TFT_UPLOAD