diff --git a/components/esp_rcp_http_download/CMakeLists.txt b/components/esp_br_http_ota/CMakeLists.txt similarity index 51% rename from components/esp_rcp_http_download/CMakeLists.txt rename to components/esp_br_http_ota/CMakeLists.txt index a5acd6d..667fc26 100644 --- a/components/esp_rcp_http_download/CMakeLists.txt +++ b/components/esp_br_http_ota/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register(SRC_DIRS src INCLUDE_DIRS include - REQUIRES esp_http_client esp_rcp_update) + REQUIRES app_update esp_http_client esp_rcp_update) diff --git a/components/esp_rcp_http_download/include/esp_rcp_http_download.h b/components/esp_br_http_ota/include/esp_br_http_ota.h similarity index 75% rename from components/esp_rcp_http_download/include/esp_rcp_http_download.h rename to components/esp_br_http_ota/include/esp_br_http_ota.h index 2944893..2fa3e12 100644 --- a/components/esp_rcp_http_download/include/esp_rcp_http_download.h +++ b/components/esp_br_http_ota/include/esp_br_http_ota.h @@ -13,7 +13,7 @@ extern "C" { #endif /** - * @brief This function downloads the RCP image from an HTTPS server + * @brief This function performs Border Router OTA by downloading from a HTTPS server. * * @param[in] http_config The HTTP server download config * @@ -24,7 +24,7 @@ extern "C" { * - ESP_ERR_INVALID_ARG If the http config is NULL or does not contain an url. * */ -esp_err_t esp_rcp_download_image(esp_http_client_config_t *http_config, const char *firmware_dir); +esp_err_t esp_br_http_ota(esp_http_client_config_t *http_config); #ifdef __cplusplus } /* extern "C" */ diff --git a/components/esp_br_http_ota/src/esp_br_http_ota.c b/components/esp_br_http_ota/src/esp_br_http_ota.c new file mode 100644 index 0000000..ca87e0e --- /dev/null +++ b/components/esp_br_http_ota/src/esp_br_http_ota.c @@ -0,0 +1,295 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_br_http_ota.h" +#include "esp_br_firmware.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_rcp_update.h" + +#define DEFAULT_REQUEST_SIZE 64 * 1024 +#define TAG "BR_OTA" +#define DOWNLOAD_BUFFER_SIZE 1024 + +static char s_download_data_buf[DOWNLOAD_BUFFER_SIZE]; + +static bool process_again(int status_code) +{ + switch (status_code) { + case HttpStatus_MovedPermanently: + case HttpStatus_Found: + case HttpStatus_TemporaryRedirect: + case HttpStatus_Unauthorized: + return true; + default: + return false; + } + return false; +} + +static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client, int status_code) +{ + esp_err_t err; + if (status_code == HttpStatus_MovedPermanently || status_code == HttpStatus_Found || + status_code == HttpStatus_TemporaryRedirect) { + err = esp_http_client_set_redirection(http_client); + if (err != ESP_OK) { + ESP_LOGE(TAG, "URL redirection Failed"); + return err; + } + } else if (status_code == HttpStatus_Unauthorized) { + esp_http_client_add_auth(http_client); + } else if (status_code == HttpStatus_NotFound || status_code == HttpStatus_Forbidden) { + ESP_LOGE(TAG, "File not found(%d)", status_code); + return ESP_FAIL; + } else if (status_code >= HttpStatus_BadRequest && status_code < HttpStatus_InternalError) { + ESP_LOGE(TAG, "Client error (%d)", status_code); + return ESP_FAIL; + } else if (status_code >= HttpStatus_InternalError) { + ESP_LOGE(TAG, "Server error (%d)", status_code); + return ESP_FAIL; + } + + char upgrade_data_buf[256]; + // process_again() returns true only in case of redirection. + if (process_again(status_code)) { + while (1) { + /* + * In case of redirection, esp_http_client_read() is called + * to clear the response buffer of http_client. + */ + int data_read = esp_http_client_read(http_client, upgrade_data_buf, sizeof(upgrade_data_buf)); + if (data_read <= 0) { + return ESP_OK; + } + } + } + return ESP_OK; +} + +static esp_err_t _http_connect(esp_http_client_handle_t http_client) +{ + esp_err_t err = ESP_FAIL; + int status_code, header_ret; + do { + char *post_data = NULL; + /* Send POST request if body is set. + * Note: Sending POST request is not supported if partial_http_download + * is enabled + */ + int post_len = esp_http_client_get_post_field(http_client, &post_data); + err = esp_http_client_open(http_client, post_len); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + return err; + } + if (post_len) { + int write_len = 0; + while (post_len > 0) { + write_len = esp_http_client_write(http_client, post_data, post_len); + if (write_len < 0) { + ESP_LOGE(TAG, "Write failed"); + return ESP_FAIL; + } + post_len -= write_len; + post_data += write_len; + } + } + header_ret = esp_http_client_fetch_headers(http_client); + if (header_ret < 0) { + return header_ret; + } + status_code = esp_http_client_get_status_code(http_client); + err = _http_handle_response_code(http_client, status_code); + if (err != ESP_OK) { + return err; + } + } while (process_again(status_code)); + return err; +} + +static void _http_cleanup(esp_http_client_handle_t client) +{ + esp_http_client_close(client); + esp_http_client_cleanup(client); +} + +static int http_client_read_check_connection(esp_http_client_handle_t client, char *data, size_t size) +{ + int len = esp_http_client_read(client, data, size); + + if (len == 0 && !esp_http_client_is_complete_data_received(client) && + (errno == ENOTCONN || errno == ECONNRESET || errno == ECONNABORTED)) { + return -1; + } + return len; +} + +static int http_read_for(esp_http_client_handle_t http_client, char *data, size_t size) +{ + int read_len = 0; + while (read_len < size) { + int len = http_client_read_check_connection(http_client, data, size - read_len); + if (len < 0) { + return read_len; + } + read_len += len; + } + return read_len; +} + +static esp_err_t download_br_ota_firmware(esp_http_client_handle_t http_client, uint32_t br_firmware_size) +{ + const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL); + ESP_RETURN_ON_FALSE(update_partition != NULL, ESP_ERR_NOT_FOUND, TAG, "Failed to find ota partition"); + + esp_ota_handle_t ota_handle; + int ret = ESP_OK; + ESP_GOTO_ON_ERROR(esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &ota_handle), exit, TAG, + "Failed to start ota"); + ESP_LOGI(TAG, "Downloading Border Router firmware data..."); + bool download_done = false; + uint32_t read_size = 0; + while (!download_done) { + int len = http_client_read_check_connection(http_client, s_download_data_buf, sizeof(s_download_data_buf)); + download_done = esp_http_client_is_complete_data_received(http_client); + ESP_GOTO_ON_FALSE(len >= 0, ESP_FAIL, exit, TAG, "Failed to download"); + read_size += len; + ESP_GOTO_ON_ERROR(esp_ota_write(ota_handle, s_download_data_buf, len), exit, TAG, "Failed to write ota"); + ESP_LOGI(TAG, "%u/%u bytes", read_size, br_firmware_size); + } + ESP_GOTO_ON_FALSE(read_size == br_firmware_size, ESP_FAIL, exit, TAG, "Incomplete firmware"); + +exit: + if (ret == ESP_OK) { + ret = esp_ota_end(ota_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to submit OTA"); + } + } else { + esp_ota_abort(ota_handle); + } + return ret; +} + +static int write_file_for_length(FILE *fp, const void *buf, size_t size) +{ + static const int k_max_retry = 5; + int retry_count = 0; + int offset = 0; + const uint8_t *data = (const uint8_t *)buf; + while (offset < size) { + int ret = fwrite(data + offset, 1, size - offset, fp); + if (ret < 0) { + return ret; + } + if (ret == 0) { + retry_count++; + } else { + offset += ret; + retry_count = 0; + } + if (retry_count > k_max_retry) { + return -1; + } + } + return size; +} + +static esp_err_t download_ota_image(esp_http_client_config_t *config, const char *rcp_firmware_dir, + int8_t rcp_update_seq) +{ + char rcp_target_path[RCP_FILENAME_MAX_SIZE]; + bool download_done = false; + + sprintf(rcp_target_path, "%s_%d/" ESP_BR_RCP_IMAGE_FILENAME, rcp_firmware_dir, rcp_update_seq); + + ESP_LOGI(TAG, "Downloading %s, RCP target file %s\n", config->url, rcp_target_path); + esp_http_client_handle_t http_client = esp_http_client_init(config); + ESP_RETURN_ON_FALSE(http_client != NULL, ESP_FAIL, TAG, "Failed to create HTTP client"); + esp_err_t ret = ESP_OK; + FILE *fp = fopen(rcp_target_path, "w"); + + ESP_GOTO_ON_FALSE(fp != NULL, ESP_FAIL, exit, TAG, "Failed to open target file"); + ESP_GOTO_ON_ERROR(_http_connect(http_client), exit, TAG, "Failed to connect to HTTP server"); + + // First decide the br firmware offset + uint32_t header_size = 0; + uint32_t read_size = 0; + uint32_t br_firmware_offset = 0; + uint32_t br_firmware_size = 0; + while (!download_done) { + esp_br_subfile_info_t subfile_info; + int len = http_read_for(http_client, (char *)&subfile_info, sizeof(subfile_info)); + ESP_LOGI(TAG, "subfile_info: tag 0x%x size %d offset %d\n", subfile_info.tag, subfile_info.size, + subfile_info.offset); + download_done = esp_http_client_is_complete_data_received(http_client); + ESP_GOTO_ON_FALSE(len == sizeof(subfile_info), ESP_FAIL, exit, TAG, "Incomplete header"); + + read_size += len; + if (subfile_info.tag == FILETAG_IMAGE_HEADER) { + header_size = subfile_info.size; + } else if (subfile_info.tag == FILETAG_BR_FIRMWARE) { + br_firmware_offset = subfile_info.offset; + br_firmware_size = subfile_info.size; + } + ESP_GOTO_ON_FALSE(write_file_for_length(fp, &subfile_info, len) == len, ESP_FAIL, exit, TAG, + "Failed to write data"); + if (read_size >= header_size) { + break; + } + } + + while (!download_done && read_size < br_firmware_offset) { + int target_read_size = sizeof(s_download_data_buf) < br_firmware_offset - read_size + ? sizeof(s_download_data_buf) + : br_firmware_offset - read_size; + int len = http_client_read_check_connection(http_client, s_download_data_buf, target_read_size); + download_done = esp_http_client_is_complete_data_received(http_client); + + ESP_GOTO_ON_FALSE(len >= 0, ESP_FAIL, exit, TAG, "Failed to download"); + if (len > 0) { + int r = write_file_for_length(fp, s_download_data_buf, len); + if (r != len) { + ESP_GOTO_ON_FALSE(r == len, ESP_FAIL, exit, TAG, "Failed to write OTA"); + } + } + read_size += len; + ESP_LOGI(TAG, "Downloaded %d bytes", read_size); + } + + ESP_GOTO_ON_FALSE(read_size == br_firmware_offset, ESP_FAIL, exit, TAG, "Incomplete RCP image"); + ESP_GOTO_ON_ERROR(download_br_ota_firmware(http_client, br_firmware_size), exit, TAG, + "Failed to download OTA firmware"); + ESP_GOTO_ON_ERROR(esp_rcp_submit_new_image(), exit, TAG, "Failed to submit RCP image"); + ret = esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set new OTA boot partition"); + // Try to revert the RCP image submission. The RCP image update sequence will be set back to the original number + // by calling esp_rcp_submit_new_image again. + esp_rcp_submit_new_image(); + } + +exit: + _http_cleanup(http_client); + if (fp != NULL) { + fclose(fp); + } + return ret; +} + +esp_err_t esp_br_http_ota(esp_http_client_config_t *http_config) +{ + const char *firmware_dir = esp_rcp_get_firmware_dir(); + ESP_RETURN_ON_FALSE(http_config != NULL, ESP_ERR_INVALID_ARG, TAG, "NULL http config"); + ESP_RETURN_ON_FALSE(http_config->url != NULL, ESP_ERR_INVALID_ARG, TAG, "NULL http url"); + + int8_t rcp_update_seq = esp_rcp_get_next_update_seq(); + ESP_RETURN_ON_ERROR(download_ota_image(http_config, firmware_dir, rcp_update_seq), TAG, + "Failed to download ota image"); + return ESP_OK; +} diff --git a/components/esp_ot_cli_extension/CMakeLists.txt b/components/esp_ot_cli_extension/CMakeLists.txt index 7b79a7f..fd54d3a 100644 --- a/components/esp_ot_cli_extension/CMakeLists.txt +++ b/components/esp_ot_cli_extension/CMakeLists.txt @@ -16,11 +16,11 @@ if(CONFIG_OPENTHREAD_CLI_WIFI) list(APPEND srcs "src/esp_ot_wifi_cmd.c") endif() -if(CONFIG_OPENTHREAD_CLI_RCP_UPDATE) - list(APPEND srcs "src/esp_rcp_update_commands.c") +if(CONFIG_OPENTHREAD_CLI_OTA) + list(APPEND srcs "src/esp_ot_ota_commands.c") endif() set(include "include") idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include}" - REQUIRES lwip openthread iperf esp_rcp_http_download) + REQUIRES lwip openthread iperf esp_br_http_ota esp_netif esp_wifi) diff --git a/components/esp_ot_cli_extension/Kconfig b/components/esp_ot_cli_extension/Kconfig index 225e20d..e4740d6 100644 --- a/components/esp_ot_cli_extension/Kconfig +++ b/components/esp_ot_cli_extension/Kconfig @@ -41,8 +41,8 @@ menu "OpenThread Extension CLI" depends on OPENTHREAD_CLI_ESP_EXTENSION && OPENTHREAD_BORDER_ROUTER default y - config OPENTHREAD_CLI_RCP_UPDATE - bool "Enable RCP update command" + config OPENTHREAD_CLI_OTA + bool "Enable OTA command" depends on OPENTHREAD_CLI_ESP_EXTENSION && OPENTHREAD_BORDER_ROUTER default y diff --git a/components/esp_ot_cli_extension/include/esp_rcp_update_commands.h b/components/esp_ot_cli_extension/include/esp_ot_ota_commands.h similarity index 70% rename from components/esp_ot_cli_extension/include/esp_rcp_update_commands.h rename to components/esp_ot_cli_extension/include/esp_ot_ota_commands.h index f6b9863..45dd50d 100644 --- a/components/esp_ot_cli_extension/include/esp_rcp_update_commands.h +++ b/components/esp_ot_cli_extension/include/esp_ot_ota_commands.h @@ -12,9 +12,9 @@ extern "C" { #endif -void esp_openthread_process_rcp_command(void *aContext, uint8_t aArgsLength, char *aArgs[]); +void esp_openthread_process_ota_command(void *aContext, uint8_t aArgsLength, char *aArgs[]); -void esp_set_rcp_server_cert(const char *cert); +void esp_set_ota_server_cert(const char *cert); #ifdef __cplusplus } /* extern "C" */ diff --git a/components/esp_ot_cli_extension/src/esp_ot_cli_extension.c b/components/esp_ot_cli_extension/src/esp_ot_cli_extension.c index 0fe4f77..a22e2cf 100644 --- a/components/esp_ot_cli_extension/src/esp_ot_cli_extension.c +++ b/components/esp_ot_cli_extension/src/esp_ot_cli_extension.c @@ -15,10 +15,10 @@ #include "esp_ot_cli_extension.h" #include "esp_openthread.h" #include "esp_ot_iperf.h" +#include "esp_ot_ota_commands.h" #include "esp_ot_tcp_socket.h" #include "esp_ot_udp_socket.h" #include "esp_ot_wifi_cmd.h" -#include "esp_rcp_update_commands.h" #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "freertos/task.h" @@ -40,8 +40,8 @@ static const otCliCommand kCommands[] = { #if CONFIG_OPENTHREAD_CLI_WIFI {"wifi", esp_ot_process_wifi_cmd}, #endif // CONFIG_OPENTHREAD_CLI_WIFI -#if CONFIG_OPENTHREAD_CLI_RCP_UPDATE - {"rcpupdate", esp_openthread_process_rcp_command}, +#if CONFIG_OPENTHREAD_CLI_OTA + {"ota", esp_openthread_process_ota_command}, #endif // CONFIG_OPENTHREAD_CLI_RCP_UPDATE }; diff --git a/components/esp_ot_cli_extension/src/esp_rcp_update_commands.c b/components/esp_ot_cli_extension/src/esp_ot_ota_commands.c similarity index 74% rename from components/esp_ot_cli_extension/src/esp_rcp_update_commands.c rename to components/esp_ot_cli_extension/src/esp_ot_ota_commands.c index a2110bb..9f6f21f 100644 --- a/components/esp_ot_cli_extension/src/esp_rcp_update_commands.c +++ b/components/esp_ot_cli_extension/src/esp_ot_ota_commands.c @@ -1,9 +1,9 @@ -#include "esp_rcp_update_commands.h" +#include "esp_ot_ota_commands.h" #include +#include "esp_br_http_ota.h" #include "esp_openthread_border_router.h" -#include "esp_rcp_http_download.h" #include "esp_rcp_update.h" #include "openthread/cli.h" @@ -14,7 +14,7 @@ static void print_help(void) otCliOutputFormat("rcp download ${server_url}"); } -void esp_openthread_process_rcp_command(void *aContext, uint8_t aArgsLength, char *aArgs[]) +void esp_openthread_process_ota_command(void *aContext, uint8_t aArgsLength, char *aArgs[]) { if (aArgsLength == 0) { print_help(); @@ -29,11 +29,12 @@ void esp_openthread_process_rcp_command(void *aContext, uint8_t aArgsLength, cha .event_handler = NULL, .keep_alive_enable = true, }; - if (esp_rcp_download_image(&config, esp_rcp_get_firmware_dir()) != ESP_OK) { + if (esp_br_http_ota(&config) != ESP_OK) { otCliOutputFormat("Failed to download image"); } } - } else if (strcmp(aArgs[0], "apply") == 0) { + esp_restart(); + } else if (strcmp(aArgs[0], "rcpupdate") == 0) { esp_openthread_rcp_deinit(); esp_rcp_update(); esp_restart(); @@ -42,7 +43,7 @@ void esp_openthread_process_rcp_command(void *aContext, uint8_t aArgsLength, cha } } -void esp_set_rcp_server_cert(const char *cert) +void esp_set_ota_server_cert(const char *cert) { s_server_cert = cert; } diff --git a/components/esp_rcp_http_download/src/esp_rcp_http_download.c b/components/esp_rcp_http_download/src/esp_rcp_http_download.c deleted file mode 100644 index fb701a0..0000000 --- a/components/esp_rcp_http_download/src/esp_rcp_http_download.c +++ /dev/null @@ -1,171 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "esp_rcp_http_download.h" -#include "esp_check.h" -#include "esp_log.h" -#include "esp_rcp_update.h" - -#define DEFAULT_REQUEST_SIZE 64 * 1024 -#define TAG "RCP_DOWNLOAD" -#define DOWNLOAD_BUFFER_SIZE 1024 - -static bool process_again(int status_code) -{ - switch (status_code) { - case HttpStatus_MovedPermanently: - case HttpStatus_Found: - case HttpStatus_TemporaryRedirect: - case HttpStatus_Unauthorized: - return true; - default: - return false; - } - return false; -} - -static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client, int status_code) -{ - esp_err_t err; - if (status_code == HttpStatus_MovedPermanently || status_code == HttpStatus_Found || - status_code == HttpStatus_TemporaryRedirect) { - err = esp_http_client_set_redirection(http_client); - if (err != ESP_OK) { - ESP_LOGE(TAG, "URL redirection Failed"); - return err; - } - } else if (status_code == HttpStatus_Unauthorized) { - esp_http_client_add_auth(http_client); - } else if (status_code == HttpStatus_NotFound || status_code == HttpStatus_Forbidden) { - ESP_LOGE(TAG, "File not found(%d)", status_code); - return ESP_FAIL; - } else if (status_code >= HttpStatus_BadRequest && status_code < HttpStatus_InternalError) { - ESP_LOGE(TAG, "Client error (%d)", status_code); - return ESP_FAIL; - } else if (status_code >= HttpStatus_InternalError) { - ESP_LOGE(TAG, "Server error (%d)", status_code); - return ESP_FAIL; - } - - char upgrade_data_buf[256]; - // process_again() returns true only in case of redirection. - if (process_again(status_code)) { - while (1) { - /* - * In case of redirection, esp_http_client_read() is called - * to clear the response buffer of http_client. - */ - int data_read = esp_http_client_read(http_client, upgrade_data_buf, sizeof(upgrade_data_buf)); - if (data_read <= 0) { - return ESP_OK; - } - } - } - return ESP_OK; -} - -static esp_err_t _http_connect(esp_http_client_handle_t http_client) -{ - esp_err_t err = ESP_FAIL; - int status_code, header_ret; - do { - char *post_data = NULL; - /* Send POST request if body is set. - * Note: Sending POST request is not supported if partial_http_download - * is enabled - */ - int post_len = esp_http_client_get_post_field(http_client, &post_data); - err = esp_http_client_open(http_client, post_len); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); - return err; - } - if (post_len) { - int write_len = 0; - while (post_len > 0) { - write_len = esp_http_client_write(http_client, post_data, post_len); - if (write_len < 0) { - ESP_LOGE(TAG, "Write failed"); - return ESP_FAIL; - } - post_len -= write_len; - post_data += write_len; - } - } - header_ret = esp_http_client_fetch_headers(http_client); - if (header_ret < 0) { - return header_ret; - } - status_code = esp_http_client_get_status_code(http_client); - err = _http_handle_response_code(http_client, status_code); - if (err != ESP_OK) { - return err; - } - } while (process_again(status_code)); - return err; -} - -static void _http_cleanup(esp_http_client_handle_t client) -{ - esp_http_client_close(client); - esp_http_client_cleanup(client); -} - -static esp_err_t download_to_spiffs(esp_http_client_config_t *config, const char *firmware_dir, int8_t firmware_seq, - const char *target_file) -{ - char target_path[RCP_FILENAME_MAX_SIZE]; - char target_url[RCP_URL_MAX_SIZE]; - bool download_done = false; - int downloaded_length = 0; - - sprintf(target_path, "%s_%d%s", firmware_dir, firmware_seq, target_file); - sprintf(target_url, "%s%s", config->url, target_file); - - ESP_LOGI(TAG, "Download %s to target file %s\n", target_url, target_path); - esp_http_client_handle_t http_client = esp_http_client_init(config); - ESP_RETURN_ON_FALSE(http_client != NULL, ESP_FAIL, TAG, "Failed to create HTTP client"); - esp_http_client_set_url(http_client, target_url); - esp_err_t ret = ESP_OK; - FILE *fp = fopen(target_path, "w"); - - ESP_GOTO_ON_FALSE(fp != NULL, ESP_FAIL, exit, TAG, "Failed to open target file"); - ESP_GOTO_ON_ERROR(_http_connect(http_client), exit, TAG, "Failed to connect to HTTP server"); - - while (!download_done) { - char data_buf[DOWNLOAD_BUFFER_SIZE]; - int len = esp_http_client_read(http_client, data_buf, sizeof(data_buf)); - download_done = esp_http_client_is_complete_data_received(http_client); - - ESP_GOTO_ON_FALSE(len >= 0, ESP_FAIL, exit, TAG, "Failed to download"); - if (len == 0 && !download_done && (errno == ENOTCONN || errno == ECONNRESET || errno == ECONNABORTED)) { - ESP_LOGE(TAG, "Connection closed, errno = %d", errno); - goto exit; - } else if (len > 0) { - ESP_GOTO_ON_FALSE(fwrite(data_buf, 1, len, fp) == len, ESP_FAIL, exit, TAG, "Failed to write data"); - } - downloaded_length += len; - ESP_LOGI(TAG, "Downloaded %d bytes", downloaded_length); - } - -exit: - _http_cleanup(http_client); - if (fp != NULL) { - fclose(fp); - } - return ret; -} - -esp_err_t esp_rcp_download_image(esp_http_client_config_t *http_config, const char *firmware_dir) -{ - ESP_RETURN_ON_FALSE(http_config != NULL, ESP_ERR_INVALID_ARG, TAG, "NULL http config"); - ESP_RETURN_ON_FALSE(http_config->url != NULL, ESP_ERR_INVALID_ARG, TAG, "NULL http url"); - - int8_t update_seq = esp_rcp_get_next_update_seq(); - ESP_RETURN_ON_ERROR(download_to_spiffs(http_config, firmware_dir, update_seq, "/rcp_image"), TAG, - "Failed to download rcp_image"); - return esp_rcp_submit_new_image(); -} diff --git a/components/esp_rcp_update/include/esp_br_firmware.h b/components/esp_rcp_update/include/esp_br_firmware.h new file mode 100644 index 0000000..e30e8ce --- /dev/null +++ b/components/esp_rcp_update/include/esp_br_firmware.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + FILETAG_RCP_VERSION = 0, + FILETAG_RCP_FLASH_ARGS = 1, + FILETAG_RCP_BOOTLOADER = 2, + FILETAG_RCP_PARTITION_TABLE = 3, + FILETAG_RCP_FIRMWARE = 4, + FILETAG_BR_FIRMWARE = 5, + FILETAG_IMAGE_HEADER = 0xff, +} esp_br_filetag_t; + +struct esp_br_subfile_info { + uint32_t tag; + uint32_t size; + uint32_t offset; +} __attribute__((packed)); + +typedef struct esp_br_subfile_info esp_br_subfile_info_t; + +#define ESP_BR_RCP_IMAGE_FILENAME "rcp_image" + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_rcp_update/src/esp_rcp_update.c b/components/esp_rcp_update/src/esp_rcp_update.c index b110181..c624ec9 100644 --- a/components/esp_rcp_update/src/esp_rcp_update.c +++ b/components/esp_rcp_update/src/esp_rcp_update.c @@ -11,6 +11,7 @@ #include #include "esp32_port.h" +#include "esp_br_firmware.h" #include "esp_check.h" #include "esp_err.h" #include "esp_loader.h" @@ -32,27 +33,11 @@ typedef struct esp_rcp_update_handle { esp_rcp_update_config_t update_config; } esp_rcp_update_handle; -typedef enum { - RCP_FILETAG_VERSION = 0, - RCP_FILETAG_FLASH_ARGS = 1, - RCP_FILETAG_BOOTLOADER = 2, - RCP_FILETAG_PARTITION_TABLE = 3, - RCP_FILETAG_FIRMWARE = 4, - RCP_FILETAG_IMAGE_HEADER = 0xff, -} rcp_filetag_t; - -struct rcp_subfile_info_t { - uint32_t tag; - uint32_t size; - uint32_t offset; -} __attribute__((packed)); - struct rcp_flash_arg_t { uint32_t tag; uint32_t offset; } __attribute__((packed)); -typedef struct rcp_subfile_info_t rcp_subfile_info_t; typedef struct rcp_flash_arg_t rcp_flash_arg_t; static esp_rcp_update_handle s_handle; @@ -76,20 +61,20 @@ static esp_loader_error_t connect_to_target(target_chip_t target_chip, uint32_t return ESP_LOADER_SUCCESS; } -static esp_err_t seek_to_subfile(FILE *fp, rcp_filetag_t tag, rcp_subfile_info_t *found_info) +static esp_err_t seek_to_subfile(FILE *fp, esp_br_filetag_t tag, esp_br_subfile_info_t *found_info) { if (fseek(fp, 0, SEEK_SET) != 0) { return ESP_FAIL; } - rcp_subfile_info_t subfile_info; + esp_br_subfile_info_t subfile_info; if (fread(&subfile_info, 1, sizeof(subfile_info), fp) != sizeof(subfile_info)) { return ESP_FAIL; } - if (subfile_info.tag != RCP_FILETAG_IMAGE_HEADER || subfile_info.size % sizeof(rcp_subfile_info_t) != 0) { + if (subfile_info.tag != FILETAG_IMAGE_HEADER || subfile_info.size % sizeof(subfile_info) != 0) { return ESP_FAIL; } - int num_subfiles = subfile_info.size / sizeof(rcp_subfile_info_t); + int num_subfiles = subfile_info.size / sizeof(subfile_info); for (int i = 1; i < num_subfiles; i++) { if (fread(&subfile_info, 1, sizeof(subfile_info), fp) != sizeof(subfile_info)) { return ESP_FAIL; @@ -107,13 +92,13 @@ esp_err_t esp_rcp_load_version_in_storage(char *version_str, size_t size) char fullpath[RCP_FILENAME_MAX_SIZE]; int8_t update_seq = s_handle.update_seq; - sprintf(fullpath, "%s_%d/rcp_image", s_handle.update_config.firmware_dir, update_seq); + sprintf(fullpath, "%s_%d/" ESP_BR_RCP_IMAGE_FILENAME, s_handle.update_config.firmware_dir, update_seq); FILE *fp = fopen(fullpath, "r"); if (fp == NULL) { return ESP_ERR_NOT_FOUND; } - rcp_subfile_info_t version_info; - ESP_RETURN_ON_ERROR(seek_to_subfile(fp, RCP_FILETAG_VERSION, &version_info), TAG, "Failed to find version subfile"); + esp_br_subfile_info_t version_info; + ESP_RETURN_ON_ERROR(seek_to_subfile(fp, FILETAG_RCP_VERSION, &version_info), TAG, "Failed to find version subfile"); memset(version_str, 0, size); int read_size = size < version_info.size ? size : version_info.size; fread(version_str, 1, read_size, fp); @@ -248,11 +233,11 @@ esp_err_t esp_rcp_update(void) char fullpath[RCP_FILENAME_MAX_SIZE]; int update_seq = esp_rcp_get_update_seq(); - sprintf(fullpath, "%s_%d/rcp_image", s_handle.update_config.firmware_dir, update_seq); + sprintf(fullpath, "%s_%d/" ESP_BR_RCP_IMAGE_FILENAME, s_handle.update_config.firmware_dir, update_seq); FILE *fp = fopen(fullpath, "r"); ESP_RETURN_ON_FALSE(fp != NULL, ESP_ERR_NOT_FOUND, TAG, "Cannot find rcp image"); - rcp_subfile_info_t subfile; - seek_to_subfile(fp, RCP_FILETAG_FLASH_ARGS, &subfile); + esp_br_subfile_info_t subfile; + seek_to_subfile(fp, FILETAG_RCP_FLASH_ARGS, &subfile); int num_flash_binaries = subfile.size / sizeof(rcp_flash_arg_t); for (int i = 0; i < num_flash_binaries; i++) { diff --git a/examples/basic_thread_border_router/README.md b/examples/basic_thread_border_router/README.md index 763e354..2cc636d 100644 --- a/examples/basic_thread_border_router/README.md +++ b/examples/basic_thread_border_router/README.md @@ -180,7 +180,7 @@ Done This service will also become visible on the Wi-Fi network: ```bash -$ avahi-browse -r _test._udp -t +$ avahi-browse -r _test._udp -t + enp1s0 IPv6 my-service _test._udp local = enp1s0 IPv6 my-service _test._udp local @@ -196,20 +196,30 @@ $ avahi-browse -r _test._udp -t txt = [] ``` -## Updating the RCP from HTTPS server +## Updating the border router from HTTPS server -To test with a local https server, you need to first create a folder with the rcp update image. +With configuration `OPENTHREAD_BR_CREATE_OTA_IMAGE` on, the border router OTA image will be automatically created at `build/br_ota_image`. The file is a bundle of the border router firmware and the RCP firmware. +To test with a local https server, you need to first create a folder with the image. -The rcp image can be found at `build/esp-idf/main/spiffs_image/ot_rcp_0/rcp_image`. +Enter this folder and create a new self-signed certificate and key with the following command: -Enter this folder and create a new self-signed certificate and key, with the command `openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes`. +```bash +$ openssl req -x509 -newkey rsa:2048 -keyout ca_key.pem -out ca_cert.pem -days 365 -nodes +``` + +Note that when prompted for the `Common Name(CN)`, the name entered shall match the hostname running the server. -Start the HTTPS server with command `openssl s_server -WWW -key ca_key.pem -cert ca_cert.pem -port 8070`. +Now launch the HTTPS server +```bash +openssl s_server -WWW -key ca_key.pem -cert ca_cert.pem -port 8070 +``` Replace `server_certs/ca_cert.pem` with the one generated by the command. Then build and flash the device. To download the image from the server, run the following command on the border router: ``` -> rcpupdate download https://${HOST_URL}:8070 +> ota download https://${HOST_URL}:8070/br_ota_image ``` + +After downloading the device will restart and update itself with the new firmware. The RCP will also be updated if the firmware version changes. diff --git a/examples/basic_thread_border_router/create_example_spiffs.py b/examples/basic_thread_border_router/create_ota_image.py similarity index 51% rename from examples/basic_thread_border_router/create_example_spiffs.py rename to examples/basic_thread_border_router/create_ota_image.py index 5375c4c..cc6d111 100755 --- a/examples/basic_thread_border_router/create_example_spiffs.py +++ b/examples/basic_thread_border_router/create_ota_image.py @@ -7,14 +7,16 @@ import shutil import struct -RCP_FILETAG_VERSION = 0 -RCP_FILETAG_FLASH_ARGS = 1 -RCP_FILETAG_BOOTLOADER = 2 -RCP_FILETAG_PARTITION_TABLE = 3 -RCP_FILETAG_FIRMWARE = 4 -RCP_FILETAG_IMAGE_HEADER = 0xff +FILETAG_RCP_VERSION = 0 +FILETAG_RCP_FLASH_ARGS = 1 +FILETAG_RCP_BOOTLOADER = 2 +FILETAG_RCP_PARTITION_TABLE = 3 +FILETAG_RCP_FIRMWARE = 4 +FILETAG_BR_OTA_IMAGE = 5 +FILETAG_IMAGE_HEADER = 0xff -RCP_IMAGE_HEADER_SIZE = 3 * 4 * 6 +HEADER_ENTRY_SIZE = 3 * 4 +RCP_IMAGE_HEADER_SIZE = HEADER_ENTRY_SIZE * 6 RCP_FLASH_ARGS_SIZE = 2 * 4 * 3 @@ -40,45 +42,52 @@ def append_flash_args(fout, flash_args_path): for offset, partition_file in partition_info_list: offset = int(offset, 0) if partition_file.find('bootloader') >= 0: - fout.write(struct.pack('= 0: - fout.write(struct.pack('