ESP32でWebサーバーを利用したOTA(Over the Air)アップデートをテストする

Arduino/ESP32Arduino,ESP32,https,OTA,PlatformIO

ちょっと忙しくなってしまい更新が途絶えてしまいましたが、、

ESP32のスケッチをオンラインでアップデートしたいという要件が出てきましたのでちょっと検討。

今回のターゲットはESP32はArduino core for the ESP32で動かしているので、ESP-IDFではなくArduinoで用意します。

普通のOTAはESP32がwifiのAPとなってOTAの流し込みを受け付ける様ですが、お手軽ではありますがこの方法だと少々セキュリティに難があり。

認証もアイパスだけではちょっと・・・

という訳で、どこかにプログラムの配布サーバーを作り、コンパイル済のバイナリをESP32にダウンロードさせて書き込もうという感じです。

とりあえず方針を決める

  • ESP32側にプログラムを直接送り込むのはセキュリティ的にNGなので管理しているサーバーからダウンロードさせて書き込む様にする
  • ESP32はwifi経由でインターネットに接続
  • どこかにWebサーバーを作り、コンパイル済バイナリをダウンロードできる様にしておく

ダウンロードさせるサーバーアドレスはFQDNでいいのかとか、OTAに必要なパーティションタイプとか色々クリアしなければいけない課題もありそうですが、、、

ひとまずEspressifのgitを検索してみると、まんま使えそうなexamplesを発見。

GitHub:espressif / arduino-esp32 : OTA Firmware Upgrade for Arduino

どうやら「HttpsOTAUpdate」というライブラリで比較的簡単にできそうですね。

わりと新しめなのか、SSL対応という優れもの。

ひとまずサンプルをもとにやってみる事に。

パーティションテーブルの変更

OTAを使うにはプログラム領域が二つ無くてはいけません。

また、プログラムもOTA処理分だけ余計に乗せなくてはいけませんので、デフォルトでは少々容量が足りなくなる恐れがあります。

このためパーティションテーブルを変更して、ちょっとプログラム領域のサイズを増やしておきます。

VSCode/PlatformIOを使って作業を行いますので、「platformio.ini」に以下の設定を追加。

[env:nodemcu-32s-ota]
platform = espressif32
board = nodemcu-32s
framework = arduino
monitor_speed = 115200
board_build.partitions = min_spiffs.csv		// ←パーティションテーブルの設定ファイルを指定

「min_spiffs.csv」は、以下の様な構成になっている様でした。

NameTypeSubTypeOffsetSize
nvsdatanvs0x90000x5000
otadatadataota0xe0000x2000
app0appota_00x100000x1E0000
app1appota_10x1F00000x1E0000
spiffsdataspiffs0x3D00000x30000
min_spiffs.csv

GitHub:espressif/arduino-esp32/tools/partitions/min_spiffs.csv

デフォルトは二つあるアプリケーション領域のサイズがそれぞれ1.5MB程度でしたが、spiffs領域を小さくしたこのプロファイルは1.9MBほどのアプリケーション領域が確保できる様です。

この様に、必要があればアプリケーションのサイズや用途に合わせて調整して下さい。

さて、では次にテストスケッチを用意します。

先にESP32に書き込んでおくスケッチ

テストですしサンプルをガッツリ使わせていただきます。

このサンプルはマイコンが起動すると勝手にAPに接続しにいって、接続できたら目的のファイルをダウンロードし、そのままマイコンに書き込む動作をする様ですね。

URLは設置する予定のURLを。

今回使うSSL証明書はLet’sEncryptのものですので、CA認証局の証明書の箇所にはLet’sEncryptのCA証明書を記載します。

#include <Arduino.h>

// This sketch provide the functionality of OTA Firmware Upgrade
#include "WiFi.h"
#include "HttpsOTAUpdate.h"
// This sketch shows how to implement HTTPS firmware update Over The Air.
// Please provide your WiFi credentials, https URL to the firmware image and the server certificate.

static const char *ssid     = "your-ssid";  // your network SSID (name of wifi network)
static const char *password = "your-password"; // your network password

static const char *url = "https://labo.mycabin.net/wp-content/uploads/2021/03/esp32_ota_update_test.bin"; //state url of your firmware image

