Skip to main content

ESP-IDF C5 - Assign. 3.1

·6 mins·
Table of Contents
Configure the partition table for OTA, then write an application that connects to Wi-Fi and performs a single OTA update by downloading new firmware from a fixed HTTPS URL with esp_https_ota.
WS00C - This article is part of a series.
Part 9: This Article

In this assignment, you’ll put the theory from Lecture 3 into practice. You’ll switch to a partition table that supports OTA, then write an application that connects to Wi-Fi and downloads a new firmware image over HTTPS using the esp_https_ota component.

The OTA firmware location is at the following link.

https://raw.githubusercontent.com/espressif/developer-portal-codebase/update-workshop-to-c5-code/content/workshops/esp-idf-with-esp32-c5/ota/ota-new-firmware.bin

Its only job is to log a message confirming the update worked.

Assignment steps
#

  1. Create a new project and set up the partition table for OTA.
  2. Add the includes.
  3. Connect to Wi-Fi.
  4. Perform the OTA update.
  5. Put it all together in app_main().
  6. Build, flash, and monitor.

Create a new project and set up the partition table for OTA
#

  1. Create a new ESP-IDF project

    OTA needs at least two app partitions: one to boot from and one to receive the new firmware, so you need to change it next.

  2. Open menuconfig (> ESP-IDF: SDK Configuration Editor (menuconfig)) and go to Partition TableFactory app, two OTA definitions.

    This replaces the single factory app partition with two OTA app partitions (ota_0 and ota_1) and adds the otadata partition used to track which one is active.

  3. In the same menuconfig window, go to Serial Flasher ConfigFlash Size4MB.

  4. Save the configuration and close menuconfig.

Add the includes
#

  1. In your main source file, add the includes for FreeRTOS, the event group, the network stack, Wi-Fi, HTTPS OTA, and NVS. Then define a TAG for logging, your network credentials, and the firmware URL:

    #include "freertos/FreeRTOS.h"
    #include "freertos/event_groups.h"
    #include "esp_event.h"
    #include "esp_log.h"
    #include "esp_netif.h"
    #include "esp_wifi.h"
    #include "esp_http_client.h"
    #include "esp_https_ota.h"
    #include "esp_crt_bundle.h"
    #include "nvs_flash.h"
    
    #define WIFI_SSID     "EXAMPLE_WIFI_SSID"
    #define WIFI_PASSWORD "EXAMPLE_WIFI_PASS"
    
    #define FIRMWARE_URL "https://raw.githubusercontent.com/espressif/developer-portal-codebase/tree/main/content/workshops/esp-idf-with-esp32-c5/ota/ota-new-firmware.bin"
    
    static const char *TAG = "ota_example";

    Replace EXAMPLE_WIFI_SSID and EXAMPLE_WIFI_PASS with the credentials of the Wi-Fi network your board should join.

Connect to Wi-Fi
#

The OTA download needs a working Wi-Fi connection first, so bring up the station and wait until it gets an IP address.

  1. Declare an event group and a bit to signal a successful connection, above app_main():

    static EventGroupHandle_t s_wifi_event_group;
    #define WIFI_CONNECTED_BIT BIT0
  2. Add the Wi-Fi event handler. It reconnects on start and on disconnect, and sets the event bit once the station gets an IP address:

    static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                                    int32_t event_id, void *event_data)
    {
        if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
            esp_wifi_connect();
        } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
            ESP_LOGI(TAG, "Disconnected from AP, retrying...");
            esp_wifi_connect();
        } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
            ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
            ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
            xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
        }
    }
  3. Add a wifi_connect() helper that initializes the network stack and the Wi-Fi driver, registers the handler above, then starts the station and blocks until it connects:

    static void wifi_connect(void)
    {
        s_wifi_event_group = xEventGroupCreate();
    
        ESP_ERROR_CHECK(esp_netif_init());
        ESP_ERROR_CHECK(esp_event_loop_create_default());
        esp_netif_create_default_wifi_sta();
    
        wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT();
        ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_cfg));
    
        ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
        ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL));
    
        wifi_config_t wifi_config = {
            .sta = {
                .ssid = WIFI_SSID,
                .password = WIFI_PASSWORD,
            },
        };
    
        ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
        ESP_ERROR_CHECK(esp_wifi_start());
    
        ESP_LOGI(TAG, "Connecting to SSID: %s", WIFI_SSID);
        xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
    }

    xEventGroupWaitBits() blocks app_main() until WIFI_CONNECTED_BIT is set, so the OTA download only starts once the board actually has an IP address.

Perform the OTA update
#

