Testing OMA LwM2M for software updates.

Hi! This post will be a short tutorial on how to set up the Nordic nRF52840 using Zephyr and a Leshan Server to receive software updates using the LwM2M protocol.

What do you need?

To complete this tutorial you need the following component:

You also need some experience with the command line and with the process of building and flashing firmware.

What will you learn?

  • Some basics of the update process;
  • How to build and flash a Zephyr application;
  • How to use OMA LwM2M to perform software updates;

Basics of software updates

In this short section, I will explain some basics of an update system, thus, if you already have experience with this process you may want to skip this part.

The update system is a process that must be included in every connected solution since, is just a matter of time, there will be bugs or security vulnerabilities to fix.

The update process normally uses a client-server approach, where the IoT device (client) register to an update server and periodically checks the availability of a new firmware.

A new firmware is a firmware that has a version (or timestamp) strictly greater than one installed on the device.

Once a new firmware is available the devices start the download, stores it in the flash memory, verifies its integrity and authenticity and applies the update, normally rebooting and leaving the other part of the process to the bootloader.

Once rebooted, the bootloader checks the version all the available slots to see if there is a new version compared to the one in the booting slot. In that case, starts the upgrade process, moving the new firmware to the bootable slot, verifying its signature and executing it.

Let’s start

To follow this tutorial, start creating a tutorial folder in your user’s home:

$ mkdir ~/leshan_tutorial

We will put there all the programs and repository you need to complete this tutorial.

Configure your OpenThread network

We will send our update using an OpenThread network. To create it, you need an OpenThread border router, since we need to expose the local interface of the computer to make it reachable from the IoT device.

To create an OpenThread border router you can follow the official guide that will help you to configure the network. The official guide explains how to build the network using a BeagleBone Black or a Raspberry Pi 3B, but the same steps will work also with a standard Linux computer or a Virtual Machine.

To write the tutorial I’m using a Linux virtual machine with the board connected to the USB port of my computer and shared with the VM from the VirtualBox settings).

Install the server

Once we have an OpenThread network we can run an update server on the same computer where the border router is connected.

We will use Eclipse Leshan as a server. It is a LwM2M server written in Java (make sure you have the Java NDK installed) that allows to perform device management and supports software updates, as defined in the LwM2M standard.

We can download and execute the Leshan server using the following commands:

$ mkdir ~/leshan_tutorial/server
$ cd ~/leshan_tutorial/server
$ wget https://hudson.eclipse.org/leshan/job/leshan/lastSuccessfulBuild/artifact/leshan-server-demo.jar
$ java -jar ./leshan-server-demo.jar -wp 8080

If everything was correct you should now be able to reach the Leshan demo server Web interface using the following address: http://localhost:8080.

Build the bootloader

As explained in the introduction, the IoT device needs to have a bootloader in charge of validating the firmware signature and upgrade if a new firmware is available.

We will use MCUBoot as a bootloader.

We can clone MCUBoot with the following commands:

$ cd ~/leshan_tutorial
$ git clone https://github.com/runtimeco/mcuboot.git
$ cd mcuboot

Since we will use Zephyr for the main application we can build the Zephyr example and flash the bootloader to the board:

$ cd samples/zephyr/
$ make boot BOARD=nrf52840_pca10056

This will configure and build the bootloader. You can see the generated output in the folder:

$ cd build/nrf52840_pca10056/mcuboot/

From there, you can configure the bootloader using the KConfig menu integrated into Zephyr, accessible using the command:

make menuconfig

We can flash the bootloader to the device using the following command:

$ make flash

Using a remote serial control program, such as Minicom, we can get the board output. If everthing was correct we should see the following output:

***** Booting Zephyr OS v1.12.0-1291-gb073b2968 *****
[MCUBOOT] [INF] main: Starting bootloader
[MCUBOOT] [INF] boot_status_source: Image 0: magic=unset, copy_done=0xff, image_ok=0xff
[MCUBOOT] [INF] boot_status_source: Scratch: magic=unset, copy_done=0x24, image_ok=0xff
[MCUBOOT] [INF] boot_status_source: Boot source: slot 0
[MCUBOOT] [INF] boot_swap_type: Swap type: none
[MCUBOOT] [ERR] main: Unable to find bootable image

In the last message, the bootloader indicates that there are no bootable images. In the next section, we will build, sign and flash an image compatible with MCUboot.

Build the firmware

We first need to build the firmware for the device. Let’s clone the Zephyr repository:

$ cd ~/leshan_tutorial
$ git clone https://github.com/zephyrproject-rtos/zephyr.git
$ cd zephyr

We will use the Hello, World! sample as a base project to test our bootloader. When we know that the bootloader is able to correctly execute the image we will move forward building the lwm2m example.

Execute the following commands to build the hello world example:

$ cd samples/hello_world/
$ mkdir build && build
$ cmake -G"Unix Makefiles" -DBOARD=nrf52840_pca10056 ..

If the build folder, we can start configuring the project to enable the OpenThread network.

Zephyr uses KConfig for the configurations. As explained in the bootloader build, is possible to use the graphical interface (make menuconfig); but it is also possible to pass a configuration script containing all the variables using the following command:

$ cmake -G"Unix Makefiles" -DBOARD=nrf52840_pca10056 -DCONF_FILE=[YOUR CONFIGURATION SCRIPT].conf ..

The configuration script contains flags to enable OpenThread and the support for the MCUboot bootloader. You can use the following configuration file:

CONFIG_CPLUSPLUS=y
CONFIG_TEXT_SECTION_OFFSET=0x200
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_BT=n
CONFIG_NETWORKING=y
CONFIG_ENTROPY_GENERATOR=y
CONFIG_ENTROPY_NRF5_RNG=y
CONFIG_NET_PKT_RX_COUNT=50
CONFIG_NET_PKT_TX_COUNT=50
CONFIG_NET_BUF_RX_COUNT=50
CONFIG_NET_BUF_TX_COUNT=50
CONFIG_NET_TX_STACK_SIZE=2048
CONFIG_NET_RX_STACK_SIZE=3072
CONFIG_NET_IPV4=n
CONFIG_NET_IPV6=y
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=10
CONFIG_NET_IPV6_NBR_CACHE=n
CONFIG_NET_IPV6_MLD=n
CONFIG_REBOOT=y
CONFIG_NET_LOG=y
CONFIG_SYS_LOG_SHOW_COLOR=y
CONFIG_PRINTK=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_MPU_ALLOW_FLASH_WRITE=y
CONFIG_NET_L2_OPENTHREAD=y
CONFIG_OPENTHREAD_L2_DEBUG=y
CONFIG_OPENTHREAD_L2_LOG_LEVEL_INFO=y
CONFIG_OPENTHREAD_CHANNEL=13
CONFIG_IEEE802154_NRF5=y
CONFIG_NET_APP=y
CONFIG_NET_APP_NEED_IPV6=y
CONFIG_NET_APP_CLIENT=y
CONFIG_NET_APP_SETTINGS=y
CONFIG_NET_APP_MY_IPV6_ADDR="fdde:ad00:beef::1"
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_NET_UDP=y
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2
CONFIG_NET_MAX_CONTEXTS=5
CONFIG_LWM2M=y
CONFIG_NET_APP_PEER_IPV6_ADDR="fdde:ad00:beef:0:a3c4:df94:d9fe:70a"
CONFIG_HAS_FLASH_LOAD_OFFSET=y

You will find an explanation for all the previous flags in the official Zephyr documentation.

We can now configure and build the firmware using the following command:

$ cmake -G"Unix Makefiles" -DBOARD=nrf52840_pca10056 -DCONF_FILE=prj_nrf52840_ot.conf ..
$ make 

Once the firmware has been correctly built, we can sign it and prepare it to be flashed on the device. We prepared a simple script to do it:

#!/bin/bash -e

MCUBOOTDIR=/Users/alangiu/Projects/tutorials/oma_lwm2m_tutorial/mcuboot/
IMGTOOL=$MCUBOOTDIR/scripts/imgtool.py
SIGNING_KEY=$MCUBOOTDIR/root-rsa-2048.pem
IMAGEBIN=zephyr/zephyr.bin
SIGNEDBIN=zephyr_signed.bin
FIRMWAREHEX=firmware.hex

$IMGTOOL sign \
    --key $SIGNING_KEY \
    --header-size 0x200 \
    --align 8 \
    --version 1.2 \
    --slot-size 0x60000 \
    $IMAGEBIN \
    $SIGNEDBIN

srec_cat $SIGNEDBIN -binary -offset 0xc000 -o $FIRMWAREHEX -intel

Save this script, make it executable and execute it. It will generate a flashable Intel Hex file. Using nrfjprog we can now flash the firmware:

$ nrfjprog --program firmware.hex --sectorerase

The flag –sectorerase indicates to the program that it should erase only the necessary pages and leave the bootloader untouched.

If the process was correct, you should now see the following serial output from the board:

***** Booting Zephyr OS v1.12.0-1291-gb073b2968 *****
[MCUBOOT] [INF] main: Starting bootloader
[MCUBOOT] [INF] boot_status_source: Image 0: magic=unset, copy_done=0xff, imaf
[MCUBOOT] [INF] boot_status_source: Scratch: magic=unset, copy_done=0x24, imaf
[MCUBOOT] [INF] boot_status_source: Boot source: slot 0
[MCUBOOT] [INF] boot_swap_type: Swap type: none
[MCUBOOT] [INF] main: Bootloader chainload address offset: 0xc000
[MCUBOOT] [INF] main: Jumping to the first image slot
[net] [INF] openthread_init: OpenThread version: OPENTHREAD/20170716-00461-gd5
[net] [INF] openthread_init: Network name:   ot_zephyr
[net] [INF] ot_state_changed_handler: State changed! Flags: 0x0037133f Curren1
***** Booting Zephyr OS v1.12.0-1291-gb073b2968 *****
Zephyr Shell, Zephyr version: 1.12.99
Type 'help' for a list of available commands
shell> [net] [INF] ot_state_changed_handler: State changed! Flags: 0x000000011
[net] [INF] ot_state_changed_handler: State changed! Flags: 0x00000040 Curren1
Hello World! arm

We can use the OpenThread shell to ping the server and see if the connection is correct.

If everything works we can repeat the previous commands in the specific lwm2m example, that will correctly start and configure the lwm2m process.

$ cd ~/leshan_tutorial
$ cd samples/net/lwm2m_client
$ mkdir build && cd build

Copy the configuration and the sign script to the lwm2m_client folder and then execute:

$ cmake -G"Unix Makefiles" -DBOARD=nrf52840_pca10056 -DCONF_FILE=prj_nrf52840_ot.conf ..
$ make
$ ./sign.sh
$ nrfjprog --program firmware.hex --sectorerase
$ nrfjprog --reset

If everything was correct, after a device reset, the device should join the OT network and then register to the Leshan server, as shown in the following image:

leshan with connected device

Build an update

We can now build an update that can be sent to the device. Obviously, the update must contain the same LwM2M client to enable future updates. For this reason, we will build the new firmware starting from the one that we already created.

Since our goal is to make a short demo, we will add a simple printf to the new firmware, send it to the device and see from the serial output what happens:

  1. Add a print in the main method (i.e. printf("I'm the updated firmware\n"););
  2. Recompile the firmware: make;
  3. Sign the firmware: ./sign.sh;

We don’t need now to create the hex file since the binary generated from the sign.sh script is exactly what should be placed in the flash memory.

Send the update

Using the Leshan Web interface you can now send the update to the device that should reboot and boot the new image correctly.