はじめに
マイコンでLチカができて、サーバとの通信も一通り実現できたけれど、意外と手が届かないのがOTA(Over the Air)。
今回は、30台程度の小規模ロットのIoT端末開発・運用を通じて得た、OTAの実装方法を紹介したいと思います。
※初投稿のため、至らない点があるかもしれませんが、よろしくお願いいたします。orz
前提条件
-
作りたいモノ
複数のIoT機器を監視・制御・メンテナンスする、よくある(?)構成を想定しています-
通常時:サーバとマイコンでいろいろ送受信(MQTT)したい
- (定期)マイコンからセンサデータをサーバに送信
- (オンライン)サーバからマイコンにつながる駆動部を制御
-
メンテナンス時(今回のメイン):OTAしてマイコンのアップデートがしたい
1.(定期)マイコンからファームウェアのバージョン確認
2.(1.でアップデートあれば)マイコンがサーバ上のファームウェアのダウンロードと更新
-
通常時:サーバとマイコンでいろいろ送受信(MQTT)したい
-
使用機材と用途
- クラウドサーバ:AWS
- IoT Core経由で通信(MQTT)
- ファームウェアのバージョン情報やファームウェアはS3に格納
- 通信機器:WiFiルータを使って、サーバとマイコンを中継
- マイコン: Raspberry Pi Pico WをArduino化して利用
- その他:各種センサおよび駆動部をマイコンと接続
- クラウドサーバ:AWS
OTAでやりたい要件
-
マイコン全台をまとめてOTAしたい
- 小ロットとはいえ、一台ずつOTAするのは非現実的
- OTAするファームウェアは全台共通だが、各マイコン固有の設定情報は保持したい
-
万が一に備え、個別アップデートも可能にしたい
- その際、個別の設定情報も書き換えられるようにしたい
-
OTA失敗時にはリトライやアラートが欲しい
- OTAが失敗すると現地対応が必要になり非常に辛い。可能な限りリトライして欲しい
解決した仕組み概要
-
マイコン上のファイル構成
- マイコンのメモリ内にFileSystem領域を用意し、マイコンの識別ID等の個体情報に書き込み、ファームウェア領域とは分離します
void setup() { mountFlashMemory();//FileSystemへのアクセス確認 #ifdef FILE_WRITE_ENABLED setupInitialConfig(deviceConfig);//deviceConfigを設定 saveConfigToFS(deviceConfig); // deviceConfigを書き込み #elif defined(FILE_WRITE_DISABLED) loadConfigFromFS(deviceConfig); //deviceConfigを読み込み #else #error "No FILE_WRITE selected! Please define FILE_WRITE setup." #endif setupMQTT();//通信関連の初期設定 checkForUpdates(ota_univ_version_url);//全体のアップデート確認 checkForUpdates(ota_device_version_url);//個別のアップデート確認 healthCheckPolling();//サーバへ諸々送信 } void loop(){ checkForUpdates(ota_univ_version_url);//全体のアップデート確認 checkForUpdates(ota_device_version_url);//個別のアップデート確認 healthCheckPolling();//サーバへ諸々送信 delay(10000);//定期的にOTAチェック }
- 個別のファームウェアを書き込む場合
#define FILE_WRITE_ENABLED //FileSystemへ以下を書き込み保存 inline void setupInitialConfig(DeviceConfigData &deviceConfig) { deviceConfig.deviceId = "device_dev_00001"; deviceConfig.deviceVersion = "ver1.0.0"; deviceConfig.wifiSSID = "hogehoge"; deviceConfig.wifiPassword = "piyopiyo"; }
- 全体のファームウェアを書き込む場合
#define FILE_WRITE_DISABLED ////FileSystemから読み込むのみ inline void setupInitialConfig(DeviceConfigData &deviceConfig) { deviceConfig.deviceId = ""; deviceConfig.deviceVersion = ""; deviceConfig.wifiSSID = ""; deviceConfig.wifiPassword = ""; }
- マイコンのメモリ内にFileSystem領域を用意し、マイコンの識別ID等の個体情報に書き込み、ファームウェア領域とは分離します
-
サーバ上のファームウェアとバージョン情報のファイル構成
- 個別書き込み用と全体書き込み用のファームウェアおよびバージョン情報を用意し、以下のようにS3に格納します
##ファイル構成 s3-bucket/ ├── firmware/ │ ├── device_dev_00001/ │ │ └── firmware.bin // device_dev_00001向けの個別ファームウェア │ ├── device_dev_00002/ │ │ └── firmware.bin // device_dev_00002向けの個別ファームウェア │ ├── device_dev_00003/ │ │ └── firmware.bin // device_dev_00003向けの個別ファームウェア │ ├── device_dev_00004/ │ │ └── firmware.bin // device_dev_00004向けの個別ファームウェア │ └── universal/ │ └── firmware.bin // 全体向けファームウェア └── version/ ├── device_dev_00001.txt // device_dev_00001向けの個別ver情報 ├── device_dev_00002.txt // device_dev_00002向けの個別ver情報 ├── device_dev_00003.txt // device_dev_00003向けの個別ver情報 ├── device_dev_00004.txt // device_dev_00004向けの個別ver情報 └── universal.txt // 全体向けver情報
- 個別書き込み用と全体書き込み用のファームウェアおよびバージョン情報を用意し、以下のようにS3に格納します
-
OTAの流れ
マイコンからの起動時や定期で下記を確認します- 個別用・全体用バージョン情報を取得
- マイコン自身のバージョンと比較
- 新しいverがなければ終了
- 新しいバージョンがあればファームウェアをダウンロード・書き込み
- ※全体向けファームウェアを書き込んでもFileSystem側に保存された個体情報は影響を受けません。
おわりに
一旦上記の設計で作りましたが、まだ改善点はあります。
特に現状の仕組みでは、個別ファームウェアを書き込む際のバージョン管理が煩雑になりがちです。
例えば、全体のファームウェアverが1.2.0
の時に個別のファームウェアでOTAしたい際には、個別のverを1.2.1
等に値を上げて書き込む必要があります。
次に全体のアップデートしたい場合、個別のverの最新が1.2.1
のために全体のファームウェアverが1.2.2
以上にする必要があります。
現状の3桁管理ではなく、個別の書き込み用に4桁管理へと増設をしておく方が良いかもしれません。
他にもS3のテキストでバージョン管理するよりはDBで管理した方が良いなぁとか、まだまだOTAは奥が深いです。
今回の記事が皆さんのIoT開発の参考になれば幸いです。