A Setup for Firmware Updates over the Air – Part 4 (Gateway)

Hands on the Gateway

In this part, we will set up a gateway where the boards of the last parts can connect to Bluetooth. We will use a Raspberry Pi 3+, install an operating system, set up WiFi, and deploy some docker containers. After that, we do our first over-the-air update!

Hardware and Operating System

For the gateway, we chose a Raspberry Pi 3+. It has Bluetooth and WiFi support and therefore fits our needs perfectly. As an operating system, Raspbian is used for simplicity.

You can, of course, use any board or device which supports Bluetooth (WiFi is optional, but makes a better ‚over-the-air‘ demonstration). Choose an appropriate operating system for your device if you want to follow the instructions, it should, however, support Docker. If you want to, you can build your kernel with Yocto.

I had some problems with the Raspberry Pi Zero, a smaller Pi, which also supports WiFi and Bluetooth and is a lot cheaper than the Raspberry Pi 3+. Unfortunately, the docker container crashed without any obvious reasons (it has less, but enough RAM), so I had to switch my hardware.

 

Download and install Raspbian

You can download the latest Raspbian here. If you want to have a graphical interface and desktop, pick the appropriate zip download. For our needs, the smallest image (lite latest) is sufficient, so let’s download it with:

fota@demo:~$ mkdir gateway && cd gateway 
fota@demo:~/gateway$ wget https://downloads.raspberrypi.org/raspbian_lite_latest
Resolving downloads.raspberrypi.org (downloads.raspberrypi.org)... 93.93.130.214, 93.93.135.188, 93.93.128.211, ...
Connecting to downloads.raspberrypi.org (downloads.raspberrypi.org)|93.93.130.214|:443... connected.
...

After that, you have a zip file in the form ‚yyyy-mm-dd-raspbian-buer-lite.img‘ in your downloads folder, where the date describes the built date of the Raspbian version. Afterwards, we unpack it:

fota@demo:~/gateway$ unzip 2019-09-26-raspbian-buster-lite.zip

For flashing, put a MicroSD card into your computer. I used a 16GB MicroSD card, so there is enough space for the operating system and services. Get the device name with:

fota@demo:~/gateway$ lsblk
NAME                             MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT

sda                                8:0    0  14.6G  0 disk  
nvme0n1                          259:0    0 465.8G  0 disk  
├─nvme0n1p1                      259:1    0   512M  0 part  /boot/efi
├─nvme0n1p2                      259:2    0   244M  0 part  /boot
...

You can get the name of the SD card by seeing which size fits the size of the SD card, or you execute the command once before plugging it in. Warning: Be 100% sure that you have the correct device. Otherwise, you will overwrite important data in the next step (e.g., if you accidentally flash the image on your hard drive instead).

Now flash the image with:

# CAREFUL: Replace /dev/sda with your sd card device!
# if is source, of is destination
# bs is blocksize (4M is a good choice)
# conv=fsync to flash data to disk after every write
# status=progress shows progress of flashing
fota@demo:~/gateway$ sudo dd if=2019-09-26-raspbian-buster-lite.img of=/dev/sda bs=4M conv=fsync status=progress

Configure the Gateway

After we flashed the image on the SD-card, we can plug it into the Raspberry Pi, connect the Pi to a monitor, a keyboard and a power supply, and boot. If everything worked well, you will see some raspberries followed by a login prompt.

After that, you can log in. The login credentials are pi/raspberry, you maybe want to change the password.

All configurations are done with the command raspi-config, which you can access by executing

pi@raspberry: sudo raspi-config

on the gateway. Start with changing the password (First entry in the menu, Change User Password). After that, we change the hostname to prevent collisions in the network. Go to Network Options and Hostname and type in the new hostname. I chose iot-fota-gw for this example.

Wireless Internet Connection and SSH

We need a working internet connection to download the services and firmware. If you want to connect your device to WiFi, choose the second point Network Options and Wi-fi in the raspi-config menu. Then, add your SSID and password of your network. You can also use Ethernet and plug in a LAN cable into your Pi.

After that, ping should work:

pi@iot-fota-gw:~$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=121 time=22.4 ms
...

Additionally, we enable SSH, so we control the gateway without having it connected to a monitor. Choose the entry Interfacing Options, choose SSH and enable it in the raspi-config again.

Create and Place an SSH Key

