Zephyr

Over-the-air (OTA) updates of external MCUs with Zephyr

Remotely update firmware of external MCUs connected to your Zephyr device.

This guide explains how to set up the Spotflow device module to perform over-the-air (OTA) updates of external MCUs connected to your Zephyr device. If you want to automatically update the firmware that runs the module itself (the main firmware), see Over-the-air (OTA) updates with Zephyr. To support both scenarios in one application, see the notes below.

Prerequisites

Before you start integrating your device with Spotflow, make sure you:

  • have development environment with Zephyr 3.7, 4.1, 4.2, 4.3, or 4.4,
  • have established a connection to the internet from the device.

When creating a new west workspace, make sure to use a supported Zephyr version, for example, using west init --mr v4.3.0. By default, west init checks out the main branch, which may contain changes breaking the build.

Alternatively, you can follow the Quickstart: Zephyr Integration Guide to integrate Spotflow using sample application, instead of adding the spotflow module to an existing project.

Before you start integrating your device with Spotflow, make sure you:

  • have development environment with nRF Connect SDK v3.0.0 or later (v3.2.4 recommended),
  • have established a connection to the internet from the device.

Alternatively, you can follow the Nordic nRF Connect SDK Integration Guide to integrate Spotflow using sample application, instead of adding the spotflow module to an existing project.

OTA updates of external MCUs additionally require:

  • A persistent Zephyr settings backend (typically NVS) so that the update state and results survive reboot.

Install Spotflow Device Module

Add the spotflow module as a west dependency to your west.yml file.

west.yml
manifest:
    projects:
    - name: spotflow
        revision: main
        path: modules/lib/spotflow
        url: https://github.com/spotflow-io/device-sdk

Then, install the west dependencies using the following command:

west update

Enable OTA updates in Kconfig

Add the following to your prj.conf:

prj.conf
CONFIG_SPOTFLOW=y
CONFIG_SPOTFLOW_DEVICE_ID="zephyr-device-001"
CONFIG_SPOTFLOW_INGEST_KEY="{your-ingest-key}"

CONFIG_SPOTFLOW_OTA=y

# Automatic main firmware handling is not needed when updating only external MCUs
CONFIG_SPOTFLOW_OTA_AUTO_HANDLE_MAIN_FIRMWARE=n

# Persistent storage for OTA attempt state and results
CONFIG_NVS=y
CONFIG_SETTINGS_NVS=y

Handle firmware updates

Implement spotflow_on_handle_firmware_update(). The device module calls it once for each received firmware update. The recommended way to download the firmware image is to use the Spotflow downloader:

#include <spotflow/ota.h>

static SPOTFLOW_DEFINE_DOWNLOADER(external_mcu_downloader);

static void write_external_mcu_block(const struct spotflow_artifact_block *block,
                                     struct spotflow_downloader *downloader,
                                     void *ctx)
{
    /* Write block->data to the external MCU. */
}

enum spotflow_ota_result
spotflow_on_handle_firmware_update(const struct spotflow_firmware_info *info)
{
    int ret = spotflow_download_artifact(&external_mcu_downloader,
                                         info->download_request,
                                         write_external_mcu_block,
                                         NULL);
    if (ret < 0) {
        return SPOTFLOW_OTA_RESULT_FAILED;
    }

    /* Verify and activate the firmware on the external MCU. */
    return SPOTFLOW_OTA_RESULT_SUCCEEDED;
}

The info parameter contains the details of the firmware to download. You can use slug to distinguish between firmware updates for different external MCUs. The download_request field contains the URL and OTA secret for the firmware image, and you can pass it directly to spotflow_download_artifact().

The implementation of spotflow_on_handle_firmware_update() must always return a terminal result: SPOTFLOW_OTA_RESULT_SUCCEEDED, SPOTFLOW_OTA_RESULT_FAILED, or SPOTFLOW_OTA_RESULT_CANCELED. The function runs in the OTA worker thread, so it can block as needed while your application performs the update. It is also acceptable to reboot while the function is running; the module calls it with the same parameters again after the device reconnects.

Deploy OTA update

Once the device is online and connected to Spotflow MQTT, create a deployment in the portal as described in Deploy Over-the-Air (OTA) Updates. Your implementation of spotflow_on_handle_firmware_update() will be called for each firmware in the deployment, and all results will be reported to the cloud.

Because you disabled CONFIG_SPOTFLOW_OTA_AUTO_HANDLE_MAIN_FIRMWARE in Kconfig, each firmware update will be passed to your implementation of spotflow_on_handle_firmware_update(), regardless of whether it is marked as main.

Still, it is a good practice to mark firmware as main only when it runs the Spotflow device module.

Advanced: Handle cancellation

When a device is currently processing an update attempt and you stop the deployment or supersede it with a new one, the cloud sends a cancellation request to the device. To prevent half-finished updates, the Spotflow device module considers the cancellation actionable only if no firmware update has already reported its result.

When the module receives an actionable cancellation, it calls spotflow_on_update_canceled(). Your handler can use it to stop ongoing work, for example by canceling the download started by spotflow_on_handle_firmware_update():

void spotflow_on_update_canceled(void)
{
    spotflow_cancel_download(&external_mcu_downloader);
}

Because spotflow_on_update_canceled() runs on the system workqueue, do not perform blocking work in it.

You can also poll for actionable cancellation using spotflow_is_update_canceled(). Report successful cancellation by returning SPOTFLOW_OTA_RESULT_CANCELED from spotflow_on_handle_firmware_update():

enum spotflow_ota_result
spotflow_on_handle_firmware_update(const struct spotflow_firmware_info *info)
{
    int ret = spotflow_download_artifact(&external_mcu_downloader,
                                         info->download_request,
                                         write_external_mcu_block,
                                         NULL);

    if (spotflow_is_update_canceled()) {
        return SPOTFLOW_OTA_RESULT_CANCELED;
    }

    if (ret < 0) {
        return SPOTFLOW_OTA_RESULT_FAILED;
    }

    /* Verify and activate the firmware on the external MCU. */
    return SPOTFLOW_OTA_RESULT_SUCCEEDED;
}

Advanced: Combine automatic and manual handling of OTA updates

You can combine both automatic and manual handling of OTA updates in one application. Keep CONFIG_SPOTFLOW_OTA_AUTO_HANDLE_MAIN_FIRMWARE=y to let the SDK update the main firmware automatically, and still implement spotflow_on_handle_firmware_update() for external MCUs.

The Spotflow device module processes firmware updates in the order specified when you create the deployment. If any firmware update fails, the module cancels the remaining firmware updates in the attempt.

How device module works

See How device module works in the main Zephyr OTA update guide for the details of attempt processing, persistence, threading, security, and configuration.

Reference repository materials

Learn more

How is this guide?