ガイド · 読了まで18分

ESP32のOTAアップデート:ArduinoOTAでコードを無線で書き込む

ESP32のOTAを使えば、USBケーブルなしでWi-Fi経由でコードを書き込めます。ArduinoOTAのネットワークポート、ElegantOTAのWebページ、パスワード、パーティション構成、途中で切れた書き込みまで解説します。

ESP32のOTA(Over-The-Air、無線アップデート)とは、USBケーブルをつながずに、Wi-Fi経由でボードに新しいコードを書き込むことです。天井に取り付けたセンサー、庭の水やりコントローラー、分電盤の中に収めたリレーボードを想像してください。小さな修正のたびにはしごを出し、ボードを取り外してパソコンにつなぐ代わりに、Arduino IDEやブラウザから数秒で新しいバージョンを送れます。ボードは新しいプログラムをバックグラウンドでメモリに書き込み、検証してから自分で再起動します。

このガイドでは、ESP32の内部でOTAがどう動くか、3つの方法(Arduino IDE用のArduinoOTA、ブラウザ用のElegantOTA、サーバーから取得するHTTPUpdate)、正しいパーティション構成の選び方、パスワードによる書き込みの保護、書き込みが途中で切れたらどうなるか、そして次のスケッチでOTAを失わない方法をステップごとに説明します。すべてのコードはESP32 Arduinoコアでコンパイルし、動作を確認しています。

OTAで無線でコードを書き込むESP32開発ボード
ESP32開発ボード:最初のOTA対応スケッチを一度USBで書き込めば、その後のアップデートはすべてWi-Fi経由で送れます。

ESP32のOTAとは?どのように動くのか

ESP32のフラッシュメモリは1つの大きな塊ではなく、パーティションテーブルという地図によっていくつかに分けられています。OTAに対応したパーティションテーブルには、プログラム用に同じ大きさの領域が2つあります。app0(ota_0)とapp1(ota_1)です。ボードが片方で動いている間に、新しいプログラムはもう片方に書き込まれます。小さなotadataパーティションには、次の起動時にどちらの領域を実行するかが記録されます。そのため書き込み中に動作中のプログラムには一切触れず、新しいコードがすべて書き込まれて検証されるまで、ボードは古いプログラムを手放しません。

  1. 新しいプログラム(コンパイルされた.binファイル)が、Wi-Fi経由で少しずつボードに届きます。
  2. ESP32は、届いた部分をその時点で使われていないプログラム用パーティションに書き込みます。その間も動作中のコードは自分の仕事を続けます。
  3. ファイル全体が届くと、イメージのヘッダーとチェックサムが確認されます。壊れたファイルや不完全なファイルは受け付けられません。
  4. 検証に成功するとotadataが更新され、次の起動時には新しいパーティションが実行されます。
  5. ボードが再起動し、新しいプログラムで立ち上がります。次のアップデートはもう一方のパーティションに書き込まれ、2つの領域が交互に使われます。
方法コードをどこから送るか向いている場面追加のライブラリ
ArduinoOTAArduino IDEのネットワークポートから同じネットワーク上で、机で開発しているとき不要(コアに付属)
ElegantOTAブラウザの/updateページで.binを選んでIDEを持っていない人に更新してもらうとき、スマホからの書き込みElegantOTA
HTTPUpdateボードがサーバーから.binファイルを自分でダウンロード多数のデバイス、離れた場所のデバイス、自動アップデート不要(コアに付属)

パーティション構成とフラッシュサイズ

これはOTAでいちばん見落とされがちな条件です。新しいプログラムを書き込むための2つ目の空き領域が必要です。そのため、OTAで書き込めるプログラムの最大サイズは、フラッシュの半分よりも小さくなります。パーティション構成はArduino IDEの「ツール」>「Partition Scheme」で選びます。4 MBフラッシュを搭載した一般的なESP32 DevKitボード(ESP32 Dev Module)では、次の選択肢があります。

Partition Schemeプログラム領域OTAファイルシステム(SPIFFS/LittleFS)
Default 4MB with spiffs2 × 1.25 MB(1,310,720バイト)あり1.375 MB
Minimal SPIFFS (1.9MB APP with OTA)2 × 1.875 MB(1,966,080バイト)あり128 KB
No OTA (2MB APP/2MB SPIFFS)1 × 2 MBなし1.875 MB
Huge APP (3MB No OTA/1MB SPIFFS)1 × 3 MBなし896 KB
Minimal (1.3MB APP/700KB SPIFFS)1 × 1.25 MB(2 MBフラッシュのボード用)なし640 KB
BluetoothとWi-Fiを両方使う大きなプログラムで「Sketch too big」(スケッチが大きすぎる)と表示されると、多くの人がHuge APPを選びます。この構成には2つ目のプログラム領域がありません。USBでは問題なく書き込めますが、OTAは始まらず、「Not Enough Space」(空き容量不足)や「Begin Failed」(開始失敗)で失敗します。OTAを使いたいならMinimal SPIFFS (1.9MB APP with OTA)を選んでください。

