esp-idf upload tft

Still a bit buggy, but this is probably the first working version of an esp-idf upload TFT to be published.
I will work more on this, but wanna have this commit as a reference.
This commit is contained in:
Edward Firmo
2023-10-31 22:48:36 +01:00
parent bfc18fd307
commit 09275f1414
2 changed files with 130 additions and 146 deletions

View File

@@ -1,3 +1,22 @@
#####################################################################################################
##### NSPANEL ESPHOME created by Blackymas - https://github.com/Blackymas/NSPanel_HA_Blueprint #####
##### TFT Upload engine #####
##### PLEASE only make changes if it is necessary and also the required knowledge is available. #####
##### For normal use with the Blueprint, no changes are necessary. #####
#####################################################################################################
substitutions:
################## Defaults ##################
# Just in case user forgets to set something #
nextion_update_url: "https://github.com/Blackymas/NSPanel_HA_Blueprint/raw/main/nspanel_eu.tft"
# nextion_update_blank_url: "https://github.com/Blackymas/NSPanel_HA_Blueprint/raw/main/custom_configuration/nspanel_blank.tft"
##############################################
##### DON'T CHANGE THIS #####
upload_tft_chunk_size_max: "32768"
#############################
external_components: external_components:
- source: github://pr#3256 # adds esp-idf support to http_request - source: github://pr#3256 # adds esp-idf support to http_request
components: components:
@@ -64,8 +83,8 @@ script:
bool is_updating_ = false; bool is_updating_ = false;
size_t transfer_buffer_size_ = 0;
uint8_t *transfer_buffer_{nullptr}; uint8_t *transfer_buffer_{nullptr};
size_t transfer_buffer_size_;
bool upload_first_chunk_sent_ = false; bool upload_first_chunk_sent_ = false;
int content_length_ = 0; int content_length_ = 0;
@@ -421,9 +440,14 @@ script:
return upload_end_(true); return upload_end_(true);
}; };
#elif defined(ESP_PLATFORM) // esp-idf # To do: Move to Nextion component on ESPHome #elif defined(ESP_PLATFORM) // esp-idf # To do: Move to Nextion component on ESPHome
auto upload_by_chunks_esp_idf = [&](const std::string &url, int range_start) -> int auto upload_by_chunks_esp_idf = [&](const std::string &url, int range_start) -> int // Is this function needed or should it be merged into the parent?
{ {
static const char *const TAG = "script.upload_tft.upload_by_chunks_esp_idf"; static const char *const TAG = "script.upload_tft.upload_by_chunks_esp_idf";
ESP_LOGD(TAG, "url: %s", url.c_str());
ESP_LOGD(TAG, "range_start: %i", range_start);
ESP_LOGD(TAG, "transfer_buffer_size_: %i", transfer_buffer_size_);
ESP_LOGD(TAG, "tft_size_: %i", tft_size_);
int range_end; int range_end;
if (range_start == 0 && transfer_buffer_size_ > 16384) { if (range_start == 0 && transfer_buffer_size_ > 16384) {
@@ -434,92 +458,65 @@ script:
if (range_end > tft_size_) if (range_end > tft_size_)
range_end = tft_size_; range_end = tft_size_;
ESP_LOGD(TAG, "range_end: %i", range_end);
char range_header[64]; int range = range_end - range_start;
sprintf(range_header, "bytes=%d-%d", range_start, range_end); ESP_LOGD(TAG, "range size: %i", range);
ESP_LOGD(TAG, "Requesting range: %s", range_header);
esp_http_client_config_t config = { esp_http_client_config_t config = {
.url = url.c_str(), .url = url.c_str(),
}; };
esp_http_client_handle_t client = esp_http_client_init(&config); esp_http_client_handle_t client = esp_http_client_init(&config);
int tries = 1; esp_err_t err;
int status; if ((err = esp_http_client_open(client, 0)) != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
while (tries <= 10) { free(transfer_buffer_);
esp_http_client_set_header(client, "Range", range_header);
status = esp_http_client_perform(client);
ESP_LOGD(TAG, "Status: %s", esp_err_to_name(status));
if (status == ESP_OK && (esp_http_client_get_status_code(client) == 200 || esp_http_client_get_status_code(client) == 206)) {
break;
}
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %d, retries(%d/10)", url.c_str(), status, tries);
tries++;
vTaskDelay(pdMS_TO_TICKS(1000));
}
if (tries > 10) {
esp_http_client_cleanup(client); esp_http_client_cleanup(client);
return -1; return -1;
} }
//char range_header[64];
//sprintf(range_header, "bytes=%d-%d", range_start, range_end);
//ESP_LOGD(TAG, "Requesting range: %s", range_header);
//esp_http_client_set_header(client, "Range", range_header);
int content_length = esp_http_client_fetch_headers(client);
ESP_LOGD(TAG, "content_length = %d", content_length);
int total_read_len = 0, read_len;
ESP_LOGD(TAG, "Allocate buffer");
uint8_t* buffer = new uint8_t[4096];
std::string recv_string; std::string recv_string;
size_t size; if (buffer == nullptr) {
int fetched = 0; ESP_LOGE(TAG, "Failed to allocate memory for buffer");
int range = range_end - range_start; ESP_LOGD(TAG, "Available heap: %u", esp_get_free_heap_size());
int write_len; } else {
while (fetched < range) { ESP_LOGI(TAG, "Memory for buffer allocated successfully");
int size = esp_http_client_get_content_length(client); ESP_LOGD(TAG, "Available heap: %u", esp_get_free_heap_size());
if (!size) {
vTaskDelay(pdMS_TO_TICKS(2)); while (true) {
continue; ESP_LOGD(TAG, "Available heap: %u", esp_get_free_heap_size());
int read_len = esp_http_client_read(client, reinterpret_cast<char*>(buffer), 4096);
if (read_len > 0) {
ESP_LOGI(TAG, "Read %d bytes from HTTP client, writing to UART", read_len);
tf_uart->write_array(buffer, read_len);
ESP_LOGI(TAG, "Write to UART successful");
recv_ret_string_(recv_string, 5000, true);
if (recv_string[0] != 0x05) { // 0x05 == "ok"
ESP_LOGD(TAG, "recv_string [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
}
recv_string.clear();
} else if (read_len == 0) {
ESP_LOGI(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
} }
int c = esp_http_client_read(client, reinterpret_cast<char*>(&transfer_buffer_[fetched]), size); }
fetched += c;
// Deallocate the buffer when done
delete[] buffer;
ESP_LOGI(TAG, "Memory for buffer deallocated");
} }
ESP_LOGD(TAG, "Fetched %d bytes", fetched);
esp_http_client_cleanup(client);
// upload fetched segments to the display in 4KB chunks
for (int i = 0; i < range; i += 4096) {
App.feed_wdt();
write_len = content_length_ < 4096 ? content_length_ : 4096;
id(tf_uart).write_array(&transfer_buffer_[i], write_len);
content_length_ -= write_len;
ESP_LOGD(TAG, "Uploaded %0.1f %%, remaining %d bytes",
100.0 * (tft_size_ - content_length_) / tft_size_,
content_length_);
if (!upload_first_chunk_sent_) {
upload_first_chunk_sent_ = true;
vTaskDelay(pdMS_TO_TICKS(500));
}
recv_ret_string_(recv_string, 5000, true);
if (recv_string[0] != 0x05) { // 0x05 == "ok"
ESP_LOGD(TAG, "recv_string [%s]",
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;
for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
}
if (result > 0) {
ESP_LOGD(TAG, "Nextion reported new range %d", result);
content_length_ = tft_size_ - result;
return result;
}
}
recv_string.clear();
}
return range_end + 1; return range_end + 1;
}; };
auto upload_tft_ = [&](const std::string &url, unsigned int update_baud_rate_) -> bool { auto upload_tft_ = [&](const std::string &url, unsigned int update_baud_rate_) -> bool {
@@ -543,77 +540,49 @@ script:
is_updating_ = true; is_updating_ = true;
// Define the configuration for the HTTP client
ESP_LOGD(TAG, "Establishing connection to HTTP server");
ESP_LOGD(TAG, "Available heap: %u", esp_get_free_heap_size());
esp_http_client_config_t config = { esp_http_client_config_t config = {
.url = url.c_str() .url = url.c_str(),
.method = HTTP_METHOD_HEAD,
.timeout_ms = 15000,
}; };
// Initialize the HTTP client with the configuration
ESP_LOGD(TAG, "Initializing HTTP client");
ESP_LOGD(TAG, "Available heap: %u", 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);
esp_http_client_set_header(http, "Range", "bytes=0-255"); if (!http) {
//esp_http_client_set_header(http, "User-Agent", "curl/7.68.0"); // Is this required? ESP_LOGE(TAG, "Failed to initialize HTTP client.");
return upload_end_(false); // return -1 to indicate an error
char *content_range = NULL;
esp_err_t err = esp_http_client_get_header(http, "Content-Length", &content_range);
if (err == ESP_OK && content_range) {
ESP_LOGD(TAG, "Received Content-Range: %s", content_range);
} else {
ESP_LOGD(TAG, "Content-Range header not found or error retrieving it.");
} }
// Perform the HTTP request
int tries = 1; ESP_LOGD(TAG, "Check if the client could connect");
int status = esp_http_client_perform(http); ESP_LOGD(TAG, "Available heap: %u", esp_get_free_heap_size());
vTaskDelay(pdMS_TO_TICKS(100)); esp_err_t err = esp_http_client_perform(http);
while ((status != ESP_OK || (esp_http_client_get_status_code(http) != 200 && esp_http_client_get_status_code(http) != 206)) && tries <= 5) { if (err != ESP_OK) {
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %d, retrying (%d/5)", url.c_str(), status, tries); ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
vTaskDelay(pdMS_TO_TICKS(250));
status = esp_http_client_perform(http);
tries++;
}
if (tries > 5) {
esp_http_client_cleanup(http); esp_http_client_cleanup(http);
return upload_end_(false); return upload_end_(false);
} }
int http_status = esp_http_client_get_status_code(http); // Check the HTTP Status Code
ESP_LOGD(TAG, "HTTP Status Code: %d", http_status); int status_code = esp_http_client_get_status_code(http);
ESP_LOGD(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);
char *content_range_cstr = nullptr; if (tft_file_size < 4096) {
char *content_length_cstr = nullptr; ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size);
esp_http_client_cleanup(http);
vTaskDelay(pdMS_TO_TICKS(500));
esp_err_t range_err = esp_http_client_get_header(http, "Content-Range", &content_range_cstr);
ESP_LOGD(TAG, "range_err: %s", esp_err_to_name(range_err));
ESP_LOGD(TAG, "ESP_OK: %d", ESP_OK);
ESP_LOGD(TAG, "Same? %i", (range_err == ESP_OK) ? 1 : 0);
ESP_LOGD(TAG, "content_range_cstr null? %i", (content_range_cstr == nullptr) ? 1 : 0);
if (range_err == ESP_OK && content_range_cstr != nullptr) {
ESP_LOGD(TAG, "Fetched Content-Range header: %s", content_range_cstr);
std::string content_range_string = content_range_cstr;
content_range_string = content_range_string.substr(content_range_string.find("/") + 1);
content_length_ = atoi(content_range_string.c_str());
ESP_LOGD(TAG, "Using Content-Range header: %s", content_range_cstr);
free(content_range_cstr);
} else {
ESP_LOGW(TAG, "Failed to fetch Content-Range header. Error: %d", range_err);
}
esp_err_t length_err = esp_http_client_get_header(http, "Content-Length", &content_length_cstr);
if (length_err == ESP_OK && content_length_cstr != nullptr) {
ESP_LOGD(TAG, "Fetched Content-Length header: %s", content_length_cstr);
content_length_ = atoi(content_length_cstr); // Convert to integer
free(content_length_cstr);
} else {
ESP_LOGW(TAG, "Failed to fetch Content-Length header. Error: %d", length_err);
}
ESP_LOGD(TAG, "Parsed content length: %d", content_length_);
if (content_length_ < 4096) {
ESP_LOGE(TAG, "File size check failed. Size: %d", content_length_);
return upload_end_(false); return upload_end_(false);
} else { } else {
ESP_LOGD(TAG, "File size check passed. Proceeding..."); ESP_LOGD(TAG, "File size check passed. Proceeding...");
} }
content_length_ = tft_file_size;
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
@@ -650,6 +619,7 @@ script:
ESP_LOGD(TAG, "Preparation for tft update done"); ESP_LOGD(TAG, "Preparation for tft update done");
} else { } else {
ESP_LOGD(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str()); ESP_LOGD(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str());
esp_http_client_cleanup(http);
return upload_end_(false); return upload_end_(false);
} }
@@ -674,6 +644,7 @@ script:
transfer_buffer_ = allocator.allocate(chunk_size); transfer_buffer_ = allocator.allocate(chunk_size);
if (!transfer_buffer_) { if (!transfer_buffer_) {
esp_http_client_cleanup(http);
return upload_end_(false); return upload_end_(false);
} }
} }
@@ -684,19 +655,34 @@ script:
ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d",
url.c_str(), content_length_, transfer_buffer_size_, esp_get_free_heap_size()); url.c_str(), content_length_, transfer_buffer_size_, esp_get_free_heap_size());
ESP_LOGD(TAG, "Starting transfer by chunks loop");
int result = 0; int result = 0;
while (content_length_ > 0) { while (content_length_ > 0) {
result = upload_by_chunks_esp_idf(url, result); result = upload_by_chunks_esp_idf(url.c_str(), result);
if (result < 0) { if (result < 0) {
ESP_LOGD(TAG, "Error updating Nextion!"); ESP_LOGD(TAG, "Error updating Nextion!");
return upload_end_(false); esp_http_client_cleanup(http);
} return upload_end_(false);
App.feed_wdt(); }
ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", esp_get_free_heap_size(), content_length_); App.feed_wdt();
ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", esp_get_free_heap_size(), content_length_);
} }
//int result = 0;
//while (content_length_ > 0) {
// result = upload_by_chunks_esp_idf(http, url, result);
// if (result < 0) {
// ESP_LOGD(TAG, "Error updating Nextion!");
// esp_http_client_cleanup(http);
// return upload_end_(false);
// }
// App.feed_wdt();
// ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", esp_get_free_heap_size(), content_length_);
//}
is_updating_ = false; is_updating_ = false;
ESP_LOGD(TAG, "Successfully updated Nextion!"); ESP_LOGD(TAG, "Successfully updated Nextion!");
esp_http_client_cleanup(http);
return upload_end_(true); return upload_end_(true);
}; };
#endif #endif