With Wi-Fi connected, download the new firmware and let esp_https_ota write it to the inactive OTA partition.

  1. Add a do_firmware_update() helper:

    static void do_firmware_update(void)
    {
        ESP_LOGI(TAG, "Starting OTA update from %s", FIRMWARE_URL);
    
        esp_http_client_config_t http_config = {
            .url = FIRMWARE_URL,
            .crt_bundle_attach = esp_crt_bundle_attach,
        };
    
        esp_https_ota_config_t ota_config = {
            .http_config = &http_config,
        };
    
        esp_err_t ret = esp_https_ota(&ota_config);
        if (ret == ESP_OK) {
            ESP_LOGI(TAG, "OTA update successful, rebooting...");
            vTaskDelay(pdMS_TO_TICKS(2000));
            esp_restart();
        } else {
            ESP_LOGE(TAG, "OTA update failed: %s", esp_err_to_name(ret));
        }
    }

    A few things worth noting:

    • crt_bundle_attach = esp_crt_bundle_attach validates the server’s TLS certificate against the certificate bundle built into ESP-IDF. Since FIRMWARE_URL uses https://, this is what lets the HTTPS connection be verified without you supplying a certificate yourself.
    • esp_https_ota() does the entire download and write in one call: it opens the HTTP connection, streams the image into the inactive OTA partition, and validates it as it goes.
    • On success, the new image is marked as the one to boot next. The application waits briefly to let the log flush, then calls esp_restart() to boot into it.
    esp_crt_bundle_attach relies on CONFIG_MBEDTLS_CERTIFICATE_BUNDLE, which is enabled by default in ESP-IDF, so no extra menuconfig step is needed for it.

Put it all together in app_main()
#

  1. Write app_main() so it initializes NVS, connects to Wi-Fi, and then performs the update:

    void app_main(void)
    {
        ESP_ERROR_CHECK(nvs_flash_init());
        wifi_connect();
        do_firmware_update();
    }

    NVS is initialized first because the Wi-Fi driver stores calibration data there.

Build, flash, and monitor
#

  1. Build and flash the project: > ESP-IDF: Build, Flash and Start a Monitor on your Device.

  2. Watch the monitor output. The board connects to Wi-Fi, downloads the firmware, and reboots:

    I (612) ota_example: Connecting to SSID: EXAMPLE_WIFI_SSID
    I (2145) ota_example: Got IP: 192.168.1.42
    I (2148) ota_example: Starting OTA update from https://raw.githubusercontent.com/...
    I (3820) esp_https_ota: Starting OTA...
    I (4560) esp_https_ota: esp_https_ota_perform returned ESP_OK
    I (4560) ota_example: OTA update successful, rebooting...
  3. After the reboot, the board runs the new firmware, which confirms the download worked and then returns from app_main():

    OTA firmware v2.0 downloaded!
    
    I (284) main_task: Returned from app_main()

    Seeing this message from a brand new binary is the confirmation that the OTA update replaced your original application: the code now running is the one you just downloaded, not the one you flashed over the wire.

Show assignment solution
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "esp_crt_bundle.h"
#include "nvs_flash.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>

#define WIFI_SSID     "SSID"
#define WIFI_PASSWORD "PASSWORD"

#define FIRMWARE_URL "https://raw.githubusercontent.com/espressif/developer-portal-codebase/tree/main/content/workshops/esp-idf-with-esp32-c5/ota/ota-new-firmware.bin"
#define FIRMWARE_VERSION_URL "https://raw.githubusercontent.com/espressif/developer-portal-codebase/tree/main/content/workshops/esp-idf-with-esp32-c5/ota/version"

#define CURRENT_FIRMWARE_VERSION "1.0.0"

static const char *TAG = "ota_version_example";

static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0

static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                                int32_t event_id, void *event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        ESP_LOGI(TAG, "Disconnected from AP, retrying...");
        esp_wifi_connect();
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
        ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

static void wifi_connect(void)
{
    s_wifi_event_group = xEventGroupCreate();

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_cfg));

    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASSWORD,
        },
    };

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "Connecting to SSID: %s", WIFI_SSID);
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
}

static void do_firmware_update(void)
{
    ESP_LOGI(TAG, "Starting OTA update from %s", FIRMWARE_URL);

    esp_http_client_config_t http_config = {
        .url = FIRMWARE_URL,
        .crt_bundle_attach = esp_crt_bundle_attach,
    };

    esp_https_ota_config_t ota_config = {
        .http_config = &http_config,
    };

    esp_err_t ret = esp_https_ota(&ota_config);
    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "OTA update successful, rebooting...");
        vTaskDelay(pdMS_TO_TICKS(2000));
        esp_restart();
    } else {
        ESP_LOGE(TAG, "OTA update failed: %s", esp_err_to_name(ret));
    }
}


void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    wifi_connect();
    do_firmware_update();

}

Conclusion
#

In this assignment, you switched to a partition table with two OTA app partitions, then wrote an application that connects to Wi-Fi and updates itself over HTTPS with a single call to esp_https_ota(). The new firmware took over after a reboot, proving the update worked end to end.

In the next assignment, you’ll extend this OTA workflow further.

Next step
#

Next assignment → Assignment 3.2

Or go back to navigation menu

WS00C - This article is part of a series.
Part 9: This Article

Related

ESP-IDF C5 - Assign. 1.1

·9 mins
Write an application that connects the ESP32-C5 to a Wi-Fi network as a station, restricts the radio to the 5 GHz band, and reports the band and channel it joined.

ESP-IDF C5 - Assign. 3.2 (Optional)

·6 mins
Extend the OTA application from the previous assignment with rollback support, so that a newly updated firmware must confirm it works before it is trusted, and is otherwise automatically reverted to the previous working image.

ESP-IDF C5 - Assign. 4.1

·4 mins
Write an application that enters deep sleep, wakes up periodically with the RTC timer, and keeps a counter alive across sleep cycles using RTC memory.