プログラムがどれだけの容量を使っているかは、コンパイルの出力で確認できます。Wi-FiとArduinoOTAを含む最初の例では「Sketch uses 758685 bytes (57%) ... Maximum is 1310720 bytes」(758685バイト(57%)を使用、最大は1310720バイト)と表示されます。この最大値は、選んだ構成のプログラム領域1つ分のサイズです。使用率が90%を超えたら、近いうちにMinimal SPIFFSへの切り替えが必要になると考えてください。もう1つ大事なルールがあります。パーティションテーブルはOTAでは変更できません。構成を変えたときの書き込みはUSBで行う必要があり、その後のOTAは新しいテーブルで動きます。8 MBや16 MBのフラッシュを搭載したボード(多くのESP32-S3モデルなど)では、「Flash Size」メニューで正しいサイズを選ぶと、はるかに大きなOTA領域を使えます。

ボードが実際にどのパーティションで動いていて、OTA用にどれだけの空きがあるかを知りたいときは、次の小さなスケッチを書き込んでください。ライブラリは不要で、ESP-IDFのOTA関数を直接使います。

C++ (Arduino IDE)
1#include <esp_ota_ops.h>
2
3void setup() {
4  Serial.begin(115200);
5  delay(1000);
6  const esp_partition_t* running = esp_ota_get_running_partition();
7  const esp_partition_t* next = esp_ota_get_next_update_partition(NULL);
8
9  Serial.printf("Flash boyutu / Flash size: %u KB\n", ESP.getFlashChipSize() / 1024);
10  Serial.printf("Program boyutu / Sketch size: %u KB\n", ESP.getSketchSize() / 1024);
11  Serial.printf("OTA icin bos alan / Free space for OTA: %u KB\n", ESP.getFreeSketchSpace() / 1024);
12  Serial.printf("Calisan bolum / Running partition: %s (0x%06x)\n", running->label, running->address);
13  if (next != NULL) {
14    Serial.printf("Siradaki OTA bolumu / Next OTA partition: %s (0x%06x)\n", next->label, next->address);
15  } else {
16    Serial.println("OTA bolumu yok: bolum tablosunu degistir / No OTA partition: change the partition scheme");
17  }
18}
19
20void loop() {
21}

デフォルトの構成では、最初にUSBで書き込んだ後の出力は「Running partition: app0 (0x010000)」と「Next OTA partition: app1 (0x150000)」になります。OTAでアップデートした後にもう一度実行すると、今度はapp1で動いていることがわかります。ESP.getFreeSketchSpace()の値は、OTAで書き込めるプログラムの最大サイズです。新しい.binファイルがこれより大きいと、アップデートは始まりません。

方法1:ArduinoOTAでArduino IDEから無線で書き込む