static const char *server_certificate = "-----BEGIN CERTIFICATE-----\n" \
	"MIIEZTCCA02gAwIBAgIQQAF1BIMUpMghjISpDBbN3zANBgkqhkiG9w0BAQsFADA/\n" \
	"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \
	"DkRTVCBSb290IENBIFgzMB4XDTIwMTAwNzE5MjE0MFoXDTIxMDkyOTE5MjE0MFow\n" \
	"MjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxCzAJBgNVBAMT\n" \
	"AlIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuwIVKMz2oJTTDxLs\n" \
	"jVWSw/iC8ZmmekKIp10mqrUrucVMsa+Oa/l1yKPXD0eUFFU1V4yeqKI5GfWCPEKp\n" \
	"Tm71O8Mu243AsFzzWTjn7c9p8FoLG77AlCQlh/o3cbMT5xys4Zvv2+Q7RVJFlqnB\n" \
	"U840yFLuta7tj95gcOKlVKu2bQ6XpUA0ayvTvGbrZjR8+muLj1cpmfgwF126cm/7\n" \
	"gcWt0oZYPRfH5wm78Sv3htzB2nFd1EbjzK0lwYi8YGd1ZrPxGPeiXOZT/zqItkel\n" \
	"/xMY6pgJdz+dU/nPAeX1pnAXFK9jpP+Zs5Od3FOnBv5IhR2haa4ldbsTzFID9e1R\n" \
	"oYvbFQIDAQABo4IBaDCCAWQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E\n" \
	"BAMCAYYwSwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5p\n" \
	"ZGVudHJ1c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTE\n" \
	"p7Gkeyxx+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEE\n" \
	"AYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2Vu\n" \
	"Y3J5cHQub3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0\n" \
	"LmNvbS9EU1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYf\n" \
	"r52LFMLGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0B\n" \
	"AQsFAAOCAQEA2UzgyfWEiDcx27sT4rP8i2tiEmxYt0l+PAK3qB8oYevO4C5z70kH\n" \
	"ejWEHx2taPDY/laBL21/WKZuNTYQHHPD5b1tXgHXbnL7KqC401dk5VvCadTQsvd8\n" \
	"S8MXjohyc9z9/G2948kLjmE6Flh9dDYrVYA9x2O+hEPGOaEOa1eePynBgPayvUfL\n" \
	"qjBstzLhWVQLGAkXXmNs+5ZnPBxzDJOLxhF2JIbeQAcH5H0tZrUlo5ZYyOqA7s9p\n" \
	"O5b85o3AM/OJ+CktFBQtfvBhcJVd9wvlwPsk+uyOy2HI7mNxKKgsBTt375teA2Tw\n" \
	"UdHkhVNcsAKX1H7GNNLOEADksd86wuoXvg==\n" \
	"-----END CERTIFICATE-----";

static HttpsOTAStatus_t otastatus;

void HttpEvent(HttpEvent_t *event)
{
    switch(event->event_id) {
        case HTTP_EVENT_ERROR:
            Serial.println("Http Event Error");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            Serial.println("Http Event On Connected");
            break;
        case HTTP_EVENT_HEADER_SENT:
            Serial.println("Http Event Header Sent");
            break;
        case HTTP_EVENT_ON_HEADER:
            Serial.printf("Http Event On Header, key=%s, value=%s\n", event->header_key, event->header_value);
            break;
        case HTTP_EVENT_ON_DATA:
            break;
        case HTTP_EVENT_ON_FINISH:
            Serial.println("Http Event On Finish");
            break;
        case HTTP_EVENT_DISCONNECTED:
            Serial.println("Http Event Disconnected");
            break;
    }
}

void setup(){

    Serial.begin(115200);
    Serial.print("Attempting to connect to SSID: ");
    WiFi.begin(ssid, password);

    // attempt to connect to Wifi network:
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(1000);
    }

    Serial.print("Connected to ");
    Serial.println(ssid);

    HttpsOTA.onHttpEvent(HttpEvent);
    Serial.println("Starting OTA");
	// 中間CAが必要 Let'sEncryptの中間CAを設定
    HttpsOTA.begin(url, server_certificate); 

    Serial.println("Please Wait it takes some time ...");
   
}

void loop(){

    otastatus = HttpsOTA.status();
    if(otastatus == HTTPS_OTA_SUCCESS) { 
        Serial.println("Firmware written successfully. To reboot device, call API ESP.restart() or PUSH restart button on device");
    } else if(otastatus == HTTPS_OTA_FAIL) { 
        Serial.println("Firmware Upgrade Fail");
    }
    delay(1000);
}

まぁこれでいいでしょう。

