Firmware Over-the-Air over HTTP
Overview
This sample shows how to update a device from any plain HTTP server using the Firmware Over-the-Air over HTTP library. The device fetches a signed image with an HTTP GET request, streams it into the slot MCUboot is not running from, and then requests the bootloader to boot it. No update server or device management protocol is required: a directory served by a web server is enough.
The sample enables the fota shell command so the whole cycle can be
driven from the console.
Requirements
A board with a network interface and an MCUboot partition layout with two application slots.
An HTTP server reachable from the board.
The sample is built with sysbuild so that MCUboot is built and flashed along with the application. The bootloader is configured in swap-using-offset mode so that the revert path can be exercised; overwrite-only mode has no revert support.
Warning
The images are signed with the MCUboot development key shipped in the
MCUboot repository. Anyone can sign an image with it. Set
CONFIG_MCUBOOT_SIGNATURE_KEY_FILE in the MCUboot
configuration to a key of your own before using this on a real device.
Building and Running
Build and flash the sample with sysbuild. Give the image a version so the two builds can be told apart on the console:
west build -b esp32c5_devkitc/esp32c5/hpcore --sysbuild samples/subsys/mgmt/fota_http -- -DCONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION=\"1.0.0\"
west flash
Build a second image with a different version. This is the one the board will download, so keep the build directory around:
west build -b esp32c5_devkitc/esp32c5/hpcore --sysbuild -d build/build-v2 samples/subsys/mgmt/fota_http -- -DCONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION=\"2.0.0\"
Serve the signed image from the host. Any HTTP server works, for example:
cd build-v2/fota_http/zephyr && python3 -m http.server 8000 --bind 0.0.0.0
Only zephyr.signed.bin is accepted by MCUboot, not zephyr.bin.
On boards with Wi-Fi, connect to the network first:
uart:~$ wifi connect -s <ssid> -p <passphrase> -k <key_mgmt>
Boards with Ethernet obtain an address with DHCP at boot.
Download the image, request the swap and reboot:
uart:~$ fota download http://<host-ip>:8000/zephyr.signed.bin
uart:~$ fota apply
uart:~$ kernel reboot cold
After the reboot the console shows image version 2.0.0 running as a test image. Without confirmation, the next reboot makes MCUboot revert to version 1.0.0. Confirm it to keep it:
uart:~$ fota status
uart:~$ fota confirm
The fota apply permanent variant skips the test boot and confirms the
image right away.
Automatic download
Set CONFIG_SAMPLE_FOTA_HTTP_AUTO_URL to have the sample start the
download itself instead of waiting for a shell command. It registers for
the connection manager L4 connected event, so the download starts as soon
as the interface has an address, then calls
fota_http_download_async() and prints the progress reported by the
library thread:
west build -b frdm_k64f --sysbuild samples/subsys/mgmt/fota_http -- -DCONFIG_SAMPLE_FOTA_HTTP_AUTO_URL=\"http://<host-ip>:8000/zephyr.signed.bin\"
On boards with Wi-Fi the download starts once wifi connect has
succeeded. The image is only stored. fota apply is still needed to
boot it. The download is skipped while the running image is still a test
image, because the secondary slot then holds the image MCUboot reverts
to, and when the secondary slot already contains the running version.
When built with the resume.conf fragment, a transfer cut short by a
reset continues from the last saved offset after the next boot.
Resuming a download
Build with the resume.conf Kconfig fragment to keep the download offset in
settings. After a power cut or a network error, run the download again
with the resume keyword and the transfer continues with an HTTP Range
request:
uart:~$ fota download http://<host-ip>:8000/zephyr.signed.bin resume
Servers that do not support Range requests answer with the full file and the download restarts from zero.
TLS
Build with the tls.conf Kconfig fragment to accept https URLs.
The sample registers the PEM certificate in src/ca_cert.pem as the
trusted CA; the build converts it into a C array included by
src/main.c. It is a self-signed certificate for localhost and
127.0.0.1 that only exists to exercise the code path. Replace it with
the certificate of your server, or with the CA that signed it, before
serving real images.
To try it on the bench, generate a self-signed certificate for the host
address and copy server.pem over src/ca_cert.pem:
openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key \
-out server.pem -days 365 -subj "/CN=<host-ip>" \
-addext "subjectAltName=IP:<host-ip>"
Then serve the build directory over https from the host:
import http.server, ssl
httpd = http.server.HTTPServer(("0.0.0.0", 8443),
http.server.SimpleHTTPRequestHandler)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.pem", "server.key")
httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()
The certificate can also be provisioned at run time instead of being built into the image. The overlay enables the TLS credentials shell for that:
uart:~$ cred add 1 CA DEFAULT strt <PEM certificate>
Certificate validity dates are only checked when
CONFIG_MBEDTLS_HAVE_TIME_DATE is enabled, which requires
a correct wall clock on the device before the first download.