To access the gateway later without typing in a password every time, we need an SSH key on the device.
On the host device (not the gateway!), create a new one and copy it with

fota@demo:~$ ssh-keygen -b 8092 -t rsa -C "fota gw access key" -f ~/.ssh/iot-fota-gw
fota@demo:~$ ssh-copy-id -i ~/.ssh/iot-fota-gw.pub pi@iot-fota-gw

To use the key at connection, add the following lines into .ssh/config

Host fota-gw
    HostName iot-fota-gw
    User pi
    IdentityFile ~/.ssh/iot-fota-gw

Now, new connections should use the SSH key. Check this with

fota@demo:~$ ssh fota-gw

If you aren’t asked for a password or a password for your SSH key, everything works as expected.

 

 

Deploy the Service Containers

Now, it’s time to deploy services on the gateway to connect to the wireless boards and proxy information to the internet. We will use docker-compose files which pull prebuild containers (all done by foundries.io). The following commands are executed on the gateway!

At first, download the repo with all Docker compose files.

# We use mp-44 for reproducibility
pi@iot-fota-gw:~$ git clone https://github.com/foundriesio/gateway-containers --branch mp-44 && cd gateway-containers

After that, we deploy the services. We will use a Leshan-Server in the next post, which uses the lwm2m-Protokoll. The Leshan server will run on our host, so we use the lwm2m.yml file. This can take a few minutes, so just be patient.

# docker compose will execute the file docker-compose.lwm2m.yml
# -d for deamon
# TAG is for reproducibility again.
pi@iot-fota-gw:~/gateway-containers$ TAG=44 docker-compose -f docker-compose.lwm2m.yml up -d

After some time, all services are deployed. Check if all containers are running:

pi@iot-fota-gw:~/gateway-containers$ docker ps
CONTAINER ID        IMAGE                                    COMMAND                  CREATED             STATUS              PORTS                    NAMES
ef65e25096f2        hub.foundries.io/bt-joiner:44            "/start.sh /bin/sh -…"   20 seconds ago      Up 17 seconds                                gateway-containers_bt-joiner_1
98f8bbb184f3        hub.foundries.io/radvd64:44              "/start.sh --interfa…"   27 seconds ago      Up 19 seconds                                gateway-containers_radvd64-bt0_1
6187672fe455        hub.foundries.io/cf-proxy-coap-http:44   "/bin/sh -c 'cd /opt…"   27 seconds ago      Up 16 seconds       0.0.0.0:5682->5682/udp   gateway-containers_californium-proxy_1
5135f17c098a        hub.foundries.io/iface-monitor:44        "/start.sh --interfa…"   27 seconds ago      Up 19 seconds                                gateway-containers_iface-mon-bt0_1
03b962a65bf8        hub.foundries.io/dns64:44                "/start.sh"              27 seconds ago      Up 20 seconds                                gateway-containers_dns64_1
e8589642f128        hub.foundries.io/nat64-jool:44           "/start.sh"              27 seconds ago      Up 20 seconds                                gateway-containers_jool_1

A few words about the containers: The bt-joiner connects with the board over Bluetooth Low Energy so that they can reach the gateway. Our boards will later request data over HTTP, he cf-proxy-coap-http container takes incoming CoAP-requests from the board (CoAP is a lightweight transfer protocol, commonly used in IoT), transforms them into HTTP-Requests and redirects them to the nginx container. The nginx container redirects them to the Leshan server (this one needs to know the IP address, this is why we had to specify it on Docker compose). This sounds complex but will get a bit clearer in the next part. If you want more information, look through the configuration files of the container in the cloned gateway repository.

 

The gateway is set up for the next and last part now, where we install a Leshan Server and comfortably perform an over-the-air update.

 

Simple Over-the-Air Update

Well, this blog part would be a bit boring if we would not make any real progress for our over-the-air updates. Therefore, let us use our new gateway to perform a simple over the air update. The result will not be entirely satisfying because it is not completely uncomfortable, but it will be the first working over-the-air update. Additionally, we can check if our board connection works.

Flash the Application

Like before, we need to use the right application to do this. Fortunately, zephyr has a sample application which connects to Bluetooth and waits for communication, the smp_svr application. Let’s build, sign, and flash the application like done in Part2. Please note: I assume that MCUboot is already flashed on the board. See the last part for the instructions.

