Part2では、開発環境の構築と、バージョン違いによるminiz.hエラーを乗り越えるまでをお届けしました。Part3では、いよいよ3ボタン通知機能の実装と、実機で発覚した2つのバグについてまとめます。
実装編:3ボタン通知機能
🧐私:右のボタンを押したら"今から帰るよー"って通知してほしい。左のボタンを押したら"もう少し遅くなる"まんなかのボタンを押したら"まだわからない"って通知をおくるようにして
コードをもらって実機で試したところ、無事に動作しました。
🧐私:うまくいったよ
つまずき④:ボタンを押していないのに通知が連射される
ところが、しばらく置いておくと想定外の現象が起きました。
🧐私:ボタンを押さずに置いたら、一定間隔で"もう少し遅くなる"が送信されるんだけどなぜ?
🤖Gemini先生:それは、コードの書き方としてよくやってしまう「条件の選び方のミス」だね。M5Stackのボタンライブラリには、主に次の2つの判定方法があるんだ。
1. 継続的に状態を拾ってしまう書き方(NGだった原因)
if (M5.BtnA.isPressed()) {
// ボタンが「今、押されている」ならずっと実行
sendDiscordMessage("もう少し遅くなる");
}
isPressed()は「今、ボタンが押し込まれていますか?」という現在の状態をYESかNOで答えるだけの関数です。マイコンの処理速度は非常に速く、loop()の中を1秒間に何千回、何万回とぐるぐる回っています。人間が「ポンッ」と一瞬だけボタンを押したつもりでも、マイコン側では何百回もisPressed() = YESの状態を検知してしまい、その結果メッセージが連続送信されることになります。
2.「押された瞬間」だけを狙い撃ちする書き方(修正後の正解)
if (M5.BtnA.wasPressed()) {
// ボタンが「新しく押された瞬間」だけ1回実行
sendDiscordMessage("もう少し遅くなる");
}
wasPressed()は「前回のチェックから今回のチェックの間に、新しく押された瞬間(変化)があったか」を答える関数です。指がボタンの上に乗ったままでも、新しく押された瞬間は最初の1回だけなので、通知は1回だけしか飛ばなくなります。
イメージでいうと、isPressed()(状態)は先生が「今、手を挙げている人〜?」と聞き続けている状態。手を挙げている間、毎回「はい!」と返事をしなければなりません。一方wasPressed()(変化)は「新しく手を挙げた人いる〜?」と聞く状態で、すでに挙げている人は無視され、新しく手を挙げた人だけが指名されます。
※isPressed()とwasPressed()の仕様は、M5Stack公式のボタンライブラリ(M5Unified/M5Stackライブラリ)のドキュメントに基づく説明です。
つまずき⑤:Wi-Fiが最初の1回しかつながらない
最後の壁がこちらでした。
🧐私:Wi-Fiにずっとつなげることができないみたい。一回目の送信はうまくいくんだけど
🤖Gemini先生:学校やフリーWi-Fi特有の「無通信状態が続くと自動で切断される仕様」が原因ですね。実は今のコードは、最初に電源を入れた時(setupの中)にしかWi-Fiに繋ぐ処理をしていません。
🧐私:じゃあ、切れたらもう一度繋ぎ直すようにしないといけないのか。どうやればいいの?
🤖Gemini先生:通知を送る直前に毎回「今、Wi-Fiに繋がってる?」と確認し、切れていたら「繋ぎ直す機能」を呼び出すように設計を変更しましょう。具体的には、以下の3つのステップでコードを改修しました。
1. Wi-Fi接続の専用機能(関数)を作る
最初はsetup()の中に直接書いていたWi-Fi接続のコードを、いつでもどこからでも呼び出せるようにconnectWiFi()という独立した機能として切り出しました。
// Wi-Fiに接続する専用の機能(切れた時もこれを呼び出す)
void connectWiFi() {
WiFi.begin(ssid, password);
// 繋がるまで(WL_CONNECTEDになるまで)待つ
while (WiFi.status() != WL_CONNECTED) {
delay(500);
M5.Lcd.print(".");
}
}
2. WiFi.status()で今の状態をチェックする
ESP32には、現在のWi-Fiの状況を教えてくれるWiFi.status()という命令があります。これがWL_CONNECTED(繋がっている状態)かどうかを調べます。
3. 送信する「直前」にチェックする(ここがポイント)
メッセージを送信する機能(sendDiscordMessage)の一番最初に、以下のようなチェック処理を挟み込みました。
void sendDiscordMessage(String msg) {
// 送信前に、Wi-Fiが切れていないかチェック
if (WiFi.status() != WL_CONNECTED) {
// もし切れていたら、専用機能を呼び出して再接続する
connectWiFi();
}
// ここから下に、Discordへ送信する本番の処理を書く
if (WiFi.status() == WL_CONNECTED) {
// (Discordへの送信処理...)
}
}
🧐私:なんでloop()の中でずっとチェックし続けるんじゃなくて、送信する直前にチェックする設計にしたの?
🤖Gemini先生:loop()の中で常に「繋がってる?」とチェックし続けることも可能ですが、それだとマイコンに負担がかかり、バッテリーも余分に消費してしまいます。今回は「ボタンを押して送信する時」にしかインターネットを使わないので、送信ボタンが押された瞬間にだけ確認し、切れていたら繋ぎ直してから送るという設計(Just-in-Timeの処理)が、一番無駄がないんです。
loop()内で常時監視する設計と、送信直前のみチェックする設計はどちらも実装可能で、どちらを選ぶかは消費電力や処理負荷とのトレードオフになる、という点は補足しておきます。
これにより、何回ボタンを押しても安定して通知が届くようになりました。
次回Part4(最終回)では、Geminiと組んで見えた「AI協働開発」の面白さと、今後の展望についてお届けします。