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)... 184.108.40.206, 220.127.116.11, 18.104.22.168, ... Connecting to downloads.raspberrypi.org (downloads.raspberrypi.org)|22.214.171.124|: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.
ping should work:
pi@iot-fota-gw:~$ ping 126.96.36.199 PING 188.8.131.52 (184.108.40.206) 56(84) bytes of data. 64 bytes from 220.127.116.11: 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
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)
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
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.
- A Setup for Firmware Updates over the Air – Part 4 (Gateway) - 11. Februar 2020
- A Setup for Firmware Updates over the Air – Part 3 (Wireless Sensor Nodes: MCUboot) - 17. Oktober 2019
- A Setup for Firmware Updates over the Air – Part 2 (Wireless Sensor Nodes: Zephyr) - 7. August 2019