前回でボードへの書き込みとシリアル出力が確認できました。今回は Wi-Fi に接続します。
AWS にデータを送るのが最終目標なので、まずネットワークに乗せるところまで進めます。
目次
認証情報をどこに置くか
Wi-Fi のパスワードをコードに書くことになります。これは最初に決めておくべきことです。 後から対処しても、一度コミットしてしまえば git の履歴に残り続けます。
#1 で決めたとおり、実体は .gitignore で除外したファイルに置き、テンプレートだけをコミットします。
cp firmware/include/secrets.h.example firmware/include/secrets.h
chmod 600 firmware/include/secrets.h
// firmware/include/secrets.h
#pragma once
#define WIFI_SSID "your-ssid"
#define WIFI_PASSWORD "your-password"
このファイルは .gitignore に書いてあるので、git add . してもコミットされません。念のため確認しておくと安心です。
git check-ignore -v firmware/include/secrets.h
SSID が分からない
さて、WIFI_SSID に何を書けばいいのでしょうか。Mac が今つないでいる Wi-Fi の名前を調べようとして、いきなり詰まりました。
$ networksetup -getairportnetwork en0
You are not associated with an AirPort network.
接続しているのに「つながっていない」と言われます。別のコマンドでも同じです。
$ ipconfig getsummary en0 | grep SSID
SSID : <redacted>
<redacted>、つまり伏せられています。
これは macOS のプライバシー保護によるものです。SSID の読み取りには位置情報サービスの許可が必要で、許可のないプロセスには見せない仕様になっています。Wi-Fi の SSID は場所の特定に使えるため、位置情報と同じ扱いなのですね。
目視で確認するならメニューバーの Wi-Fi アイコン、またはシステム設定 → Wi-Fi から見られます。
2.4GHz でなければつながらない
ここが今回いちばん大事なところです。
ESP32 は 2.4GHz 帯にしか対応していません。 5GHz の SSID を指定しても接続できません。
しかもエラーが不親切です。5GHz を指定すると WL_NO_SSID_AVAIL、つまり「SSID が見つからない」というエラーになります。SSID の綴りを間違えたのだと思い込んでしまい、周波数帯が原因だとは気づきにくいわけです。
私が使っているのは NEC の Aterm ですが、登録済みネットワークの一覧を見ると判断できました。この一覧は伏せられません。
$ networksetup -listpreferredwirelessnetworks en0
Preferred networks on en0:
aterm-xxxxxx-a
aterm-xxxxxx-g
...
Aterm には命名の規則があります。
| SSID の末尾 | 周波数帯 |
|---|---|
-a | 5GHz(802.11a 系) |
-g | 2.4GHz(802.11g 系) |
さらに、Mac が今どちらにつないでいるかも分かります。
$ system_profiler SPAirPortDataType
Current Network Information:
<redacted>:
PHY Mode: 802.11ac
802.11ac は 5GHz 専用の規格です。 つまり Mac は 5GHz 側につながっていました。ESP32 には -g のほうを指定する必要がある、と判断できます。
2.4GHz と 5GHz が同じ SSID にまとめられているルーター(バンドステアリング)の場合は、2.4GHz 専用の SSID を別途有効にする必要があります。
コードを書く
接続部分はこれだけです。
#include <WiFi.h>
#include "secrets.h"
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(250);
}
ただ、これだけだと失敗したときに何も分かりません。タイムアウトと、失敗理由の表示を足します。
失敗の理由を日本語で出す
WiFi.status() は数値を返しますが、4 と言われても困ります。切り分けできる文言に変換しました。
static const char *wifiStatusText(wl_status_t status) {
switch (status) {
case WL_NO_SSID_AVAIL: return "SSID が見つからない(SSID の誤り、または 5GHz 帯の可能性)";
case WL_CONNECT_FAILED: return "接続失敗(パスワードの誤りの可能性)";
case WL_CONNECTION_LOST: return "接続が切断された";
case WL_DISCONNECTED: return "未接続";
case WL_IDLE_STATUS: return "待機中";
default: return "不明な状態";
}
}
WL_NO_SSID_AVAIL の説明に「5GHz 帯の可能性」を入れているのがポイントです。前述のとおり、このエラーは 5GHz を指定したときにも出ます。将来の自分がここで悩まないように書いておきました。
LED で状態を示す
シリアルモニタを開いていなくても分かるように、LED の点滅で状態を表しています。
| 状態 | LED |
|---|---|
| 接続処理中 | 250ms 間隔で高速点滅 |
| 接続完了後 | 1秒間隔でゆっくり点滅 |
切れたら勝手につなぎ直す
センサーノードとして常時動かすつもりなので、切断時の再接続も入れました。10秒ごとに状態を見て、切れていれば接続し直します。
if (WiFi.status() != WL_CONNECTED) {
Serial.println("切断されました。再接続します。");
connectWiFi();
}
つながりました
Wi-Fi に接続しています: aterm-xxxxxx-g
.
接続しました(250 ms)
--- Wi-Fi 接続情報 ---
SSID : aterm-xxxxxx-g
IP アドレス : 192.168.1.89
サブネット : 255.255.240.0
ゲートウェイ : 192.168.0.254
DNS : 192.168.0.254
電波強度 : -51 dBm
チャンネル : 11
----------------------
250 ミリ秒、ドット1個ぶんで接続できました。

