##################################################################################################### ##### 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. ##### ##################################################################################################### ##### ATTENTION: This will add advanced elements to the core system and requires the core part. ##### ##################################################################################################### --- substitutions: ################## Defaults ################## # Just in case user forgets to set something # nextion_update_url: "https://raw.githubusercontent.com/Blackymas/NSPanel_HA_Blueprint/main/nspanel_eu.tft" nextion_update_base_url: "https://raw.githubusercontent.com/Blackymas/NSPanel_HA_Blueprint/" ############################################## ##### DON'T CHANGE THIS ##### upload_tft_chunk_size_max: "32768" upload_tft_baud_rate: "921600" ############################# ##### External components ##### external_components: - source: # Update this before the release type: git url: https://github.com/edwardtfn/esphome ref: nextion-22 components: - nextion refresh: 300s api: on_client_connected: - script.execute: report_settings services: ##### SERVICE TO UPDATE THE TFT FILE from URL ##### ##### It will use the default url if url is empty or "default" - service: upload_tft_url variables: url: string then: - lambda: |- static const char *const TAG = "addon_upload_tft.service.upload_tft_url"; ESP_LOGVV(TAG, "Starting..."); std::string clean_url = url; // Convert to lowercase std::transform(clean_url.begin(), clean_url.end(), clean_url.begin(), [](unsigned char c){ return std::tolower(c); }); // Trim trailing spaces auto endPos = clean_url.find_last_not_of(" \t"); if (std::string::npos != endPos) { clean_url = clean_url.substr(0, endPos + 1); } if (clean_url.empty() or clean_url == "default") url = id(tft_url); upload_tft->execute(url.c_str()); button: ##### UPDATE TFT DISPLAY ##### - id: tft_update name: Update TFT display platform: template icon: mdi:file-sync entity_category: config on_press: - lambda: |- static const char *const TAG = "addon_upload_tft.button.tft_update.on_press"; ESP_LOGD(TAG, "Update TFT display button pressed"); upload_tft->execute(id(tft_url).c_str()); display: - id: !extend disp1 tft_url: ${nextion_update_url} exit_reparse_on_start: true esphome: on_boot: - priority: 601.0 then: - lambda: |- // Hide TFT file selectors when using arduino #ifdef ARDUINO tft_file_model->set_internal(true); #elif defined(ESP_PLATFORM) tft_file_model->set_internal(false); #endif globals: - id: baud_rate_original type: uint restore_value: false initial_value: '115200' - id: baud_rate_target type: uint restore_value: false initial_value: ${upload_tft_baud_rate} - id: tft_is_valid type: bool restore_value: false initial_value: 'false' - id: tft_upload_attempt type: uint restore_value: false initial_value: '0' - id: tft_url type: std::string restore_value: false initial_value: '"${nextion_update_url}"' - id: tft_upload_result type: esphome::nextion::Nextion::TFTUploadResult restore_value: false initial_value: 'esphome::nextion::Nextion::TFTUploadResult::Unknown' script: - id: nextion_uart_command mode: queued parameters: command: string then: - lambda: |- static const char *const TAG = "addon_upload_tft.script.nextion_uart_command"; if (disp1->is_setup()) { ESP_LOGD(TAG, "Sending `%s` directly to Nextion", command.c_str()); disp1->send_command_printf(command.c_str()); } else { ESP_LOGD(TAG, "Sending `%s` directly to UART", command.c_str()); tf_uart->write_str(command.c_str()); const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF}; tf_uart->write_array(to_send, sizeof(to_send)); } App.feed_wdt(); - id: nextion_upload mode: single then: - lambda: |- static const char *const TAG = "addon_upload_tft.script.nextion_upload"; ESP_LOGD(TAG, "Waiting for empty UART and Nextion queues"); - wait_until: condition: - lambda: !lambda return (disp1->queue_size()<=0); - lambda: !lambda return (tf_uart->available()<=0); timeout: 10s - delay: 2s - lambda: |- static const char *const TAG = "addon_upload_tft.script.nextion_upload"; ESP_LOGD(TAG, "Starting TFT upload..."); id(tft_upload_result) = disp1->upload_tft(); ESP_LOGD(TAG, "TFT upload: %s", esphome::nextion::Nextion::TFTUploadResultToString(id(tft_upload_result))); - id: open_upload_dialog mode: restart then: - lambda: |- static const char *const TAG = "addon_upload_tft.script.open_upload_dialog"; ESP_LOGD(TAG, "Showing upload dialog page"); disp1->goto_page("confirm"); disp1->hide_component("bt_close"); disp1->hide_component("bt_accept"); disp1->hide_component("bt_clear"); disp1->hide_component("bt_close"); #ifdef ARDUINO disp1->set_component_text_printf("confirm.title", "Upload TFT\\rArduino"); #elif defined(ESP_PLATFORM) disp1->set_component_text_printf("confirm.title", "Upload TFT\\rESP-IDF"); #endif page_id->update(); - id: report_settings mode: restart then: - delay: 15s - lambda: |- static const char *const TAG = "addon_upload_tft.script.report_settings"; ESP_LOGI(TAG, "TFT URL: %s", id(tft_url).c_str()); ESP_LOGI(TAG, "Substitutions:"); ESP_LOGI(TAG, " nextion_update_url: ${nextion_update_url}"); ESP_LOGI(TAG, " nextion_update_base_url: ${nextion_update_base_url}"); ESP_LOGI(TAG, " upload_tft_chunk_size_max: ${upload_tft_chunk_size_max}"); ESP_LOGI(TAG, " upload_tft_baud_rate: ${upload_tft_baud_rate}"); - id: report_upload_progress mode: restart parameters: message: string then: - lambda: |- static const char *const TAG = "addon_upload_tft.script.report_upload_progress"; ESP_LOGD(TAG, "%s", message.c_str()); if (id(tft_is_valid)) { if (page_id->state != 26) { open_upload_dialog->execute(); } display_wrapped_text->execute("confirm.body", message.c_str(), 18); disp1->set_backlight_brightness(1); App.feed_wdt(); } - id: set_tft_file mode: restart then: - delay: 2s - lambda: |- #ifdef ARDUINO static const char *const TAG = "addon_upload_tft.script.set_tft_file.arduino"; #elif defined(ESP_PLATFORM) static const char *const TAG = "addon_upload_tft.script.set_tft_file.esp_idf"; #endif std::string branch = "v${version}"; if (branch.find("beta") != std::string::npos) branch = "beta"; else if (branch.find("dev") != std::string::npos) branch = "dev"; std::string model = tft_file_model->state; ESP_LOGD(TAG, "TFT URL set:"); ESP_LOGD(TAG, " Branch: %s", branch.c_str()); ESP_LOGD(TAG, " Model: %s", model.c_str()); if (id(is_uploading_tft)) ESP_LOGW(TAG, " TFT Upload in progress."); else { #ifdef ARDUINO std::string url = "${nextion_update_url}"; #elif defined(ESP_PLATFORM) std::string url; std::string file_name; if (model == "NSPanel Blank") file_name = "nspanel_blank.tft"; else if (model == "NSPanel EU") file_name = "nspanel_eu.tft"; else if (model == "NSPanel US") file_name = "nspanel_us.tft"; else if (model == "NSPanel US Landscape") file_name = "nspanel_us_land.tft"; else if (model == "NSPanel EU (CJK languages)") file_name = "advanced/hmi/nspanel_CJK_eu.tft"; else if (model == "NSPanel US (CJK languages)") file_name = "advanced/hmi/nspanel_CJK_us.tft"; else if (model == "NSPanel US Landscape (CJK languages)") file_name = "advanced/hmi/nspanel_CJK_us_land.tft"; if (file_name.empty()) url = "${nextion_update_url}"; else { std::string base_url("${nextion_update_base_url}"); url = base_url + branch + "/" + file_name; // Remove trailing slashes or spaces auto endPos = url.find_last_not_of(" /"); if (std::string::npos != endPos) { url = url.substr(0, endPos + 1); } } #endif ESP_LOGD(TAG, " Full URL: %s", url.c_str()); id(tft_url) = url; disp1->set_tft_url(url.c_str()); } - id: upload_tft # I've changed this to use ESPHome commands to avoid the parallelism from lambdas mode: single parameters: url: string then: # Make sure the screen is ON - if: condition: - switch.is_off: screen_power then: - switch.turn_on: screen_power - delay: 5s # Wait for recent changes to TFT url - script.wait: set_tft_file # Then start the upload - lambda: |- static const char *const TAG = "addon_upload_tft.script.upload_tft"; ESP_LOGD(TAG, "Starting..."); id(is_uploading_tft) = true; nextion_status->execute(); uint32_t supported_baud_rates[] = {2400, 4800, 9600, 19200, 31250, 38400, 57600, 115200, 230400, 250000, 256000, 512000, 921600}; auto is_baud_rate_supported = [supported_baud_rates](uint32_t baud_rate_requested) -> bool { size_t size = sizeof(supported_baud_rates) / sizeof(supported_baud_rates[0]); for (size_t i = 0; i < size; ++i) { if (supported_baud_rates[i] == baud_rate_requested) { return true; } } return false; // Return false if not found }; // The upload process starts here ESP_LOGD(TAG, "Starting the upload script"); // Detect baud rates to be used id(baud_rate_original) = tf_uart->get_baud_rate(); if (!is_baud_rate_supported(id(baud_rate_original))) id(baud_rate_original) = 115200; std::string upload_tft_baud_rate_string = "${upload_tft_baud_rate}"; id(baud_rate_target) = stoi(upload_tft_baud_rate_string); if (!is_baud_rate_supported(id(baud_rate_target))) id(baud_rate_target) = id(baud_rate_original); ESP_LOGD(TAG, " Target upload baud rate: %d bps", id(baud_rate_target)); ESP_LOGD(TAG, " Current baud rate: %" PRIu32 " bps", tf_uart->get_baud_rate()); ESP_LOGD(TAG, " Valid TFT: %s", YESNO(id(tft_is_valid))); // Upload URL ESP_LOGD(TAG, " Upload URL: %s", url.c_str()); #ifdef ARDUINO auto startsWith = [](const std::string& str, const std::string& prefix) -> bool { if (str.length() < prefix.length()) return false; std::string lowerStr = str.substr(0, prefix.length()); std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), [](unsigned char c){ return std::tolower(c); }); std::string lowerPrefix = prefix; std::transform(lowerPrefix.begin(), lowerPrefix.end(), lowerPrefix.begin(), [](unsigned char c){ return std::tolower(c); }); return lowerStr == lowerPrefix; }; if (startsWith(url, "https")) { ESP_LOGE(TAG, "HTTPS is not supported by Arduino framework."); ESP_LOGE(TAG, "This transfer will most likely fail."); ESP_LOGE(TAG, "Please use HTTP instead."); } #endif disp1->set_tft_url(url.c_str()); nextion_uart_command->execute("bkcmd=3"); - lambda: if (id(tft_is_valid)) disp1->goto_page("home"); - delay: 2s - script.execute: open_upload_dialog - script.wait: open_upload_dialog - lambda: page_id->update(); - wait_until: condition: - lambda: !lambda return (page_id->state == 26); timeout: 2s - script.execute: id: report_upload_progress message: "Set Nextion unavailable for blueprint calls" - script.wait: report_upload_progress - binary_sensor.template.publish: id: nextion_init state: false - script.execute: id: report_upload_progress message: "Stopping other scripts" - script.wait: report_upload_progress - script.execute: stop_all - script.wait: stop_all - wait_until: condition: - lambda: !lambda return (!id(tft_is_valid)); timeout: 1s ### Attempt twice at the target baud rate - script.execute: id: upload_tft_sequence_attempt baud_rate: !lambda return id(baud_rate_target); - script.wait: upload_tft_sequence_attempt ### Attempt twice at the original baud rate - if: condition: - lambda: |- return (id(baud_rate_original) != id(baud_rate_target)); then: - script.execute: id: upload_tft_sequence_attempt baud_rate: !lambda return id(baud_rate_original); - script.wait: upload_tft_sequence_attempt ### Attempt twice at the Nextion's default baud rate (115200bps) - if: condition: - lambda: |- return (id(baud_rate_original) != 115200 and id(baud_rate_target) != 115200); then: - script.execute: id: upload_tft_sequence_attempt baud_rate: 115200 - script.wait: upload_tft_sequence_attempt ### Restart Nextion and attempt twice again at default baud rate (115200bps) - script.execute: id: report_upload_progress message: "Restarting Nextion display" - script.wait: report_upload_progress - wait_until: condition: - lambda: !lambda return (!id(tft_is_valid)); timeout: 3s - switch.turn_off: screen_power - delay: 2s - switch.turn_on: screen_power - delay: 5s - script.execute: id: upload_tft_sequence_attempt baud_rate: 115200 - script.wait: upload_tft_sequence_attempt ### All tries failed ### - script.execute: id: report_upload_progress message: "TFT upload failed" - script.wait: report_upload_progress - wait_until: condition: - lambda: !lambda return (!id(tft_is_valid)); timeout: 5s - script.execute: id: report_upload_progress message: "Turn off Nextion and restart ESPHome" - script.wait: report_upload_progress - wait_until: condition: - lambda: !lambda return (!id(tft_is_valid)); timeout: 5s - switch.turn_off: screen_power - delay: 2s # Restart ESPHome - lambda: !lambda App.safe_reboot(); ### This code should never run - delay: 2s - lambda: |- static const char *const TAG = "addon_upload_tft.script.upload_tft"; ESP_LOGD(TAG, "Finishing..."); id(is_uploading_tft) = false; screen_power->publish_state(true); ESP_LOGE(TAG, "TFT upload finished unsuccessfully!"); - id: upload_tft_sequence_attempt mode: single parameters: baud_rate: uint then: - script.execute: nextion_status - script.wait: nextion_status - script.execute: id: report_upload_progress message: "Setting baud rate" - script.wait: report_upload_progress - script.execute: id: set_baud_rate baud_rate: !lambda return baud_rate; definitive: false - script.wait: set_baud_rate - delay: 2s - repeat: count: 2 then: # First attempt - script.execute: upload_tft_attempt - script.wait: upload_tft_attempt - delay: 5s - id: upload_tft_attempt mode: single then: - logger.log: "Attempting to upload TFT" - lambda: !lambda id(tft_upload_attempt)++; - lambda: |- char update_msg[128]; sprintf(update_msg, "Attempt #%d at %" PRIu32 " bps", id(tft_upload_attempt), tf_uart->get_baud_rate()); id(tft_upload_result) = esphome::nextion::Nextion::TFTUploadResult::Unknown; report_upload_progress->execute(update_msg); - script.wait: report_upload_progress - wait_until: condition: - lambda: !lambda return (!id(tft_is_valid)); timeout: 1s - script.execute: nextion_upload - script.wait: nextion_upload - lambda: |- char update_msg[128]; sprintf(update_msg, "Attempt #%d at %" PRIu32 " bps returned: %s", id(tft_upload_attempt), tf_uart->get_baud_rate(), esphome::nextion::Nextion::TFTUploadResultToString(id(tft_upload_result))); report_upload_progress->execute(update_msg); - script.wait: report_upload_progress - if: condition: not: - lambda: return id(tft_upload_result) == esphome::nextion::Nextion::TFTUploadResult::Unknown; - lambda: return id(tft_upload_result) == esphome::nextion::Nextion::TFTUploadResult::UploadInProgress; - lambda: return id(tft_upload_result) == esphome::nextion::Nextion::TFTUploadResult::NextionError_PreparationFailed; - lambda: return id(tft_upload_result) == esphome::nextion::Nextion::TFTUploadResult::NextionError_InvalidResponse; then: - delay: 5s - lambda: |- App.safe_reboot(); - id: !extend watchdog then: - lambda: |- if (!id(is_uploading_tft)) { static const char *const TAG = "addon_upload_tft.script.watchdog"; ESP_LOGI(TAG, "Add-on Upload TFT:"); ESP_LOGI(TAG, " File model: %s", tft_file_model->state.c_str()); ESP_LOGD(TAG, " Valid TFT: %s", YESNO(id(tft_is_valid))); } select: - id: tft_file_model name: Update TFT display - Model platform: template options: - "Use nextion_update_url" - "NSPanel Blank" - "NSPanel EU" - "NSPanel US" - "NSPanel US Landscape" - "NSPanel EU (CJK languages)" - "NSPanel US (CJK languages)" - "NSPanel US Landscape (CJK languages)" initial_option: "Use nextion_update_url" optimistic: true restore_value: true internal: true entity_category: config disabled_by_default: false icon: mdi:file-sync on_value: - script.execute: set_tft_file sensor: - id: !extend display_mode on_value: then: lambda: |- static const char *const TAG = "addon_upload_tft.sensor.display_mode"; id(tft_is_valid) = (display_mode->state > 0 and display_mode->state < 4); if (id(tft_is_valid)) ESP_LOGD(TAG, "Valid TFT: True"); else { ESP_LOGW(TAG, "Display mode: %i", int(display_mode->state)); ESP_LOGW(TAG, "Valid TFT: False"); } ...