概要
アプリランチャーっぽいものをSDカードのないM5StickCでできるか検討したところ、Web経由のOTAが楽そうと思い、実験してみました。
OTAとは?
Over The Airの略で、無線経由でなにかをアップデートすることをいいますが、無線以外の場合もOTAと表す場合があります。
ESP32の場合には、ESP32からファームウエアをSDカードやHTTP経由でダウンロードすることの他に、ESP32が待ち受けているポートやHTTPサーバーに対して、ファイルをアップロードする方法があります。
上記の記事でもまとめてあります。今回はAWS_S3_OTA_Updateをベースにして、アプリランチャーからアプリを選択して、HTTP経由でダウンロード。利用し終わったらアプリランチャーに戻る検証をしました。
作成物の説明
| スケッチ名 | 概要 |
| M5StickC-Update | 最初にWi-Fiの設定とメニューを読み込む |
| M5StickC-Update-Menu | 実際のアプリランチャー |
| M5StickC-Update-NTP | NTPを利用してRTCを設定するサンプルアプリ |
| M5StickC-Update-MovingIcons | LovyanGFXのサンプルアプリ |
最初にM5StickC-Updateを実行して、Wi-Fi設定をして実際にアプリランチャーをOTAしています。このアプリが動かないってことはWi-Fi設定がちゃんとできていないってことになります。
直接アプリランチャーを入れてもいいのですが、Wi-Fi設定ができていないと切り分けが難しいので、分離しました。アプリランチャーは単純にアプリ2を選択でき、OTAするだけの作りです。
サンプルアプリは既存のアプリにボタン押したらランチャーアプリにOTAで戻る仕組みを追加しています。
容量で左右すると思いますが、700キロバイトぐらいのファームウエアが15秒弱ぐらいでOTAできました。OTA実行中は画面が止まってしまうのでわかりにくいですが、別タスクなどでアニメーションを入れたほうがいいかもしれません。
書込アプリスケッチ
#include <M5StickC.h>
#include <WiFi.h>
#include <Update.h>
WiFiClient client;
void execOTA(String host, int port, String bin);
void setup() {
//WiFiに接続したことがない場合には接続してください
//WiFi.begin("SSID", "KEY");
String host = "lang-ship.com";
int port = 80;
String bin = "/tools/update-esp32/M5StickC-Update-Menu.ino.m5stick_c.bin";
execOTA(host, port, bin);
}
void loop() {
}
// Utility to extract header value from headers
String getHeaderValue(String header, String headerName) {
return header.substring(strlen(headerName.c_str()));
}
// OTA Logic
void execOTA(String host, int port, String bin) {
Serial.println("Connecting to Wi-fi");
// Connect to provided SSID and PSWD
WiFi.begin();
// Wait for connection to establish
while (WiFi.status() != WL_CONNECTED) {
Serial.print("."); // Keep the serial monitor lit!
delay(500);
}
// Connection Succeed
Serial.println("");
Serial.println("Connected to Wi-Fi");
long contentLength = 0;
bool isValidContentType = false;
Serial.println("Connecting to: " + String(host));
if (client.connect(host.c_str(), port)) {
Serial.println("Fetching Bin: " + String(bin));
client.print(String("GET ") + bin + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Cache-Control: no-cache\r\n" +
"Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
client.stop();
return;
}
}
while (client.available()) {
String line = client.readStringUntil('\n');
line.trim();
if (!line.length()) {
break;
}
if (line.startsWith("HTTP/1.1")) {
if (line.indexOf("200") < 0) {
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
break;
}
}
if (line.startsWith("Content-Length: ")) {
contentLength = atol((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
if (line.startsWith("Content-Type: ")) {
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/octet-stream") {
isValidContentType = true;
}
}
}
} else {
Serial.println("Connection to " + String(host) + " failed. Please check your setup");
}
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
if (contentLength && isValidContentType) {
bool canBegin = Update.begin(contentLength);
if (canBegin) {
Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
size_t written = Update.writeStream(client);
if (written == contentLength) {
Serial.println("Written : " + String(written) + " successfully");
} else {
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?" );
}
if (Update.end()) {
Serial.println("OTA done!");
if (Update.isFinished()) {
Serial.println("Update successfully completed. Rebooting.");
ESP.restart();
} else {
Serial.println("Update not finished? Something went wrong!");
}
} else {
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
}
} else {
Serial.println("Not enough space to begin OTA");
client.flush();
}
} else {
Serial.println("There was no content in the response");
client.flush();
}
}
ライブラリ化すればスッキリしますが、後半の関数はAWS_S3_OTA_Updateをそのまま持ってきています。
void setup() {
//WiFiに接続したことがない場合には接続してください
//WiFi.begin("SSID", "KEY");
ESP32のWi-Fi設定は最後に接続したものを保存していますので、すでにWi-Fi接続済みであれば上記の設定は必要ありません。実用化のためには複数AP情報を登録できたり、SmartConfigに対応したり、M5StickCから登録したり、APモードでブラウザから設定できたりしたほうがいいと思います。
String host = "lang-ship.com"; int port = 80; String bin = "/tools/update-esp32/M5StickC-Update-Menu.ino.m5stick_c.bin"; execOTA(host, port, bin);
ベタ書きで、ファームウエアを指定してOTAしています。execOTAの中身はAWS_S3_OTA_Updateのものを持ってきています。
実はこれだけでOTAできてしまいます。ArduinoJsonとかをつかって、サーバーと現在のバージョンやファームウエアのリストを解析して、更新があったらOTAなども実現可能です。
上記のライブラリとかは、バージョンチェックとOTAまでやってくれそうです。
このライブラリはもう一歩進んで、IoTプラットフォームとしてIOTAppStory.comというのがあるみたいです。とはいえ、Googleで検索しても、件数があまりでないからあまり利用されていないのかな?
まとめ
本当はMQTTなどを使って、端末とサーバーでメッセージのやりとりも含めたライブラリにすることで、サーバー上に新しいファームウエアをアップロードして、ポチッとボタン押すことで特定の端末にOTAすることなども可能になります。
MTQQ使わなくても、定期的にサーバーにチェックして、次はこのファームウエアに更新みたいな仕組みができたらいいなと思っています。
Oracle Cloud Free Tierで無料のサーバーが作れたので、時間が取れたらもう少し実験してみたいと思います。




コメント