電波強度の -51 dBm は良好な部類です。目安としてはこのくらいで見ています。
| 電波強度 | 状態 |
|---|---|
| -50 dBm 前後 | 非常に良好 |
| -60 dBm 台 | 良好 |
| -70 dBm を下回る | 不安定になりやすい |
サブネットマスクが広くて戸惑った
出力を見ていて一瞬「あれ?」となったのがここです。
IP アドレス : 192.168.1.89
ゲートウェイ : 192.168.0.254
ESP32 が 192.168.1.89、ゲートウェイが 192.168.0.254。第3オクテットが違います。普通なら別のセグメントで、通信できないはずです。
でもサブネットマスクを見ると 255.255.240.0、つまり /20 でした。この場合 192.168.0.0 〜 192.168.15.255 までが同一セグメントなので、問題なく通信できます。Aterm の既定としては広めの設定ですね。
インターネットにつながっているか確認する
Wi-Fi につながっても、それはルーターまでつながっただけです。AWS にデータを送るには、その先まで到達できる必要があります。
起動時に4項目を順に確認するようにしました。
[2/5] NTP で時刻を同期しています
.............
成功(2601 ms): 2026-09-09 00:05:26 JST
[3/5] DNS を解決しています: example.com
成功(34 ms): 172.66.147.243
[4/5] HTTP で接続しています: http://example.com/
成功(85 ms): HTTP 200
[5/5] HTTPS で接続しています: https://example.com/
成功(1209 ms): HTTPS 200
==================
疎通確認: 5 / 5 項目で成功
空きヒープ: 244076 bytes
==================
なぜ NTP を入れたのか
「時刻合わせなんて後でいいのでは」と思われるかもしれません。これは AWS に接続するために必要です。
AWS IoT Core への接続は TLS で行いますが、TLS ではサーバ証明書の有効期限を検証します。デバイスの時刻がずれていると、有効期限内の証明書でも「期限切れ」と判定されて接続に失敗します。
しかも、そのときのエラーメッセージは handshake の失敗としか出ません。時刻が原因だとは、まず気づけません。
さらに ESP32 には RTC のバックアップ電源がありません。電源を入れるたびに時刻は 1970-01-01 から始まります。つまり起動のたびに NTP で合わせる必要があります。
先に確認しておけば、後で AWS の接続に失敗したときに「時刻は合っている」と切り分けられます。
HTTPS が HTTP の14倍かかる
HTTP : 85 ms
HTTPS : 1209 ms
差は TLS ハンドシェイクのコストです。鍵交換と証明書の検証をやっているので、この程度はかかります。
AWS IoT では MQTT で接続を維持するため、このコストは接続時の1回だけです。データを送るたびに 1.2 秒かかるわけではありません。
なお今回の HTTPS は、サーバ証明書の検証をあえて無効にしています。
WiFiClientSecure client;
// ここではサーバ証明書を検証しない。TLS スタックが動くことの確認が目的。
// AWS IoT Core に接続する際は Amazon のルート CA を設定して検証を有効にする。
client.setInsecure();
TLS のスタックが動くことの確認が目的なので今回はこれで十分ですが、本番でこれをやってはいけません。中間者攻撃を防げなくなります。
メモリの余裕
起動直後 : 295,808 bytes
TLS 使用後: 244,076 bytes
TLS で約 51KB 使っています。ESP32 のヒープは 320KB 程度なので、244KB 残っていればセンサーの処理を足す余裕は十分あります。
次回
これで AWS にデータを送る準備が整いました。
- Wi-Fi につながる
- 時刻が正確
- 名前解決ができる
- TLS が動く
次回はセンサーをつないで、実際にデータを取ってみます。AWS 側の構成(IoT Core を使うか、API Gateway + Lambda にするか)もそろそろ決めないといけません。