Smart HomeをDIYする

Apple HomeKit は対応製品が少なくて高価なのでHomebridgeでがんばります

Arduino IDEからESP32をOTAアップデート(改訂版)

OTA (Over The Air)を使ってESP32のスケッチをアップデートする方法の改訂版です。以前、試したESP32のスケッチ例は、もっと簡単にしても良いことを他の方のブログで知りました。おかげさまで活用できるようになりました。

ということで前回の記事

diysmarthome.hatenablog.com

は取り消します。その代わりに、以下をご覧ください。

ArduinoOTA

ESP32の開発キットをArduino IDEでプログラムする際には、通常はUSB/シリアル接続します。開発段階ではそれで良いのですが、家のどこかに設置してしまった後から、プログラムを更新しようとすると厄介です。またUSB/シリアル変換チップを取り外して、ESP32本体だけを組み込んだ場合は、もはやシリアル接続できなくなってます。そこでWiFi経由のOTAでプログラムを更新する方法が使われます。ESPHomeではOTAが用意されていて、簡単に使えました。でも、Home Assistantを使わないとあまりメリットがないです。一方で、Arduino IDEのESP32ボードマネージャにもOTAのスケッチ例が用意されてます。割と長めのスケッチですが、省略できる部分が大きいので、実は簡単でした。

ArduinoのOTAスケッチ例

まずはスケッチ例を試した結果です。ArduinoにESP32のボードマネージャをインストールすると、メニューに、ファイル/スケッチ例/ESP32Dev Module用のスケッチ例/ArduinoOTAが現れ、この中に、BasicOTAとOTAWebUpdaterのサンプルが用意されています。

  • BasicOTAArduino IDEのシリアルの代わりにWiFiで接続してプログラムをダウンロードする方式です。
  • OTAWebUpdaterはES32をwebサーバにして、そこに接続してバイナリをアップロードする方式です。

スケッチ例を試してみました。どちらもすぐに動きました。ただ、OTAWebUpdaterは、webページを作るためにプログラムが長くて、本筋のプログラムがわかりにくくなりそうです。また、BasicOTAの方もsetup()部分が割と長いです。使いにくいと思ってたのですが、他の方のブログ記事でざっくり省略しても良いことを知りました。確かによく見ると、デバッグのためにシリアルに表示するような設定がほとんどでした。なのでBasicOTAが使いやすいと思います。

OTA対応Lチカ

例えば、OTAではない普通のLチカをOTA対応させる手順を書きます。これはGPIOの4番に取り付けたLEDを1秒ごとに点滅します。

#define LED 4
void setup() {
  pinMode(LED, OUTPUT);
}
void loop() {
  digitalWrite(LED, HIGH);
  delay(1000);
  digitalWrite(LED, LOW);
  delay(1000);
}

これをArduino IDEでOTAできるよう書き換えるためには、以下のように追加します。太字が追加部分です。

#include <WiFi.h>
#include <ArduinoOTA.h>

#define LED 4
const char* ssid = "XXXXXXXX"; //WiFi SSID
const char* password = "xxxxxxxx"; //WiFi password

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    delay(5000);
    ESP.restart();
  }
ArduinoOTA.begin(); pinMode(LED,OUTPUT); } void loop() { ArduinoOTA.handle(); digitalWrite(LED, HIGH); delay(1000); digitalWrite(LED, LOW); delay(1000); }

WiFiのために追加した部分がほとんどです。WiFiを利用するためにはいずれにしても必要なので、スマートホーム系のアプリなら仕方ないところかと思います。WiFi設定を除いてOTAに必要な行は3行で、

  1. 冒頭で#include <ArduinoOTA.h>をインクルードする
  2. setup()でArduinoOTA.begin();する
  3. loop()で毎回ArduinoOTA.handle();を呼び出する

という行を追加すれば良いようです。

これを書き込む前は、Arduino IDEの接続手段にシリアルポートしか見えていませんでした。

でもこのプログラムが動き始めるとネットワークポートが見えるようになってます。これを選択すると、新しいプログラムが書き込めます。OTAのネットワークポートが現れない場合は、ESP32をリセット(電源をoff/on)すると良いようでした。(下のスクショは、後述する方法でArduinoOTAのポート名をBlinkLED_OTAにした場合です)

今回のプログラムではloop()で合計2秒もdelay()しているので、ArduinoOTA.handle()が呼ばれる頻度がとても少ないです。それでも動きました。

ネットワークポート名とパスワードをつける

このままだと少し使いづらいです。そこで、まずはネットワークポート名に分かりやすい名前をつけます。分かりやすい名前を付けないと、複数のESP32のOTAが動作している時に、どれに接続して良いか分かりません。これには、setHostname()メソッドを使います。以下のように、setup()のところで、begin()する前に設定すれば良いようです。ここでBlinkLED_OTAという名前をつけると、上のスクリーンショットのように、ネットワークポートに現れます。

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    delay(5000);
    ESP.restart();
  }
  ArduinoOTA.setHostname("BlinkLED_OTA");
  ArduinoOTA.setPasswordHash("3294................105f4241e");
  ArduinoOTA.begin();
  pinMode(LED,OUTPUT);
}

 上の例では、パスワードもつけました。OTAにするとLAN上から誰でもアクセスできるので、パスワードをつけておきたい場面は多いと思います。これでArduino IDEから最初に接続する際にパスワードを尋ねられます。

 

 

