ThingsBoard / ESP32のFirmwareをOver-The-Airで更新する

2025/02/25に公開

Overview

IoT機器を開発するのに、FirmwareのOTA(遠隔での更新)は重要な機能のひとつです。ThingsBoardにもOTAの機能がありますのでこれを紹介します。後述する通り、Device側(本記事ではESP32)でOTAできるような実装も必要になります。ThingsBoardを使わずとももちろんOTAはできますので、ESP32の実装の参考にもなればと思います。

使うもの

  • Arduino
  • PlatformIO
  • ESP32

作るものの全体像

OTAの機能がある、といってもThingBoardが担う範囲は期待するより小さく、大別すると以下2つです。

  1. BuildしたFirmwareのBinaryを保管する
  2. Shared AttributesとしてDeviceのOTAのメタ情報を管理する

前者は単純にストレージとしての役割です。認証付きのストレージと大差ありません。
後者に関しては以下を見てください。こちらの記事でShared Attributeの扱い方について触れましたが、これとほぼ同じで、Shared AttributeでFirmwareのメタ情報(更新したFirmware名など)をDeviceと授受します。更新対象のFirmwareをDeviceに把握させ、そのBinaryをDeviceに渡すことでFirmwareを更新します。

本記事ではESP32からThingsBoardに対してHTTPリクエストを行うことでFirmwareのBinaryをダウンロードしてOTAするようなFirmwareを例示しますが、MQTTで受け取るなど方法は様々です。いずれにしてもESP32でFirmwareを受け取る仕組みを実装しなければならず、ThingsBoardさえあればうまいことOTAしてくれるものではありません。

Firmwareの実装

以下にFirmwareの例を書きました。
https://github.com/kazrin/thingsboard-firmwares/blob/main/src/OTA/ota.cpp

OTA前後の2つのFirmwareが必要なので、platformio.iniで [env:beforeOTA][env:afterOTA] の2つを用意しましたが、いずれも ota.cpp を参照しています。

重要な部分だけピックアップしてみます。

Attributeの変更の検知

ThingsBoardのMQTT Brokerに接続するようにした上で、callback関数を定義して、pubsubClient.setCallback(callback); に設定しています。これでThingsBoardでShared Attributesが変更をESP32が検知したら、callbackが実行されます。

callbackの中身

callbackでは、まずThingsBoardからのメッセージを受信します。ThingsBoardの設定完了後、実際にFirmwareを更新してみると以下のようなメッセージを受け取ります。

{"fw_title":"TEST","fw_version":"1.1.0","fw_tag":"TEST 1.1.0","fw_size":818512,"fw_checksum_algorithm":"SHA256","fw_checksum":"68c371163883760963fc1d8ffba80d3bcccaaf57dbb1b363b4eeab40fff3a99c"}

とりあえずはDeviceのAccess Tokenに加えて、Firmwareのタイトルとバージョン(それぞれ fw_titlefw_version)さえあれば、Firmware APIのendpointにリクエストし、Firmwareをダウンロードすることができます。

HTTPUpdateを使ってOTAする

更新する対象のFirmwareのURLがわかっている場合には、HTTPUpdate を使うのが便利です。以下のような記述のみでOTAできるようになります。

#include <WiFi.h>
#include <HTTPUpdate.h>
WiFiClient OTAclient;
httpUpdate.update(OTAclient, char *<FirmwareのURL>);

メッセージ受け取る -> FirmwareのTitleとVersionを取得し、URLを構成する -> HTTPUpdateを実行する、これだけでOTAできてしまいます。

Firmwareを書き込む

実装できたらESP32にOTA前のFirmware( env:beforeOTA)を書き込みます。簡単のためDevice Profile: defaultに紐づけたDeviceのAccess Tokenを設定して書き込みます。

補足

ota.cpp中に以下のような記述をしました。SETUP_MODE が定義されていれば、Preferenceを使ってWiFiのSSID、パスワード、ThingsBoardのAccess Tokenをフラッシュメモリに書き込み、それ以降はフラッシュメモリの値を読み込んで使うようにしています。
https://github.com/kazrin/thingsboard-firmwares/blob/21640d69f875899a136e7a28660e9478c4e08757/src/OTA/ota.cpp#L154-L167

SETUP_MODEenv:beforeOTAでのみ定義されていて、env:afterOTAでは定義していません。初回のFirmwareの書き込みでは、WiFiやAccess TokenなどDevice個別の情報が必要になりますが、env:afterOTAはDeviceに帰属する情報は入れずに汎用的に使えるFirmwareにすべきなのでこのような実装にしています。

FirmwareのBinaryを取得する

このあとThingsBoardの設定に移りますが、FirmwareのソースコードではなくBinaryをアップロードする必要があります。この記事ではPlatformIOを使うので、PlatformIOの使い方の解説になってしまいますがご了承ください。

env:afterOTAをBuildします。GUI使ってもいいですし、CLIだと platformio.ini が存在するディクトリで以下のコマンドを実行することになります。

$ pio run -e afterOTA

その後、同じディレクトリで以下のコマンドを実行します。buildは以下はbuildコマンドを実行すると生成されます。この中に firmware.bin というファイルがありますが、これがFirmwareのBinaryです。これをこのあとThingsBoardにアップロードします。