fota@demo:~/builds$ west build -d build-smp_svr -b 96b_nitrogen -s ../zephyrproject/zephyr/samples/subsys/mgmt/mcumgr/smp_svr -- -DCONFIG_BOOTLOADER_MCUBOOT=y
fota@demo:~/builds$ west sign -t imgtool -p ../mcuboot/scripts/imgtool.py -d build-smp_svr -- --key ../mcuboot/root-rsa-2048.pem -H smp_svr.signed.hex
fota@demo:~/builds$ west flash -d build-smp_svr/ --hex-file smp_svr.signed.hex

If this worked so far, you will see the following output from your board on your minicom terminal:

***** Booting Zephyr OS v1.14.0-rc1-1238-g350f6f715632 *****                    
Bluetooth initialized                                                           
Advertising successfully started

Additionally, let us already prepare a new application which will be upgraded later. To preserve our ability to connect over Bluetooth, we use the smp_svr application and add a print line on start:

# Add 'printk("New version!\n");' in the first line of void main()
fota@demo:~/builds$ vim ../zephyrproject/zephyr/samples/subsys/mgmt/mcumgr/smp_svr/src/main.c 

After that, build and sign (but not flash!!!) the application. After that, already copy it on the gateway.

fota@demo:~/builds$ west build -d build-new_smp_svr -b 96b_nitrogen -s ../zephyrproject/zephyr/samples/subsys/mgmt/mcumgr/smp_svr -- -DCONFIG_BOOTLOADER_MCUBOOT=y
fota@demo:~/builds$ west sign -t imgtool -p ../mcuboot/scripts/imgtool.py -d build-new_smp_svr -- --key ../mcuboot/root-rsa-2048.pem -H new_smp_svr.signed.hex
fota@demo:~/builds$ scp new_smp_svr.signed.hex fota-gw: # the : at the end is important!
sending incremental file list
new_smp_svr.signed.hex
        123,804 100%   10.20MB/s    0:00:00 (xfr#1, to-chk=0/1)

Mcumgr

As a simple way to communicate with the board mcumgr is used. It implements communication with remote devices where an SMP server is running (for example, our board). We will now build a docker container where mcumgr is installed and send some commands afterwards.
I prepared a Dockerfile here, which uses alpine as a base system and installs mcumgr-cli inside. Let’s create a new folder on our gateway, download the Dockerfile, and build it.

pi@iot-fota-gw:~$ mkdir mcumgr-container && cd mcumgr-container
pi@iot-fota-gw:~/mcumgr-container$ wget -O Dockerfile https://www.methodpark.de/blog/wp-content/uploads/2019/05/Dockerfile.txt
pi@iot-fota-gw:~/mcumgr-container$ docker build -t mcumgr:latest -f Dockerfile .

Please note: I used arm32v6 as a base file because I use a Raspberry Pi Zero W. If you use another device, you maybe have to replace this line with your architecture!

After that, we can start the container:

# -it for interactive start
# --net=host and --privileged to access the hci controller and be able to communicate over BLE 
pi@iot-fota-gw:~$ docker run -it --net=host --privileged --name mcumgr mcumgr

Now, copy the new image into the container. Normally, you would mount a folder into the container, but for testing purposes, this is fine.

# must be executed in another terminal on the gateway, the first one runs the mcumgr container
pi@iot-fota-gw:~$ docker cp new_smp_svr.signed.hex mcumgr:/go

Update

Now we can update the firmware. Additional information can also be found in the mcumgr-cli repo.
Let us ping our board and echo it some string to test the communication:

# conntype specifies the type of connection, here bluetooth low energy. other types are possible (uart ie)
# connstring specifys the controller and the peer name
/go # mcumgr --conntype ble --connstring ctlr_name=hci0,peer_name='Zephyr' echo hello_world
hello_world

After that, we can check which images are currently stored on the board:

/go # mcumgr --conntype ble --connstring ctlr_name=hci0,peer_name='Zephyr' image list
Images:
 slot=0
    version: 0.0.0
    bootable: true
    flags: active confirmed
    hash: 81f...
Split status: N/A (0)

As you see, we have an active image in slot0 and no image in slot1. Let us change this by uploading a new image. It will automatically be stored into slot1:

/go # mcumgr --conntype ble --connstring ctlr_name=hci0,peer_name='Zephyr' image upload new_smp_svr.signed.hex
 19.73 KiB / 120.90 KiB [=====>-------------------------]  16.32% 3.35 KiB/s 30s
 41.27 KiB / 120.90 KiB [==========>--------------------]  34.13% 1.75 KiB/s 45s
 71.96 KiB / 120.90 KiB [==================>------------]  59.52% 1.47 KiB/s 33s
 97.22 KiB / 120.90 KiB [========================>------]  80.41% 1.39 KiB/s 17s
 120.90 KiB / 120.90 KiB [===============================] 100.00% 1.35 KiB/s 1m29s
 Done

A new check with image list shows the new image in slot1:

/go # mcumgr --conntype ble --connstring ctlr_name=hci0,peer_name='Zephyr' image list
Images:
 slot=0
    version: 0.0.0
    bootable: true
    flags: active confirmed
    hash: 81f08896a513b0e04c60149f220fa433b2ddafa213e5c992b4a3129da2d1c456
 slot=1
    version: 0.0.0
    bootable: true
    flags: 
    hash: 39e23b19f2b99a5ecdf159a6f9e4dbb95a343a32ecb3e33742fa0610ba670d04
Split status: N/A (0)

Now, we mark it, so it gets tested on the next reboot.

/go # mcumgr --conntype ble --connstring ctlr_name=hci0,peer_name='Zephyr' image test 3
9e23b19f2b99a5ecdf159a6f9e4dbb95a343a32ecb3e33742fa0610ba670d04
Images:
 slot=0
    version: 0.0.0
    bootable: true
    flags: active confirmed
    hash: 81f08896a513b0e04c60149f220fa433b2ddafa213e5c992b4a3129da2d1c456
 slot=1
    version: 0.0.0
    bootable: true
    flags: pending
    hash: 39e23b19f2b99a5ecdf159a6f9e4dbb95a343a32ecb3e33742fa0610ba670d04
Split status: N/A (0)

As you can see, the new image is now pending! Let us reset the board, so it gets restarted. Of course, we do this remotely:

/go # mcumgr --conntype ble --connstring ctlr_name=hci0,peer_name='Zephyr' reset

If everything worked, you should see the following output on your board:

/go # mcumgr --conntype ble --connstring ctlr_name=hci0,peer_name='Zephyr' reset
Connected                                                                       
***** Booting Zephyr OS v1.14.0-rc1-1238-g350f6f715632 *****                    
...                   
[00:00:00.036,743]  mcuboot: Swap type: test                               
[00:00:07.125,213]  mcuboot: Bootloader chainload address offset: 0xa000   
[00:00:07.132,690]  mcuboot: Jumping to the first image slot               
***** Booting Zephyr OS v1.14.0-rc1-1238-g350f6f715632 *****                    
New version!                                                                 
Bluetooth initialized                                                           
Advertising successfully started

As you can see, the update worked! Now, additionally, let us confirm the image so it won’t get reverted anymore like last time!

/go # mcumgr --conntype ble --connstring ctlr_name=hci0,peer_name='Zephyr' image confirm
Images:
 slot=0
    version: 0.0.0
    bootable: true
    flags: active confirmed
    hash: 39e23b19f2b99a5ecdf159a6f9e4dbb95a343a32ecb3e33742fa0610ba670d04
 slot=1
    version: 0.0.0
    bootable: true
    flags: 
    hash: 81f08896a513b0e04c60149f220fa433b2ddafa213e5c992b4a3129da2d1c456
Split status: N/A (0)

After a reset, the new image will still be booted!

/go # mcumgr --conntype ble --connstring ctlr_name=hci0,peer_name='Zephyr' reset
***** Booting Zephyr OS v1.14.0-rc1-1238-g350f6f715632 *****
...
[00:00:00.033,264] mcuboot: Swap type: none
[00:00:00.245,544] mcuboot: Bootloader chainload address offset: 0xa000
[00:00:00.253,326] mcuboot: Jumping to the first image slot
***** Booting Zephyr OS v1.14.0-rc1-1238-g350f6f715632 *****
New version!
Bluetooth initialized
Advertising successfully started

Congratulation, you successfully performed your first over-the-air update!

In the next article, we will make updating a bit more comfortable with a Leshan server where we can perform an update with a few simple mouse clicks.