概要
アプリランチャーっぽいものを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で無料のサーバーが作れたので、時間が取れたらもう少し実験してみたいと思います。
コメント