View File

@@ -9,13 +9,10 @@ substitutions:
################## Defaults ################## ################## Defaults ##################
# Just in case user forgets to set something # # Just in case user forgets to set something #
nextion_update_url: "https://github.com/Blackymas/NSPanel_HA_Blueprint/raw/main/nspanel_eu.tft"
# nextion_update_blank_url: "https://github.com/Blackymas/NSPanel_HA_Blueprint/raw/main/custom_configuration/nspanel_blank.tft"
############################################## ##############################################
##### DON'T CHANGE THIS ##### ##### DON'T CHANGE THIS #####
version: "4.1dev3" version: "4.1dev3"
upload_tft_chunk_size_max: "32768"
############################# #############################
##### ESPHOME CONFIGURATION ##### ##### ESPHOME CONFIGURATION #####
@@ -716,10 +713,11 @@ display:
- id: disp1 - id: disp1
platform: nextion platform: nextion
uart_id: tf_uart uart_id: tf_uart
#tft_url: ${nextion_update_url} #tft_url: ${nextion_update_url} # Should come back when esp-idf Nextion is available
on_page: # I couldn't make this trigger to work, so used text_sensor nspanelevent and localevent instead on_page: # I couldn't make this trigger to work, so used text_sensor nspanelevent and localevent instead
then: lambda: |-
- lambda: ESP_LOGW("display.disp1.on_page", "NEXTION PAGE CHANGED"); ESP_LOGW("display.disp1.on_page", "NEXTION PAGE CHANGED");
ESP_LOGW("display.disp1.on_page", "New page: %i", int(x));
on_setup: on_setup:
then: then:
- lambda: |- - lambda: |-