Nextion TFT upload IDF memory optimization (#6128)

* Nextion TFT upload IDF memory optimization

This optimizes the memory in use for TFT upload when using `esp-idf` framework.

Basically, the engine establishes 3 connections to the the http/https server:
1. Fetch the file size (used to manage chunks and file size)
2. Transfer the 1st chunk (when it evaluates Nextion response to define either to continue from that point or to another point in the file)
3. Transfer the remaining data.

Until now, connection 1 was kept open during the whole process taking aprox 40kb of heap in a esp32dev (NSPanel in my tests) and the same amount of memory was needed to the 2nd and 3rd connections (which never competes to each other).
With this change, each connection is closed and released before opening the next one with a significant reduction on the required heap needed for this transfer.

This can still be improved to use a persistent connection, but I will look at this in the future, so it is not part of this change.

In addition to the better connection management, I've added quite a lot of log (mostly at VERBOSE level), which was used for troubleshooting here.
I was unsure about removing this. As it can be useful for others, I decided to keep it, but I will be fine about removing it if this is now in line with ESPHome best practices.

* clang-format

* Log response length
This commit is contained in:
Edward Firmo 2024-01-23 08:49:28 +01:00 committed by GitHub
parent c35a21773e
commit 4812997429
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -24,7 +24,7 @@ int Nextion::upload_range(const std::string &url, int range_start) {
ESP_LOGVV(TAG, "url: %s", url.c_str()); ESP_LOGVV(TAG, "url: %s", url.c_str());
uint range_size = this->tft_size_ - range_start; uint range_size = this->tft_size_ - range_start;
ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_); ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_);
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_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_; int range_end = (range_start == 0) ? std::min(this->tft_size_, 16383) : this->tft_size_;
if (range_size <= 0 or range_end <= range_start) { if (range_size <= 0 or range_end <= range_start) {
ESP_LOGE(TAG, "Invalid range"); ESP_LOGE(TAG, "Invalid range");
@ -67,12 +67,13 @@ int Nextion::upload_range(const std::string &url, int range_start) {
int total_read_len = 0, read_len; int total_read_len = 0, read_len;
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG, "Allocate buffer"); ESP_LOGV(TAG, "Allocate buffer");
uint8_t *buffer = new uint8_t[4096]; uint8_t *buffer = new uint8_t[4096];
std::string recv_string; std::string recv_string;
if (buffer == nullptr) { if (buffer == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for buffer"); ESP_LOGE(TAG, "Failed to allocate memory for buffer");
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
} else { } else {
ESP_LOGV(TAG, "Memory for buffer allocated successfully"); ESP_LOGV(TAG, "Memory for buffer allocated successfully");
@ -86,15 +87,14 @@ int Nextion::upload_range(const std::string &url, int range_start) {
ESP_LOGVV(TAG, "Write to UART successful"); ESP_LOGVV(TAG, "Write to UART successful");
this->recv_ret_string_(recv_string, 5000, true); this->recv_ret_string_(recv_string, 5000, true);
this->content_length_ -= read_len; this->content_length_ -= read_len;
ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes", 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_); 100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_,
if (recv_string[0] != 0x05) { // 0x05 == "ok" esp_get_free_heap_size());
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
ESP_LOGD( ESP_LOGD(
TAG, "recv_string [%s]", 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);
@ -103,13 +103,37 @@ int Nextion::upload_range(const std::string &url, int range_start) {
ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result); ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result);
this->content_length_ = this->tft_size_ - result; this->content_length_ = this->tft_size_ - result;
// Deallocate the buffer when done // Deallocate the buffer when done
ESP_LOGV(TAG, "Deallocate buffer");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
delete[] buffer; delete[] buffer;
ESP_LOGVV(TAG, "Memory for buffer deallocated"); ESP_LOGVV(TAG, "Memory for buffer deallocated");
esp_http_client_cleanup(client); 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_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; return result;
} }
} else if (recv_string[0] != 0x05) { // 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());
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(); recv_string.clear();
} else if (read_len == 0) { } else if (read_len == 0) {
ESP_LOGV(TAG, "End of HTTP response reached"); ESP_LOGV(TAG, "End of HTTP response reached");
@ -121,11 +145,18 @@ int Nextion::upload_range(const std::string &url, int range_start) {
} }
// Deallocate the buffer when done // Deallocate the buffer when done
ESP_LOGV(TAG, "Deallocate buffer");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
delete[] buffer; delete[] buffer;
ESP_LOGVV(TAG, "Memory for buffer deallocated"); ESP_LOGVV(TAG, "Memory for buffer deallocated");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
} }
esp_http_client_cleanup(client); 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_close(client);
esp_http_client_cleanup(client);
ESP_LOGVV(TAG, "Client closed");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
return range_end + 1; return range_end + 1;
} }
@ -159,7 +190,7 @@ bool Nextion::upload_tft() {
// Initialize the HTTP client with the configuration // Initialize the HTTP client with the configuration
ESP_LOGV(TAG, "Initializing HTTP client"); ESP_LOGV(TAG, "Initializing HTTP client");
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
esp_http_client_handle_t http = esp_http_client_init(&config); esp_http_client_handle_t http = esp_http_client_init(&config);
if (!http) { if (!http) {
ESP_LOGE(TAG, "Failed to initialize HTTP client."); ESP_LOGE(TAG, "Failed to initialize HTTP client.");
@ -168,7 +199,7 @@ bool Nextion::upload_tft() {
// Perform the HTTP request // Perform the HTTP request
ESP_LOGV(TAG, "Check if the client could connect"); ESP_LOGV(TAG, "Check if the client could connect");
ESP_LOGV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size()); ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
esp_err_t err = esp_http_client_perform(http); esp_err_t err = esp_http_client_perform(http);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
@ -177,14 +208,22 @@ bool Nextion::upload_tft() {
} }
// Check the HTTP Status Code // 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); int status_code = esp_http_client_get_status_code(http);
ESP_LOGV(TAG, "HTTP Status Code: %d", status_code); ESP_LOGV(TAG, "HTTP Status Code: %d", status_code);
size_t tft_file_size = esp_http_client_get_content_length(http); 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, "TFT file size: %zu", tft_file_size);
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) { if (tft_file_size < 4096) {
ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size); ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size);
esp_http_client_cleanup(http);
return this->upload_end(false); return this->upload_end(false);
} else { } else {
ESP_LOGV(TAG, "File size check passed. Proceeding..."); ESP_LOGV(TAG, "File size check passed. Proceeding...");
@ -193,8 +232,10 @@ bool Nextion::upload_tft() {
this->tft_size_ = tft_file_size; this->tft_size_ = tft_file_size;
ESP_LOGD(TAG, "Updating Nextion"); ESP_LOGD(TAG, "Updating Nextion");
// The Nextion will ignore the update command if it is sleeping
// 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());
this->send_command_("sleep=0"); this->send_command_("sleep=0");
this->set_backlight_brightness(1.0); this->set_backlight_brightness(1.0);
vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT
@ -207,26 +248,31 @@ bool Nextion::upload_tft() {
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_, this->parent_->get_baud_rate());
// Clear serial receive buffer // Clear serial receive buffer
ESP_LOGV(TAG, "Clear serial receive buffer");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
uint8_t d; uint8_t d;
while (this->available()) { while (this->available()) {
this->read_byte(&d); this->read_byte(&d);
}; };
ESP_LOGV(TAG, "Send update instruction: %s", command);
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
this->send_command_(command); this->send_command_(command);
std::string response; std::string response;
ESP_LOGV(TAG, "Waiting for upgrade response"); ESP_LOGV(TAG, "Waiting for upgrade response");
this->recv_ret_string_(response, 2048, 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]", ESP_LOGD(TAG, "Upgrade response is [%s] - %zu bytes",
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());
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
if (response.find(0x05) != std::string::npos) { if (response.find(0x05) != std::string::npos) {
ESP_LOGV(TAG, "Preparation for tft update done"); ESP_LOGV(TAG, "Preparation for tft update done");
} else { } else {
ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str()); ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str());
esp_http_client_cleanup(http);
return this->upload_end(false); return this->upload_end(false);
} }
@ -234,12 +280,12 @@ bool Nextion::upload_tft() {
content_length_, esp_get_free_heap_size()); content_length_, esp_get_free_heap_size());
ESP_LOGV(TAG, "Starting transfer by chunks loop"); ESP_LOGV(TAG, "Starting transfer by chunks loop");
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
int result = 0; int result = 0;
while (content_length_ > 0) { while (content_length_ > 0) {
result = upload_range(this->tft_url_.c_str(), result); result = upload_range(this->tft_url_.c_str(), result);
if (result < 0) { if (result < 0) {
ESP_LOGE(TAG, "Error updating Nextion!"); ESP_LOGE(TAG, "Error updating Nextion!");
esp_http_client_cleanup(http);
return this->upload_end(false); return this->upload_end(false);
} }
App.feed_wdt(); App.feed_wdt();
@ -248,9 +294,6 @@ bool Nextion::upload_tft() {
ESP_LOGD(TAG, "Successfully updated Nextion!"); ESP_LOGD(TAG, "Successfully updated Nextion!");
ESP_LOGD(TAG, "Close HTTP connection");
esp_http_client_close(http);
esp_http_client_cleanup(http);
return upload_end(true); return upload_end(true);
} }