ArduinoOTAライブラリはESP32コアに付属しているので、インストールは不要です。ボードはネットワークに接続するとmDNSで自分の存在を知らせ、Arduino IDEがそれを見つけて「ポート」メニューにネットワークポートを追加します。「書き込み」を押すと、IDEはUSBの代わりにこのネットワークポートからプログラムを送ります。ESP32では、OTAサービスはデフォルトで3232番ポートで待ち受けます。次のスケッチはWi-Fiに接続し、パスワード付きでOTAを開始し、動作の証拠として内蔵LEDを点滅させます。

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <ArduinoOTA.h>
3
4const char* WIFI_SSID = "AgAdi";          // Wi-Fi adın / your Wi-Fi name
5const char* WIFI_PASS = "AgSifresi";      // Wi-Fi şifren / your Wi-Fi password
6const int LED_PIN = 2;                    // çoğu DevKit'te dahili LED / onboard LED on most DevKits
7
8unsigned long lastBlink = 0;
9
10void setup() {
11  Serial.begin(115200);
12  pinMode(LED_PIN, OUTPUT);
13
14  WiFi.mode(WIFI_STA);
15  WiFi.begin(WIFI_SSID, WIFI_PASS);
16  while (WiFi.status() != WL_CONNECTED) {   // bağlanana kadar bekle / wait until connected
17    delay(500);
18    Serial.print(".");
19  }
20  Serial.println();
21  Serial.print("IP: ");
22  Serial.println(WiFi.localIP());
23
24  ArduinoOTA.setHostname("bluegrays-esp32");  // IDE'de görünen ad / name shown in the IDE
25  ArduinoOTA.setPassword("ota1234");          // yükleme parolası / upload password
26
27  ArduinoOTA.onStart([]() {
28    // Güncelleme başlıyor: motorları, röleleri güvenli konuma al / update starting: put motors and relays in a safe state
29    String type = (ArduinoOTA.getCommand() == U_FLASH) ? "program" : "dosya sistemi / filesystem";
30    Serial.println("OTA basladi / started: " + type);
31  });
32  ArduinoOTA.onEnd([]() {
33    Serial.println("\nOTA bitti, yeniden baslatiliyor / done, rebooting");
34  });
35  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
36    Serial.printf("Ilerleme / Progress: %u%%\r", progress * 100 / total);
37  });
38  ArduinoOTA.onError([](ota_error_t error) {
39    Serial.printf("Hata / Error[%u]: ", error);
40    if (error == OTA_AUTH_ERROR) Serial.println("parola / auth failed");
41    else if (error == OTA_BEGIN_ERROR) Serial.println("baslatilamadi, yer yok / begin failed, no space");
42    else if (error == OTA_CONNECT_ERROR) Serial.println("baglanti / connect failed");
43    else if (error == OTA_RECEIVE_ERROR) Serial.println("veri alinamadi / receive failed");
44    else if (error == OTA_END_ERROR) Serial.println("bitirilemedi / end failed");
45  });
46
47  ArduinoOTA.begin();                         // OTA hizmetini başlat / start the OTA service
48}
49
50void loop() {
51  ArduinoOTA.handle();                        // her turda çağır, unutma / call on every pass, never forget
52
53  // delay() yerine millis(): OTA isteklerini kaçırmamak için / millis() instead of delay() so OTA requests aren't missed
54  if (millis() - lastBlink >= 500) {
55    lastBlink = millis();
56    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
57  }
58}
行何をするか
ArduinoOTA.setHostname("bluegrays-esp32")(ホスト名の設定)IDEの「ポート」一覧とネットワーク上に表示される名前です。ボードが複数あるなら、それぞれに別の名前を付けます。
ArduinoOTA.setPassword("ota1234")(パスワードの設定)パスワードがないと、ネットワーク上の誰でもボードにコードを書き込めてしまいます。IDEは書き込みの前にこのパスワードを求めます。
onStart / onEnd(開始時・終了時)アップデートの開始時と終了時に実行されます。モーターを止める、リレーをオフにする、ディスプレイに「更新中」と表示するのに最適な場所です。
onProgress(進捗)進捗率を受け取れます。LCDやOLEDに進行状況バーを描けます。
onError(エラー)エラーの種類を教えてくれます。パスワード、空き容量不足、接続、受信、終了のエラーです。
ArduinoOTA.handle()(要求の確認)届いたOTAの要求を確認します。loop()の中で頻繁に呼ばれないと、ボードはIDEに応答できません。

ステップごとに:初めてのOTA書き込み

  1. コードのWIFI_SSIDとWIFI_PASSの値を、自分のネットワークに合わせて書き換えます。ESP32は2.4 GHzのネットワークにしか接続できません。
  2. 「ツール」メニューでボード(例:ESP32 Dev Module)と、OTAを含むPartition Scheme(DefaultまたはMinimal SPIFFS)を選びます。
  3. 最初の書き込みはUSBケーブルで行います。ボードにはまだOTAのコードがないので、このステップは必須です。
  4. シリアルモニタを115200ボーで開き、ボードが表示するIPアドレスをメモします。
  5. USBケーブルを抜き、必要なら別の電源につなぎます。10〜20秒ほどで、「ツール」>「ポート」のネットワークポートの下に「bluegrays-esp32 at 192.168.1.x」のような行が表示されるので、それを選びます。
  6. コードのLEDの待ち時間を500から100に変えて「書き込み」を押します。IDEがパスワードを求めたら「ota1234」と入力します。
  7. 出力ウィンドウに進捗率が表示され、最後に「Done」や「Result: OK」のような行が出ます。ボードが再起動し、LEDが速く点滅するようになります。
ネットワークポートが表示されない場合は、まずパソコンとESP32が同じネットワークにあるか確認してください。ゲストネットワークや一部のメッシュWi-Fiでは、デバイス同士が互いを見つけられません。IDEを再起動する、ボードを再起動する、または数秒待つだけで直ることがほとんどです。MacとLinuxでは、IDEが書き込みにespota.pyスクリプトを使うため、パソコンにPython 3がインストールされている必要があります。

知っておきたい制限が1つあります。ネットワークポートは書き込み専用で、シリアルモニタは無線では使えません。ボードのメッセージを見るには、USBをつなぎ直すか、状態をWebページやディスプレイ、MQTTのメッセージで表示します。机で開発している間は、ボードをUSBにつないだまま、書き込みだけをネットワークで行うこともできます。そうすれば、速い書き込みとシリアルモニタを同時に使えます。

パスワードによる保護とセキュリティ