ssidとpasswordの箇所は、ご自宅のwifiのAPに接続できる様に設定して下さい。

ササっとこれをESP32に流し込みまして・・・

ダウンロードして反映させるスケッチ

今度は用意するWebサーバーに設置しておく新しいプログラムを作成します。

処理内容は、前述のスケッチが動作し、ダウンロードして正しく反映されたらシリアルモニタにSuccessed.って出させる事に。

#include <Arduino.h>

// This sketch provide the functionality of OTA Firmware Upgrade
#include "WiFi.h"
#include "HttpsOTAUpdate.h"
// This sketch shows how to implement HTTPS firmware update Over The Air.
// Please provide your WiFi credentials, https URL to the firmware image and the server certificate.

static const char *ssid     = "your-ssid";  // your network SSID (name of wifi network)
static const char *password = "your-password"; // your network password

static const char *url = "https://labo.mycabin.net/wp-content/uploads/2021/03/esp32_ota_update_test.bin"; //state url of your firmware image

static HttpsOTAStatus_t otastatus;

void HttpEvent(HttpEvent_t *event)
{
    switch(event->event_id) {
        case HTTP_EVENT_ERROR:
            Serial.println("Http Event Error");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            Serial.println("Http Event On Connected");
            break;
        case HTTP_EVENT_HEADER_SENT:
            Serial.println("Http Event Header Sent");
            break;
        case HTTP_EVENT_ON_HEADER:
            Serial.printf("Http Event On Header, key=%s, value=%s\n", event->header_key, event->header_value);
            break;
        case HTTP_EVENT_ON_DATA:
            break;
        case HTTP_EVENT_ON_FINISH:
            Serial.println("Http Event On Finish");
            break;
        case HTTP_EVENT_DISCONNECTED:
            Serial.println("Http Event Disconnected");
            break;
    }
}

void setup(){

    Serial.begin(115200);
    Serial.print("Attempting to connect to SSID: ");
    WiFi.begin(ssid, password);

    // attempt to connect to Wifi network:
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(1000);
    }

    Serial.print("Connected to ");
    Serial.println(ssid);
}

void loop(){
    Serial.println("Firmware Upgrade Successed.");
    delay(1000);
}

wifiとかも起動しなくても良いのですが、面倒ですのでこれでいいでしょう。

これをコンパイルだけして、ESP32には転送しません。

VSCode/PlatformIOで作ったバイナリは、プロジェクトのルート以下の.pioフォルダの下の方に作成されます。

.pio\build\nodemcu-32s-ota\firmware.bin

この「firmware.bin」を「esp32_ota_update_test.bin」にリネームして取っておきます。

Webサーバーを立ててバイナリイメージを置いておく

わざわざ新しくWebサーバーを立てるのも面倒ですので、このブログのサーバーに置いてしまいます。

WordPressを動かしていますがまぁいいでしょう。
ダウンロードできる様にしておけばいいだけですので・・・

SSL証明書も乗ってますのでファイルアップロードするだけで手間も省けて都合が良いかも。

さてダウンロードさせるスケッチですので、前述の「ダウンロードして反映させるスケッチ」をコンパイルして出来たバイナリをサーバーに設置しておきます。

WordPressで.binファイルとかアップロードできませんので、直接アップロードしてしまうま。

念のため、ブラウザでアクセスしてダウンロードできる状態かどうかを確認してください。

検証

どうやら正しくファームウェアが書き込まれた様です。

「Firmware written successfully. To reboot device, call API ESP.restart() or PUSH restart button on device」の文字がシリアルモニタに出力されました。

ではご指示の通り、ESP32をリセットして再起動してみます。

シリアルモニタに「Firmware Upgrade Successed.」の出力があるので、書き換え後のファームになっている事が確認できます。

わりと簡単ではありますが、ちょっと周辺ライブラリのサイズが大きいですね。

これだけの内容で600kbちょっとの容量がありますので、OTAを使う場合にはパーティションサイズの調整は必須かもしれません。

ひとまずはこんな感じで。

あとはこのサンプルをちょこっと書き足してアイツに追加してやるんですが、、、

完全リモートで操作する事を想定していますので、ファームアップデート後の自動リセットなんかも書いておいた方が良さそうですね。

恒久的にアップデートをさせる予定なら、SSLのCA証明書の更新も盛り込んでやらないと何年かしたら使えなくなってしまうま。

こういう周辺のケアは案外めんどくさいですぬぇ

以上、サンプルを使った簡単なテストでした。