$ ls -l .pio/build/afterOTA/ 
total 59584
drwxr-xr-x@ 91 kazrin  staff      2912  2 25 17:11 FrameworkArduino
-rw-r--r--@  1 kazrin  staff     17536  2 25 17:11 bootloader.bin
-rw-r--r--@  1 kazrin  staff    818704  2 25 17:11 firmware.bin
-rwxr-xr-x@  1 kazrin  staff  13224344  2 25 17:11 firmware.elf
-rw-r--r--@  1 kazrin  staff  11831438  2 25 17:11 firmware.map
drwxr-xr-x@  4 kazrin  staff       128  2 25 17:11 lib04a
drwxr-xr-x@  4 kazrin  staff       128  2 25 17:11 lib239
drwxr-xr-x@  4 kazrin  staff       128  2 25 17:11 lib6a3
-rw-r--r--@  1 kazrin  staff   3299502  2 25 17:11 libFrameworkArduino.a
drwxr-xr-x@  4 kazrin  staff       128  2 25 17:11 libd2e
drwxr-xr-x@  4 kazrin  staff       128  2 25 17:11 libdc4
drwxr-xr-x@  4 kazrin  staff       128  2 25 17:11 libf0b
drwxr-xr-x@  4 kazrin  staff       128  2 25 17:11 libfe5
-rw-r--r--@  1 kazrin  staff      3072  2 25 17:11 partitions.bin
drwxr-xr-x@  7 kazrin  staff       224  2 25 17:11 src

ThingsBoardの設定

Firmwareのアップロード

ThingsBoardの設定に移ります。以下の画面からFirmwareをアップロードすることができます。
Advanced features > OTA updates から「+」をクリックです。

以下を設定した上でAddでFirmwareをアップロードします。

  • Title: TESTを入力
  • Version: 1.1.0 を入力。1.0.0以外ならなんでも可。
  • Device profile: default
  • Package type: Firmware
  • Upload binary fileを選択して、さきほど取得したfirmware.binをアップロード

Firmwareの実装で、TitleがTESTで、Versionが1.0.0ではない場合のみOTAを実行するように記述しました。ref
そのため、ここではTitleに TEST、Versionに1.0.0以外を指定する必要があります。

Firmwareを更新

それではFirmwareの更新を行ってみます。さきほど実装したFirmwareがESP32で動作していて、かつインターネットに接続していることを確認した上で行います。Serial Monitorで出力を見ながらの方がわかりやすいです。

ThingsBoardの対象Deviceを開き、"Assigned firmware"にアップロードしたFirmwareを指定します。

すると、Serial Monitorに以下のような出力が見られます。

21:17:29:093 -> Message arrived [v1/devices/me/attributes] {"fw_title":"TEST","fw_version":"1.1.0","fw_tag":"TEST 1.1.0","fw_size":818688,"fw_checksum_algorithm":"SHA256","fw_checksum":"fb0c844fe39f714c3c1ab2444e87a4d00adf45ea6ba09729d3e225b422544d4f"}
21:17:29:112 -> Update firmware
21:17:31:057 -> Callback: OTA process started
21:17:31:160 -> Callback: OTA process at 0 of 818688 bytes (0.0%)
21:17:31:174 -> Callback: OTA process at 0 of 818688 bytes (0.0%)
21:17:31:459 -> Callback: OTA process at 4096 of 818688 bytes (0.5%)
(中略)
21:18:19:108 -> Callback: OTA process at 818688 of 818688 bytes (100.0%)
21:18:19:111 -> Callback: OTA process at 818688 of 818688 bytes (100.0%)
21:18:19:383 -> Callback: OTA process finished
21:18:19:443 -> ets Jul 29 2019 12:21:46
21:18:19:443 -> 
21:18:19:443 -> rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
21:18:19:448 -> configsip: 0, SPIWP:0xee
21:18:19:451 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
21:18:19:457 -> mode:DIO, clock div:2
21:18:19:459 -> load:0x3fff0030,len:1184
21:18:19:463 -> load:0x40078000,len:13232
21:18:19:465 -> load:0x40080400,len:3028
21:18:19:465 -> entry 0x400805e4

重要な部分をピックアップすると以下の流れで、無事OTAが完了しました。

  • 21:17:29:093 -> Shared Attributeの更新を検知
  • 21:17:29:112 -> Firmwareのダウンロード開始
  • 21:18:19:383 -> ダウンロード完了
  • 21:18:19:443 -> 再起動

補足

より実践的なFirmwareの実装

本記事で例示したFirmwareのOTAは「ESP32がインターネットに接続している間に、ThingsBoardでAssignするFirmwareを更新」しなければならない実装になっています。しかし、ESP32がいつでもインターネットに接続しているとは限らないので、「起動時に1度だけ」もしくは「定期的に」Attributeを確認して、今のFirmwareとAttributeに設定されているFirmwareのバージョンに違いがないかを検証した上でOTAを実行する、というのが実践的なFirmwareの実装になると思います。

Device ProfileにFirmwareをAssignする

上の手順ではDeviceにFirmwareをアサインしましたが、Device ProfileにAssignすることで、そのDevice Profileに属するすべてのFirmwareを対象に一括でShared Attributeを更新することもできます。とすると、なおさらすべてのDeviceがインターネットに接続しているとは限らないので、先述のような実装が好ましいです。

まとめ

ThingsBoardを使ったFirmwareのOTAについて書きました。とりあえずOTAを試す目的で不要な箇所は削ぎ落としたFirmwareになっているのでかなりシンプルですが、OTAが失敗して装置が動かなくなるリスクをはらんでいるので、エラーハンドリングなど考慮すべき事項は多々あると思います。OTA難しい。

Discussion