ESP32 と MQTT を使って家の PC の Wake On LAN を実現する
はじめに
遅ればせながら最近 IoT にハマっています。
直近では MQTT を使ってメッセージを送り、LED を光らせる、なんていうものを作っていました。(ちょっとやりたいことがあるので)
今回はこの MQTT と ESP32 を使って、スリープ中の家の PC を WOL(Wake On LAN)する方法について書こうと思います。
MQTT とは
MQTT は「Message Queuing Telemetry Transport」の略とされていますが、Queuing の機能はなく名称だけが残っているようです。HTTP よりも軽量なため消費電力が少なく、非同期な双方向通信が可能です。そのため IoT に最適なメッセージングプロトコルです。Pub/Sub パターンを採用しており、非同期に 1 対多の通信が可能です。特に大事なのは組み込みデバイスやセンサーなど、メモリやネットワーク環境に制限があるような状況を想定して作られている点です。そのため IoT を用いた開発では重要になってくる技術です。
MQTT 自体の詳細な解説はここでは割愛します。どんなものかを知りたい方は以下の記事が参考になります。
用語
とはいえ、まったく説明がないと理解しづらいと思うので軽く用語を整理しておきます。
- ブローカー
- パブリッシュ/サブスクライブ
- メッセージ
- トピック
- QoS (Quality of Service) レベル
ブローカーとクライアント
ブローカーは、簡単に言えばサーバのことです。ここへ、クライアントである IoT デバイスが接続し、特定のトピックを購読します。
またクライアントはパブリッシュする場合もあります。こちらはメッセージを送る側のクライアントですね。
メッセージとトピック
MQTT の基本単位はメッセージです。その中にトピックという、いわば仕分けようのデータがあり、それを元にブローカーはどこに通知を行うかを決めるようになっています。
トピックは hoge/fuga/foo/bar
のように /
で区切られた文字列のことを言い、基本的には /
で始まらない文字列を指定します。
これはディレクトリをイメージしてもらえれば分かりやすいと思いますが、それぞれのカテゴリごとに下層があり、それを識別できるようにしています。
またワイルドカードの指定などもできるようになっており、例えば hoge/+/foo
などのように指定することもできます。
が、今回はひとつの操作のみをするため、ここはあまりこだわらなくても大丈夫です。
QoS (Quality of Service)
その名の通り、メッセージ通知の信頼性です。QoS0 ~ QoS2 までのレベルがあり、QoS の違いは以下の通りです。
- QoS0: 最大 1 回のメッセージ送信。到達は保証しない
- QoS1: 最低 1 回のメッセージ到達保証。ただし重複の可能性あり
- QoS2: 正確に 1 度メッセージを届ける。一番確実だがその分処理負荷も高い
Wake On LAN とは
PC はスリープなど起動していない状態でも、ネットワークから特定のパケット(マジックパケット)を受信すると復帰する機能を有しているものがあります。また、シャットダウン状態にもいくつか段階があるようで、どの段階まで対応しているかは OS によります。
PC 側の設定
WOL を有効にするためにはいくつかの設定を変更しないとなりません。中には BIOS の設定をいじる部分もあります。
詳細については以下の記事が参考になるので、こちらを見ながらご自身の環境に合わせて設定を行ってください。
主な必要な設定は以下です。
- IP アドレスの固定
- ネットワークアダプタの設定
- BIOS / UEFI の設定
- マジックパケットでの起動を有効にする
また、実装にあたって必要な情報は以下です。
- 物理アドレス(MAC アドレス)
- IPv4 アドレス
ESP32 とは
ESP32 は、Espressif Systems 社が開発した低消費電力の Wi-Fi と Bluetooth を搭載したマイコンチップです。Arduino と比較して高性能でありながら価格が安く、IoT プロジェクトで広く使われています。
今回使用する XIAO ESP32C3 は、Seeed Studio が販売する小型の ESP32 開発ボードで、以下のような特徴があります。
- Wi-Fi と Bluetooth 5.0 (LE) を搭載
- 小型サイズ(親指サイズ)でブレッドボードに直接挿せる
- USB Type-C コネクタを搭載
- Arduino IDE や PlatformIO で開発可能
- 低消費電力設計
ESP32 は様々なセンサーやアクチュエータと組み合わせて使うことができ、Wi-Fi 経由で MQTT などのプロトコルを使った通信が簡単に実装できます。今回は ESP32 を MQTT クライアントとして動作させ、受信したメッセージをトリガーにして PC へマジックパケットを送信します。
今回使っているのはこんなちっちゃなボードです。大体 1,000 円くらいで買えます。
環境
今回の実装は Arduino IDE で行っています。
その他、利用しているサービスやハードは以下です。
- Arduino IDE 2.3.6
- HiveMQ フリープラン
- Seeed Studio XIAO ESP32C3
- IoT MQTT Panel (Android / iOS)
ボードマネージャのインストール
デフォルトでは XIAO ESP32C3 向けのボードマネージャがないので、以下の URL を Arduino IDE の Additional boards manager URLs
に追加します。
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
設定は File
> Preferences
から開いた画面で行います。
ライブラリのインストール
今回利用するライブラリは以下です。これをライブラリマネージャからインストールします。
- WakeOnLan 1.1.7
- PubSubClient 2.8
ライブラリのインストールは IDE 左側にある本のアイコンをクリックするとマネージャが開くので、そこから検索してインストールします。
実装
環境が整ったので実装していきましょう。
そんなに長くないのでひとまず全文コードを載せます。いくつかの箇所はご自身の環境に合わせて変更してください。
MQTT ブローカーの設定については HiveMQ を使うので後述します。
新規のスケッチを作成して以下のコードを貼り付けてください。
#include <WiFi.h>
#include <WiFiUdp.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <WakeOnLan.h>
WiFiUDP UDP;
WakeOnLan WOL(UDP);
WiFiClientSecure secureClient;
PubSubClient mqtt(secureClient);
// Wi-Fi 設定
const char* ssid = "YOUR WI-FI SSID";
const char* password = "YOUR WI-FI PASSWORD";
// MQTT
const char* MQTT_HOST = "YOUR HOST URL";
const int MQTT_PORT = 8883;
const char* MQTT_CLIENT_ID = "XIAO_WOL";
const char* MQTT_TOPIC = "wakeup/pc/home";
const char* MQTT_USERNAME = "YOUR USER NAME";
const char* MQTT_PASSWORD = "YOUR PASSWORD";
// PC 情報の設定
const char* pcMacAddress = "YOUR MAC ADDRESS"; // 例: AA:BB:CC:DD:EE:FF
const IPAddress pcIP(192, 168, 1, 123); // ここも、自身で固定した IP を指定
void onMqtt(char* topic, byte* payload, unsigned int len) {
String msg;
msg.reserve(len + 1);
for (unsigned int i = 0; i < len; ++i) {
msg += (char)payload[i];
}
Serial.printf("[MQTT] %s <- %s\n", topic, msg.c_str());
if (!strcmp(topic, MQTT_TOPIC)) {
// マジックパケット送信
WOL.sendMagicPacket(pcMacAddress); // MAC アドレスを使用してマジックパケットを送信
Serial.println("Wake on LAN パケット送信");
}
}
void ensureWifi() {
if (WiFi.status() == WL_CONNECTED) return;
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.printf("WiFi connecting to %s", ssid);
int tries = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (++tries > 60) {
ESP.restart();
}
}
// サーバー証明書の検証をスキップ(自己署名テスト用)
secureClient.setInsecure();
Serial.println("\nConnected to Wi-Fi");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
}
void ensureMqtt() {
if (mqtt.connected()) return;
Serial.print("[MQTT] connect ...");
if (mqtt.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
Serial.println("OK");
mqtt.subscribe(MQTT_TOPIC);
}
else {
Serial.printf("fail rc=%d\n", mqtt.state());
delay(1500);
}
}
void setup() {
Serial.begin(115200);
// Wi-Fi に接続
ensureWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMqtt);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
ensureWifi();
}
if (!mqtt.connected()) {
ensureMqtt();
}
mqtt.loop();
}
では順番に実装を見ていきましょう。
setup / loop
Arduino 開発ではふたつの重要な関数があります。それが setup
と loop
です。
Unity などのゲームエンジンを触ったことがある人はピンとくると思いますが、 setup
は起動時に初回一度だけ呼ばれ、 loop
は起動後常に呼ばれる関数となっています。ある意味名前の通りですね。
今回で言えば Wi-Fi への接続やシリアル通信の初期化などを setup
関数内で行っています。
void setup() {
Serial.begin(115200);
// Wi-Fi に接続
ensureWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMqtt);
}
冒頭の Serial.begin(115200);
はシリアル通信を開始するための処理です。引数に渡しているのは転送レートです。(基本的には 115200
でいいはず?)
そして続く処理で Wi-Fi への接続と、MQTT ブローカーの設定を行っています。
Wi-Fi への接続処理は以下です。
void ensureWifi() {
if (WiFi.status() == WL_CONNECTED) return;
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.printf("WiFi connecting to %s", ssid);
int tries = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (++tries > 60) {
ESP.restart();
}
}
// サーバー証明書の検証をスキップ(自己署名テスト用)
secureClient.setInsecure();
Serial.println("\nConnected to Wi-Fi");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
}
ここで行っているのは、Wi-Fi を、アクセスポイントではなく通常のアクセスとして設定しつつ、指定された SSID / Password で接続を試みます。
接続まで少し時間がかかるのでその間状態をチェックし、connected
になったら処理を進めています。もし規定回数を超えても繋がらない場合は再起動しています。
また、今回はごく限られた状況かつ自分でしか利用しないため自己署名にしてサーバ証明書の検証をスキップしていますが、実際の運用など自分以外が利用する場合はしっかりと実装したほうがいいでしょう。
無事に Wi-Fi に接続できたら MQTT ブローカーの設定とコールバックの設定をして setup
は終了です。
ループ処理は以下です。
void loop() {
if (WiFi.status() != WL_CONNECTED) {
ensureWifi();
}
if (!mqtt.connected()) {
ensureMqtt();
}
mqtt.loop();
}
ループの最初では Wi-Fi の接続確認を行っています。もし接続が切れていたら再接続する処理ということですね。
Wi-Fi に接続している場合は続いて MQTT ブローカーへの接続チェックを行います。もし接続されていない場合は ensureMqtt()
関数によって接続が試みられます。
void ensureMqtt() {
if (mqtt.connected()) return;
Serial.print("[MQTT] connect ...");
if (mqtt.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
Serial.println("OK");
mqtt.subscribe(MQTT_TOPIC);
}
else {
Serial.printf("fail rc=%d\n", mqtt.state());
delay(1500);
}
}
行っていることはシンプルです。事前に設定されたクライアント ID と、ユーザ名、パスワードを用いて接続を試みます。
サーバのアドレスやポートはセットアップ時点で設定されているのでここでは指定していません。
そして無事に接続できたら、対象トピックについて購読( subscribe
)して終了です。
マジックパケットの送信(スリープの解除)
さて、これでインターネットを介した MQTT ブローカーとの接続が完了しました。あとはサーバからメッセージが飛んできたら適切に処理することで WOL を達成できます。
その処理を行っているのがコールバックに指定している onMqtt()
関数です。
void onMqtt(char* topic, byte* payload, unsigned int len) {
String msg;
msg.reserve(len + 1);
for (unsigned int i = 0; i < len; ++i) {
msg += (char)payload[i];
}
Serial.printf("[MQTT] %s <- %s\n", topic, msg.c_str());
if (!strcmp(topic, MQTT_TOPIC)) {
// マジックパケット送信
WOL.sendMagicPacket(pcMacAddress); // MAC アドレスを使用してマジックパケットを送信
Serial.println("Wake on LAN パケット送信");
}
}
コールバックに渡されるのは topic
と payload
(とその長さ)です。
冒頭の処理は payload
から指定された長さ分だけ文字列を取り出しています。今回はペイロードの情報は使わないので意味がないのですが、汎用的にするために処理を残してあります。
大事な点は最後の !strcmp(topic, MQTT_TOPIC)
です。なぜ !
なのかというと、strcmp
関数は文字列同士を比較して同じであれば 0
を返します。そのため if
文としては false
に該当する処理が「同じ」を意味するため !
で反転しているというわけです。
そして目的のトピックだった場合に、PC に向けてマジックパケットを送信してスリープを解除しています。
スケッチのアップロード
プログラムが完成したら、スケッチをボードに書き込みます。(アップロードと呼びます)
ESP32 ボードを PC に接続し、Arduino IDE の左上のボタンを押すとアップロードできます。が、いくつかアップロードするための準備があるので以下の手順を実行してください。
ボードの選択
まずはスケッチをアップロードするボードを選択します。環境のところでボードをインストールしていると以下の項目が選択できるようになります。
Tools
> Board: "..."
> esp32
> XIAO ESP32C3
を選択します。(Board: のところには現在選択されているボード名が入ります)
ポートの選択
次に、接続しているポートを選択します。ここは環境によって異なるため、ご自身の環境に合わせて適切なポートを選択してください。
ログの確認
無事にアップロードが終わると自動的に起動され、setup
と loop
関数が呼ばれるようになります。
シリアル通信のログを表示するウィンドウを表示すると、シリアル通信のポートに流れてきたログを確認することができます。
ログウィンドウは右上のボタンを押すと表示されます。また、ウィンドウの右側にある転送レートを、プログラムで設定した 115200
に変更します。
正常に接続されていれば以下のようにログが表示されるはずです。
20:27:43.191 -> WiFi connecting to Buffalo-2G-F5B0........
20:27:43.191 -> Connected to Wi-Fi
20:27:43.191 -> IP Address: 192.168.11.28
20:27:43.191 -> [MQTT] connect ...OK
HiveMQ Cloud で MQTT ブローカーを立ち上げる
以上で WOL 自体の実装は完成です。しかし、実際に外出先からメッセージを飛ばすにはインターネット上に存在する MQTT ブローカーを利用しないとなりません。(あるいは家の Wi-Fi ルータのポートを開けて自前サーバという手もありますが色々めんどくさい)
今回は冒頭でも紹介した HiveMQ Cloud というサービスを利用します。
こちらはフリーでも利用できる MQTT ブローカーを提供してくれているサービスです。今回の用途レベルでは有料にしなくても問題なく使えます。
まずはこちらのサービスでアカウントを作成してください。フリープランであればクレカの登録も不要です。
クラスターの作成
初回ではクラスターが作成されていないので CLUSTERS OVERVIEW という項目の横の「+」ボタンから新しくクラスターを作成します。
プランが聞かれると思いますが、フリーの Create Serverless Cluster を選択してください。
追加すると以下のように作成されるので「Manage Cluster」ボタンを押して設定画面に入ります。
設定画面に入ったら Access Management タブを開き、右上の「Edit」ボタンを押して Add Credentials
からユーザ名とパスワードを追加してください。
ここで設定したユーザ名とパスワードを、実装のところで出てきた MQTT_USERNAME
と MQTT_PASSWORD
に設定してください。
さぁこれで準備が整いました。
Web クライアントでチェック
HiveMQ の画面には Web Client というタブがあります。これを開くとこの画面から接続テストが行えます。デバイスを接続したのちに、ここから設定したトピックを送信して受信されるかをチェックするといいでしょう。
サーバ証明書の設定
紹介したコードは開発時のもので、サーバ証明書の設定をしていません。しかし、本番運用の場合はそれを見越してしっかりと設定しておくのが望ましいです。
サーバ証明書の取得については Q&A で言及されています。
Where can I download the Root CA server certificate for Serverless and Starter?
The root ca can be downloaded from https://letsencrypt.org/certs/isrgrootx1.pem. This will create a file isrgrootx1.pem which should be used as a “server certificate”.
上記説明内の PEM ファイルのリンクからファイルをダウンロードします。
中身はテキストなのでそれをコピーして利用します。
プログラムでの指定
以下は、実際のコード例です。取得した PEM ファイルの中身を文字列として保持し、それを設定してアクセスします。
const char* root_ca = "-----BEGIN CERTIFICATE-----\n"
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
// 中略
"-----END CERTIFICATE-----";
長いので中略しています。実際のものと置き換えて使ってください。
開発用では setInsecure()
としていた箇所を以下のように書き換えます。
// secureClient.setInsecure();
// ↓ こっちに書き換える
secureClient.setCACert(root_ca);
これでサーバ証明書を利用した形でアクセスすることができます。
スマホアプリのインストール
Web クライアントで確認できても、外出先から実際にオンにするにはなにかしら別の方法が必要です。今回は MQTT 関連をまとめて管理できるアプリがあるのでそれを利用します。
以下のアプリを、ご自身の使用スマホに合わせてインストールしてください。
▼ iOS 版
▼ Android 版
自分は iPhone ユーザなので iOS アプリを使います。
WOL 用ボタンを作成
以下の説明に沿って WOL 用のボタンを作成していきます。
基本的にこのアプリは Dashboard という単位がひとつの MQTT ブローカーとの接続単位となります。この Dashboard の中にボタンやスイッチなどを配置していく構成になっています。
そのためまずは Dashboard を作成します。
右下の「+」ボタンを押すと「Add Connection」画面に移ります。ここに、接続先ブローカーの情報を入力します。
ユーザ名とパスワードは Additional options の中にあるので、これを開いて先ほど自身で設定したものを入力してください。
Dashboard が作成されたら、次はパネルを作成します。パネルは、ボタンやスイッチなどの単位です。
今回はボタンを押したらスリープを解除したいのでボタンを選択します。
ボタンを選択するとボタンの名前や見た目を設定する画面になります。
ここはお好みで設定してください。
無事にボタンが作成されると Dashboard にボタンが表示されます。(先ほどの再掲)
自分は「HOME PC WOL」という名前のボタンにしました。
動作確認
以上ですべての準備が整いました。
ESP32 を起動し、Arduino IDE のシリアル通信のログ画面を表示した状態で MQTT ブローカーへの接続完了を確認したのち、先ほど作成したボタンを押して見てください。正常に接続できていればログが出力されるはずです。
20:45:03.170 -> [MQTT] wakeup/pc/home <- none
20:45:03.211 -> Wake on LAN パケット送信
最後に
最近は IoT デバイスをあれこれするのがとても楽しいです。特に最近では AI の進化が凄まじく、こうした IoT デバイスと組み合わせることで可能性が無限大に広がります。
今回この WOL を試したのも、AI を絡めた IoT デバイスを開発する過程で思いついた方法でした。これからも色々作ってブログ記事にしたいと思います。
Discussion