OTAサービスが有効なボードは、ネットワーク上の誰もがコードを書き込めるドアのようなものです。ArduinoOTAのsetPassword()は、パスワードを平文のままコードの中に保持します。コードを共有するつもりなら、setPasswordHash()でパスワードのMD5ハッシュを渡せば、パスワードそのものはソースコードに現れません。ElegantOTAは、/updateページをユーザー名とパスワード(HTTPのBasic認証)で保護します。

  • デバイスごとに、推測されにくい別々のパスワードを付けます:「1234」や「admin」のような値は、真っ先に試されます。
  • ボードをインターネットに公開しないでください:ルーターのポート転送でOTAや/updateページを外部に公開するのは重大なリスクです。これらの方法は暗号化されていないHTTPで動きます。
  • 離れた場所のデバイスにはHTTPUpdateを使います:ボードが自分でアップデートを取りに行くので、内側から外側への接続だけで済みます。インターネット経由で取得するなら、WiFiClientSecureを使ったHTTPSを選んでください。
  • アップデート中は安全な状態にします:onStartの中でモーターを止め、ヒーターやポンプのリレーをオフにします。再起動には数秒かかり、その間ピンの状態は不定になることがあります。

方法2:ElegantOTAでブラウザから.binを書き込む

ElegantOTAは、ESP32の中で動くWebサーバーに、すっきりした/updateページを追加します。スマホやArduino IDEのないパソコンからこのページを開き、コンパイル済みの.binファイルを選んで書き込みます。進行状況バーはブラウザに表示されます。デバイスを誰かに引き渡すときや、現場でアップデートするときにいちばん便利な方法です。

  1. Arduino IDEでライブラリマネージャを開き、ElegantOTA(Ayush Sharma)を検索してインストールします。
  2. 次のコードを書き換え、最初はUSBで書き込みます。
  3. シリアルモニタに表示されたアドレスをブラウザで開き、ユーザー名「admin」とパスワード「ota1234」でログインします(例:http://192.168.1.42/update)。
  4. モードはFirmwareを選んだままにして、.binファイルをドラッグ&ドロップします。書き込みが終わると、ボードが自動で再起動します。
C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WebServer.h>
3#include <ElegantOTA.h>
4
5const char* WIFI_SSID = "AgAdi";          // Wi-Fi adın / your Wi-Fi name
6const char* WIFI_PASS = "AgSifresi";      // Wi-Fi şifren / your Wi-Fi password
7
8WebServer server(80);
9unsigned long otaProgressMillis = 0;
10
11void onOTAStart() {
12  Serial.println("OTA basladi / OTA started");     // röleleri kapat / switch relays off
13}
14
15void onOTAProgress(size_t current, size_t final) {
16  if (millis() - otaProgressMillis > 1000) {        // saniyede bir yaz / print once per second
17    otaProgressMillis = millis();
18    Serial.printf("%u / %u bayt / bytes\n", current, final);
19  }
20}
21
22void onOTAEnd(bool success) {
23  Serial.println(success ? "OTA basarili / OTA successful" : "OTA hatasi / OTA failed");
24}
25
26void setup() {
27  Serial.begin(115200);
28  WiFi.mode(WIFI_STA);
29  WiFi.begin(WIFI_SSID, WIFI_PASS);
30  while (WiFi.status() != WL_CONNECTED) {
31    delay(500);
32    Serial.print(".");
33  }
34  Serial.println();
35  Serial.print("Adres / Address: http://");
36  Serial.print(WiFi.localIP());
37  Serial.println("/update");
38
39  server.on("/", []() {
40    server.send(200, "text/plain", "BlueGrays ESP32 - surum / version 1.0");
41  });
42
43  ElegantOTA.begin(&server, "admin", "ota1234");   // /update sayfası + kullanıcı/parola / /update page + user/password
44  ElegantOTA.onStart(onOTAStart);
45  ElegantOTA.onProgress(onOTAProgress);
46  ElegantOTA.onEnd(onOTAEnd);
47
48  server.begin();
49}
50
51void loop() {
52  server.handleClient();     // web isteklerini işle / handle web requests
53  ElegantOTA.loop();         // başarılı yüklemeden sonra yeniden başlatır / reboots after a successful upload
54}

ElegantOTAはデフォルトで、コアに付属する同期型のWebServerクラスで動きます。そのため、loop()の中のserver.handleClient()とElegantOTA.loop()の呼び出しが必須です。プロジェクトですでにESPAsyncWebServerを使っているなら、ライブラリのELEGANTOTA_USE_ASYNC_WEBSERVERオプションを1にすれば、非同期サーバーでも使えます。非同期サーバーの準備は「ESP32の非同期WebサーバーとWebSocket」の記事で説明しています。ページのFilesystemモードは、プログラムではなくLittleFSやSPIFFSのイメージ(Webページのファイル、設定ファイル)を更新します。

.binファイルを用意する:コンパイルしたバイナリの出力

ElegantOTAとHTTPUpdateには、コンパイル済みのプログラムファイルが必要です。Arduino IDEで「スケッチ」>「コンパイルしたバイナリを出力」を実行します。IDEはプログラムをコンパイルし、スケッチフォルダの中のbuildフォルダにいくつかのファイルを出力します。OTAに使えるのはそのうち1つだけです。

ファイル中身OTAで使う?
myproject.ino.bin(アプリケーション本体のみ)アプリケーションのみ(自分のプログラム)はい、書き込むのはこのファイル
myproject.ino.bootloader.bin(ブートローダー)ブートローダーいいえ
myproject.ino.partitions.bin(パーティションテーブル)パーティションテーブルいいえ
myproject.ino.merged.bin(コア3.x)ブートローダー、テーブル、プログラムをまとめたものいいえ、USBでアドレス0x0に書き込むときだけ使う
ボードに載っているチップ向けにコンパイルしたか確認してください。ESP32向けにコンパイルした.binをESP32-S3やESP32-C3に書き込もうとすると、最後の検証で拒否され、ボードは古いプログラムのまま動き続けます。誤ってmerged.binやbootloader.binを選ぶと、アップデートは「Wrong Magic Byte」(マジックバイトの不一致)、空き容量不足、または有効化のエラーで止まります。

方法3:HTTPUpdateでサーバーから自動アップデート

家のあちこちに5台のセンサーボードがあるとします。1台ずつIDEから書き込む代わりに、新しい.binファイルをサーバーに置き、ボードが自分でダウンロードするようにできます。ESP32コアに付属するHTTPUpdateライブラリなら、これが1行でできます。次の例では、ボードのBOOTボタン(GPIO 0)を押すとアップデートが始まります。

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <HTTPUpdate.h>
3
4const char* WIFI_SSID = "AgAdi";
5const char* WIFI_PASS = "AgSifresi";
6// Bilgisayarındaki ya da sunucundaki .bin dosyası / the .bin file on your computer or server
7const char* FIRMWARE_URL = "http://192.168.1.50:8000/firmware.bin";
8const int BUTTON_PIN = 0;               // karttaki BOOT butonu / the BOOT button on the board
9
10void setup() {
11  Serial.begin(115200);
12  pinMode(BUTTON_PIN, INPUT_PULLUP);
13  WiFi.begin(WIFI_SSID, WIFI_PASS);
14  while (WiFi.status() != WL_CONNECTED) delay(500);
15  Serial.println("Hazir: guncellemek icin BOOT'a bas / Ready: press BOOT to update");
16}
17
18void loop() {
19  if (digitalRead(BUTTON_PIN) == LOW) {      // butona basıldı / button pressed
20    WiFiClient client;
21    httpUpdate.rebootOnUpdate(true);          // başarılıysa yeniden başlat / reboot on success
22    t_httpUpdate_return ret = httpUpdate.update(client, FIRMWARE_URL);
23
24    switch (ret) {
25      case HTTP_UPDATE_FAILED:
26        Serial.printf("Hata / Error (%d): %s\n", httpUpdate.getLastError(),
27                      httpUpdate.getLastErrorString().c_str());
28        break;
29      case HTTP_UPDATE_NO_UPDATES:
30        Serial.println("Guncelleme yok / No update");
31        break;
32      case HTTP_UPDATE_OK:
33        Serial.println("Tamam / OK");        // buraya genelde gelinmez, kart yeniden başlar / usually not reached, the board reboots
34        break;
35    }
36    delay(1000);
37  }
38}

試すには、パソコンのフォルダに.binファイルをfirmware.binという名前で置き、そのフォルダでpython3 -m http.server 8000を実行します。コードのIPアドレスは、パソコンのローカルIPに置き換えてください。実際に使うときは、ボタンをタイマーに置き換えます。たとえば1時間に1回、ボードが小さな「version.txt」ファイルを読み、サーバーのバージョン番号が自分のものより大きければアップデートを始めます。こうすれば、同じファイルを何度もダウンロードして再起動を繰り返すこともありません。

OTAの途中で接続が切れたらどうなる?

結論から言うと、ボードは壊れません。新しいプログラムは使われていないパーティションに書き込まれ、起動するパーティションはファイル全体の検証が終わってから切り替わるので、書き込みが途中で切れてもボードは古いプログラムで動き続けます。IDEでは「Error Uploading」(書き込みエラー)、ArduinoOTAではOTA_RECEIVE_ERROR、ElegantOTAでは失敗の結果が表示されます。原因を取り除いて、もう一度書き込めば大丈夫です。書き込み中に停電した場合も同じで、ボードは古いプログラムで起動します。本当のリスクは、新しいプログラムそのものに問題がある場合だけです。Wi-Fiに接続しないプログラムや、OTAのコードを含まないプログラムを書き込むと、次のアップデートにはまたケーブルが必要になります。

  • Wi-Fiの電波が弱い:書き込みが30〜60%あたりで止まるなら、ボードをルーターに近づけるか、外部アンテナ付きのモジュールを使います。シリアルモニタで見たWiFi.RSSI()の値が-75 dBmより悪い場合は、切断が起きると考えてください。詳しくは「ESP32がWi-Fiにつながらないとき」を参照してください。
  • 電源が足りない:フラッシュへの書き込みとWi-Fiの送信は、同時に電流を消費します。弱いUSBポートや細いケーブルだと、書き込み中にボードがリセットされることがあり、これはブラウンアウトエラーとして現れます。少なくとも500 mAを供給できる、しっかりした5 V電源を使ってください。
  • loop()の中の長い待ち時間:delay(5000)のような待ちはhandle()の呼び出しを遅らせ、IDEに「No response from the ESP」(ESPから応答がない)と表示されます。タイミングはmillis()で管理してください。
  • ファイアウォール:ArduinoOTAでは、書き込みが始まるとESP32がパソコンに接続し返します。Windowsファイアウォールやウイルス対策ソフトがこの着信接続をブロックすると、書き込みはすぐに失敗します。最初に試したときに表示される許可のウィンドウで、プライベートネットワークを許可してください。

新しいスケッチでもOTAを残す

OTAでいちばんよくある失敗はこうです。まったく別のプロジェクトをOTAで書き込んだところ、そのプロジェクトにはArduinoOTAのコードがなく、ボードがネットワークポートに表示されなくなります。新しく書き込んだプログラムは古いプログラムと完全に入れ替わります。OTAは「ボードに残る」機能ではなく、どのプログラムも自分でOTAを持っている必要があります。天井のデバイスにまたはしごで登らずに済むよう、次のルールを習慣にしましょう。

  • テンプレートを使う:Wi-Fiの接続とArduinoOTAの設定を別の関数(例:setupOTA())にまとめ、新しいプロジェクトはいつもそのファイルから始めます。
  • handle()の呼び出しはloop()のいちばん最初に書き、loop()の中に長時間処理を止めるループは決して置かないでください。
  • Wi-Fiが切れたら再接続する:WiFi.setAutoReconnect(true)を使うか、接続を定期的に確認します。接続のないボードはアップデートを受け取れません。
  • ディープスリープを使うプロジェクトにはメンテナンス時間を設ける:スリープ中のボードにはOTAの要求が届きません。起動時にボタンが押されている場合や、MQTTのフラグが届いた場合に、スリープする前に60秒間OTAを待つモードを作りましょう。スリープモードについては「ESP32のディープスリープ」の記事を参照してください。
  • まず机の上の2台目のボードで試す:現場のデバイスに送る前に、同じコードを手元のボードで動かします。Wi-Fiに接続し、ネットワークポートに表示されれば、安心して送れます。
  • バージョン番号を表示する:コードの先頭にFW_VERSIONという定数を置き、Webページやディスプレイに表示します。どのボードでどのバージョンが動いているか、いつでもわかります。

ESP8266とRaspberry Pi Pico WでのOTA

このガイドのArduinoOTAとElegantOTAのコードは、少し変えるだけでESP8266(NodeMCU、Wemos D1 Mini)のボードでも動きます。WiFi.hの代わりにESP8266WiFi.h、WebServerの代わりにESP8266WebServerを使い、OTAのポートは8266です。4 MBフラッシュのESP8266ボードでは、「Flash Size」メニューでOTAに十分なプログラム領域が残る選択肢を選んでください。Raspberry Pi Pico Wでは、arduino-picoコアがArduinoOTAに対応しています。こちらでは新しいプログラムがまずLittleFSのファイルシステムにダウンロードされ、起動時に所定の場所へコピーされます。そのためPico Wでは、「Flash Size」メニューでプログラムより大きなファイルシステム領域を確保する必要があります。MicroPythonには既製のOTAツールがありません。ネットワークから.pyファイルをダウンロードして置き換える小さなスクリプトは書けますが、ESP32のような検証付きの2パーティション方式のアップデートではありません。

OTAを使ったプロジェクトのアイデア

  • 天井や壁のセンサー:温度、湿度、動きを測るボードのしきい値を、ケーブルに触れずに変更します。
  • スマートホームのリレー:電気ボックスの中のリレーボードのタイマー設定をOTAで更新します。onStartの中でリレーを安全な状態にしておきます。
  • Home Assistantのデバイス:MQTTでHome Assistantに接続したセンサーに、ネットワーク経由で新しい機能を追加します。
  • 庭の水やりコントローラー:防水ボックスを開けずに、水やりの時刻や土壌水分のしきい値を変更します。
  • LCDの進行状況バー:onProgressの値を、16x2 LCDに進捗率とバーで表示します。BlueGraysのESP32 OTAのプロジェクトは、まさにこれを行います。
  • まとめてアップデート:複数のボードが、HTTPUpdateで毎晩同じサーバーに新しいバージョンがないか確認するようにします。

よくある間違いと対処法

症状考えられる原因対処法
「ポート」メニューにネットワークポートがないボードが別のネットワークにある、mDNSがブロックされている、コードがArduinoOTA.begin()を呼んでいない同じ2.4 GHzのネットワークを使う、IDEを再起動する、シリアルモニタにIPが表示されるか確認する
エラー:「No response from the ESP」(ESPから応答がない)loop()の中の長いdelay()、ボードが処理中、またはファイアウォールdelay()の代わりにmillis()を使う、ファイアウォールでIDEを許可する
エラー:「Authentication Failed」(認証失敗)OTAのパスワードが間違っているコードのsetPasswordの値を入力する。忘れた場合は一度USBで書き込む
エラー:「Not Enough Space」(空き容量不足)/「Begin Failed」(開始失敗)Huge APPまたはNo OTAの構成、プログラムが2つ目の領域に収まらないMinimal SPIFFSを選んで一度USBで書き込む。プログラムを小さくする
書き込みが途中で止まる電波が弱い、電圧の低下、接続の切断ボードをルーターに近づけ、しっかりした5 V電源を使い、もう一度試す。古いプログラムは動き続ける
書き込み後、ボードがネットワークに接続しない新しいコードにWi-FiやOTAがない、SSIDが間違っているUSBで直す。今後はすべてのプロジェクトをOTAのテンプレートから始める
ElegantOTAで「Wrong Magic Byte」(マジックバイトの不一致)や有効化のエラーmerged.bin、bootloader.bin、または別のチップ向けにコンパイルしたファイルを選んだmyproject.ino.binファイルだけを書き込む
初めてOTAを設定するときは、ボードをUSBにつないだまま、シリアルモニタを開いておきましょう。onErrorの出力が、原因がパスワードなのか、空き容量なのか、接続なのかを1行で教えてくれます。すべてうまく動いたら、ケーブルを抜いてかまいません。

OTAは、ESP32のプロジェクトを机の上から実際の設置場所へ移すための鍵です。一度きちんと設定すれば、修正、新機能、しきい値の変更が無線で数秒で届きます。BlueGraysアプリでは、ESP32 OTAアップデートのプロジェクト(LCDにIPと進捗を表示)を、Wi-Fi接続、非同期Webサーバー、mDNS、NTPのプロジェクトとあわせて、回路図とコード付きでステップごとに確認できます。

よくある質問

ESP32のOTAとは何ですか?

OTA(Over-The-Air)とは、USBケーブルの代わりにWi-Fi経由でESP32に新しいプログラムを書き込むことです。ボードは新しいコードを使われていないプログラム用パーティションに書き込み、検証してから、新しいバージョンで再起動します。最初のOTA対応スケッチだけは、一度USBで書き込む必要があります。

Arduino IDEにESP32のネットワークポートが表示されないのはなぜですか?

よくある原因は、パソコンとボードが別のネットワークにあること、ゲストネットワークがデバイス同士を分離していること、コードのArduinoOTA.begin()の呼び出しが実行されていないことです。シリアルモニタでボードがIPを取得したか確認し、IDEを再起動して数秒待ってください。

ESP32のOTAにはどのパーティション構成を選べばいいですか?

4 MBフラッシュのボードではDefault(2 × 1.25 MBのプログラム領域)を、プログラムが大きい場合はMinimal SPIFFS(2 × 1.875 MB)を選びます。Huge APPとNo OTAには2つ目のプログラム領域がないので、OTAは動きません。構成を変えた後の最初の書き込みはUSBで行う必要があります。

OTAの書き込みが途中で切れると、ESP32は壊れますか?

いいえ。新しいプログラムは空いているパーティションに書き込まれ、起動するパーティションはファイル全体の検証が終わってから切り替わります。接続や電源が切れても、ボードは古いプログラムで起動するので、もう一度書き込みを試すだけで大丈夫です。

OTAで書き込むとき、シリアルモニタは使えますか?

ネットワークポートはコードの書き込み専用で、シリアルモニタは無線では使えません。メッセージを見るには、ボードをUSBにつないだままにするか、情報をWebページやディスプレイ、MQTTで表示します。

ESP32のOTAのパスワードはどう設定しますか?

ArduinoOTAでは、begin()の前にArduinoOTA.setPassword("password")を呼びます。IDEは書き込みの前にこのパスワードを求めます。コードにパスワードをそのまま書きたくない場合は、setPasswordHash()でMD5ハッシュを渡せます。ElegantOTAでは、begin(&server, "user", "password")でページを保護します。

全プロジェクトを見る
ESP32 OTA更新:無線でファームウェアを書き込み(LCD付き)
#132上級
プレミアム

ESP32 OTA更新:無線でファームウェアを書き込み(LCD付き)

このプロジェクトでは、USBケーブルを使わず、Wi-Fi経由でESP32に新しいファームウェアを書き込む方法を学びます。ArduinoOTAライブラリを使うと、ESP32がネットワーク上で検出され、Arduino IDEの「ネットワークポート」タブにbluegrays-otaとして自動表示されます。初回はUSBで書き込み、その後の更新はすべて無線で行えます。設置済みのデバイスにとって非常に重要な機能です。I2C LCDディスプレイには、Wi-Fiの状態、IPアドレス、ホスト名、OTA書き込みの進捗率、エラーメッセージをトルコ語と英語で表示します。

4.6(14)
22
ESP32 Wi-Fi STA接続(LCDディスプレイ付き)
#127上級

ESP32 Wi-Fi STA接続(LCDディスプレイ付き)

このプロジェクトでは、ESP32をステーション(STA)モードで家庭のWi-Fiネットワークに接続し、安全で障害に強い接続を実現します。SSIDとパスワードを別のsecrets.hファイルに保存してGitへの流出を防ぎ、自動再接続の仕組みを実装します。RSSIによる信号強度とIPアドレスはI2C LCDディスプレイに表示します。内蔵LEDは状態表示に使います。実運用を想定した接続管理の仕組みです。

4.4(21)
34
ESP32非同期Webサーバー(LCDにIPアドレスを表示)
#129上級

ESP32非同期Webサーバー(LCDにIPアドレスを表示)

このプロジェクトでは、ESP32を同じWi-Fiネットワーク上のスマートフォンやパソコンから操作できる非同期Webサーバーにします。ESPAsyncWebServerライブラリを使うと、従来のWebServer.hのように1つのリクエストで処理がブロックされず、複数のリクエストを同時に処理できます。ブラウザーでESP32のIPアドレスを開き、ON/OFFボタン(/onと/offのルート)を押すと、GPIO 5に接続したLEDを点灯・消灯できます。

4.8(16)
26
ESP32 mDNS:bluegrays-esp.localでデバイスにアクセス(LCD付き)
#131上級
プレミアム

ESP32 mDNS:bluegrays-esp.localでデバイスにアクセス(LCD付き)

このプロジェクトでは、ESP32にmDNS(Multicast DNS)サービスを追加し、IPアドレスの代わりにbluegrays-esp.localのような覚えやすいホスト名でアクセスできるようにします。DHCPで割り当てられるIPアドレスが変わっても、同じ名前でデバイスにアクセスできます。小さなWebサーバーも動かし、ブラウザーでhttp://bluegrays-esp.local/を開いてLEDを操作します。I2C LCDディスプレイにはホスト名、IPアドレス、LEDの状態をトルコ語と英語で表示します。iOS、macOS、LinuxはmDNSに標準対応しています。

4.8(14)
23
ESP32 WebSocketカウンター(2つのボタン+LCD+Webリアルタイム表示)
#130上級
プレミアム

ESP32 WebSocketカウンター(2つのボタン+LCD+Webリアルタイム表示)

このプロジェクトでは、ESP32に接続した2つのボタンでカウンターの値を変更します(GPIO 5で増加、GPIO 4で減少)。値は16x2 I2C LCDに即座に反映され、ESPAsyncWebServerのWebSocket機能により、接続中のすべてのブラウザーでもページを再読み込みせずに更新されます。HTTPポーリングの代わりに1つの接続を維持し、ESP32がws.textAll()で新しい値を全員に送信します。

4.5(14)
23
ESP32とNTPでインターネットから正確な時刻を取得
#128上級
プレミアム

ESP32とNTPでインターネットから正確な時刻を取得

ESP32をWi-Fiに接続し、NTP(Network Time Protocol)サーバーからミリ秒精度の現在時刻を取得します。configTzTime()関数でタイムゾーンを自動適用し、夏時間を採用する国ではPOSIX TZ文字列で夏時間と標準時間の切り替えも自動管理します。時刻はシリアルモニタと、必要に応じて接続するI2C LCDに表示します。configTzTime()がSNTPサービスを開始し、ネットワークにアクセスできる間は定期的に再同期します。

4.5(13)
21
ESP32 HTTPClient+天気API(LCD付き)
#133上級
プレミアム

ESP32 HTTPClient+天気API(LCD付き)

このプロジェクトでは、ESP32でHTTPClient.hを使い、インターネット経由で天気API(wttr.in)をREST APIとして呼び出します。返されたJSONレスポンスをArduinoJson v7で解析し、都市名、気温、湿度、天気、風の情報をI2C LCDディスプレイにトルコ語と英語で表示します。wttr.inは無料で、APIキーも不要です。都市名を入力するだけで、その都市の現在の天気を取得できます。30分ごとに自動更新し、表示画面ごとに異なるLEDの光り方で状態を知らせます。

4.7(13)
20
ESP32のディープスリープで節電(OLED + RTC RAMカウンター)
#135上級
プレミアム

ESP32のディープスリープで節電(OLED + RTC RAMカウンター)

ESP32のディープスリープモードは、CPUとほとんどの周辺機器を停止し、消費電流を約10 µAまで抑えます。電池で動くIoT機器に欠かせない機能です。このプロジェクトでは、タイマー(15秒)、外部ピン(EXT0、ボタンはGPIO 33)、タッチ(T0/GPIO 4)の3種類の復帰方法を使います。RTC_DATA_ATTRを付けた変数はディープスリープ中も保持され、起動回数、タイマーによる復帰回数、ボタンによる復帰回数を継続して記録できます。復帰するたびにSSD1306 OLEDディスプレイに復帰理由と回数を表示します。

4.3(10)
16