setPassword()メソッドで平文のパスワードが書けます。一方で、setPasswordHash()を使えばmd5ハッシュ値でパスワード指定できます。ハッシュ値からパスワードは逆算できないので、ソースコードに書いても少しは安心です。ハッシュ値は、macOSならばmd5コマンドで得られます。例えば、hogehogeというパスワードのハッシュ値は、

% echo -n "hogehoge" | md5   
329435e5e66be809a656af105f42401e

で、得られます。echoコマンドの結果には通常は最後に改行が入ります。そのままmd5に渡すと、改行までを含んだハッシュ値を計算してしまうので、パスワード入力に使えません。それで、-nオプションで改行を省略して、md5に渡してます。この英数字をsetPasswordHash()に書いておけば良いです。

OTA対応MQTT

次はMQTTクライアントプログラムをOTAアップデート可能に改造します。まずは改造前のプログラムです。以下では、1秒ごとにhelloというメッセージをMQTTブローカーに送ります。

#include <Arduino.h>
#include <EspMQTTClient.h>
EspMQTTClient *client; //instance of MQTT
const char SSID[] = "XXXXXXXX"; //WiFi SSID
const char PASS[] = "xxxxxxxx"; //WiFi password
char CLIENTID[] = "IRremote_953322564"; //something random
const char  MQTTADD[] = "192.168.xxx.xxx"; //Broker IP address
const short MQTTPORT = 1883; //Broker port
const char  MQTTUSER[] = "";//Can be omitted if not needed
const char  MQTTPASS[] = "";//Can be omitted if not needed
const char  PUBTOPIC[] = "mqttthing/test";

void setup() {
  client = new EspMQTTClient(SSID,PASS,MQTTADD,MQTTUSER,MQTTPASS,CLIENTID,MQTTPORT); 
}

void onConnectionEstablished() {
}

void loop() {
  client->publish(PUBTOPIC,"hello");
  client->loop(); 
  delay(1000);
}

mosquitto_subコマンドでモニターすると、以下のようにhelloが表示されます。

mqttthing/test hello
mqttthing/test hello
mqttthing/test hello

これも、Lチカの時と同様に、以下のようにすればOTA版が作れます。太字が追加した部分です。

#include <Arduino.h>
#include <ArduinoOTA.h>
#include <EspMQTTClient.h>
EspMQTTClient *client; //instance of MQTT
const char SSID[] = "XXXXXXXX"; //WiFi SSID
const char PASS[] = "xxxxxxxx"; //WiFi password
char CLIENTID[] = "IRremote_953322564"; //something random
const char  MQTTADD[] = "192.168.xxx.xxx"; //Broker IP address
const short MQTTPORT = 1883; //Broker port
const char  MQTTUSER[] = "";//Can be omitted if not needed
const char  MQTTPASS[] = "";//Can be omitted if not needed
const char  PUBTOPIC[] = "mqttthing/test";

void setup() {
  client = new EspMQTTClient(SSID,PASS,MQTTADD,MQTTUSER,MQTTPASS,CLIENTID,MQTTPORT); 
}

void onConnectionEstablished() {
  ArduinoOTA.setHostname("eps32mqtt");
  ArduinoOTA.setPasswordHash("3294................105f4241e");
  ArduinoOTA.begin();
}

void loop() {
  ArduinoOTA.handle();
  client->publish(PUBTOPIC,"hello");
  client->loop(); 
  delay(1000);
}

先ほどのLチカの例と異なり、MQTTの例では、WiFi設定をMQTTライブラリが裏でやってくれてます。なので追加する行は少ないです。

EspMQTTClientクラスでは、プログラムには表れていませんが、setup()でnewしてMQTTコンストラクタを呼んでいるタイミングで、WiFi設定が開始されています。それでWiFi接続が終わって、MQTTブローカに接続できたタイミングで、onConnectionEstablishedが呼ばれます。また、動作中に万一WiFiが不通になった場合も、MQTTライブラリがWiFi接続をやり直します。その時も再接続できれば、やはりonConnectionEstablishedが呼ばれます。なのでここでArduinoOTAも起動しておけば良いようです。

まとめ

ArduinoOTAライブラリを使って、Arduino IDEからESP32をOTAアップデートしました。ESP32で何かを作成する最初の段階では、シリアルポート接続でテストしたりデバッグしたりするのが簡単です。動作が確認できて、実際に使用する場所に設置する段階になったら、OTAに移行しておくと良いかと思いました。現場で動かしながら修正をする場合には、OTAで接続できるととても楽です。