diff --git a/examples/system/ota/advanced_https_ota/CMakeLists.txt b/examples/system/ota/advanced_https_ota/CMakeLists.txt new file mode 100644 index 000000000..3386014f6 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(advanced_https_ota) diff --git a/examples/system/ota/advanced_https_ota/Makefile b/examples/system/ota/advanced_https_ota/Makefile new file mode 100644 index 000000000..301cf1eb9 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := advanced_https_ota + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/system/ota/advanced_https_ota/main/CMakeLists.txt b/examples/system/ota/advanced_https_ota/main/CMakeLists.txt new file mode 100644 index 000000000..2b2b10728 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/main/CMakeLists.txt @@ -0,0 +1,8 @@ +set(COMPONENT_SRCS "advanced_https_ota_example.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + + +# Embed the server root certificate into the final binary +set(COMPONENT_EMBED_TXTFILES ${IDF_PROJECT_PATH}/server_certs/ca_cert.pem) + +register_component() diff --git a/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild b/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild new file mode 100644 index 000000000..f3f71e3a8 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Example Configuration" + + config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + + config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + + config FIRMWARE_UPGRADE_URL + string "firmware upgrade url endpoint" + default "https://192.168.0.3:8070/hello-world.bin" + help + URL of server which hosts the firmware + image. +endmenu diff --git a/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c new file mode 100644 index 000000000..43fd16243 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c @@ -0,0 +1,177 @@ +/* Advanced HTTPS OTA example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include "string.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_http_client.h" +#include "esp_https_ota.h" + +#include "nvs.h" +#include "nvs_flash.h" + +static const char *TAG = "advanced_https_ota_example"; +extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start"); +extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end"); + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int CONNECTED_BIT = BIT0; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_WIFI_SSID, + .password = CONFIG_WIFI_PASSWORD, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s", wifi_config.sta.ssid); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +static esp_err_t validate_image_header(esp_app_desc_t *new_app_info) +{ + if (new_app_info == NULL) { + return ESP_ERR_INVALID_ARG; + } + + const esp_partition_t *running = esp_ota_get_running_partition(); + esp_app_desc_t running_app_info; + if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version); + } + + if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) { + ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update."); + return ESP_FAIL; + } + return ESP_OK; +} + +void advanced_ota_example_task(void * pvParameter) +{ + ESP_LOGI(TAG, "Starting Advanced OTA example"); + + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, + false, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connected to WiFi network! Attempting to connect to server..."); + + esp_err_t ota_finish_err = ESP_OK; + esp_http_client_config_t config = { + .url = CONFIG_FIRMWARE_UPGRADE_URL, + .cert_pem = (char *)server_cert_pem_start, + }; + + esp_https_ota_config_t ota_config = { + .http_config = &config, + }; + + esp_https_ota_handle_t https_ota_handle = NULL; + esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed"); + vTaskDelete(NULL); + } + + esp_app_desc_t app_desc; + err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_https_ota_read_img_desc failed"); + goto ota_end; + } + err = validate_image_header(&app_desc); + if (err != ESP_OK) { + ESP_LOGE(TAG, "image header verification failed"); + goto ota_end; + } + + while (1) { + err = esp_https_ota_perform(https_ota_handle); + if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) { + break; + } + // esp_https_ota_perform returns after every read operation which gives user the ability to + // monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image + // data read so far. + ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle)); + } + +ota_end: + ota_finish_err = esp_https_ota_finish(https_ota_handle); + if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) { + ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ..."); + vTaskDelay(1000 / portTICK_PERIOD_MS); + esp_restart(); + } else { + ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed..."); + } + + while (1) { + vTaskDelay(1000 / portTICK_PERIOD_MS); + } +} + +void app_main() +{ + // Initialize NVS. + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + // 1.OTA app partition table has a smaller NVS partition size than the non-OTA + // partition table. This size mismatch may cause NVS initialization to fail. + // 2.NVS partition contains data in new format and cannot be recognized by this version of code. + // If this happens, we erase NVS partition and initialize NVS again. + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK( err ); + + initialise_wifi(); + xTaskCreate(&advanced_ota_example_task, "advanced_ota_example_task", 1024 * 8, NULL, 5, NULL); +} + diff --git a/examples/system/ota/advanced_https_ota/main/component.mk b/examples/system/ota/advanced_https_ota/main/component.mk new file mode 100644 index 000000000..3a1333340 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/main/component.mk @@ -0,0 +1,6 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_EMBED_TXTFILES := ${PROJECT_PATH}/server_certs/ca_cert.pem diff --git a/examples/system/ota/advanced_https_ota/server_certs/ca_cert.pem b/examples/system/ota/advanced_https_ota/server_certs/ca_cert.pem new file mode 100644 index 000000000..e69de29bb