編集履歴一覧に戻る
sako_DIYのアイコン画像

sako_DIY が 2021年02月12日00時47分34秒 に編集

初版

タイトルの変更

+

ESP32を用いたお風呂満水通知IoT

タグの変更

+

Arduino

+

ESP32

+

M5Atom

+

Googlehome

+

IoT

+

秋葉原2021

メイン画像の変更

メイン画像が設定されました

本文の変更

+

# 概要 筆者の家は新築アパートでありながら、風呂に自動給湯機能はない。 蛇口の制御は厳しいが、満水を知らせるデバイスなら作れそうということで、 お風呂の満水通知を行うIoTデバイスを作成した。 ![キャプションを入力できます](https://camo.elchika.com/c25ccb93448294d43a8727e1d7a239a1da420f56/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39326264396664302d363263312d343039332d383234382d6532633630396638623839392f30333161333836642d346663322d343232302d393738642d643535303638353862326534/) 仕組みは単純で、超音波距離センサーで水面までの距離を測定、 事前に設定したしきい値以下になったときにgooglehomeから通知する。 ![キャプションを入力できます](https://camo.elchika.com/0dcb5a905e37f6d9707f1a62b23e8bcd98c9ab2d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39326264396664302d363263312d343039332d383234382d6532633630396638623839392f32636363336563362d303131302d343664612d616638322d386631373834616438336233/) # 材料 - M5Atom Lite [https://www.switch-science.com/catalog/6262/](https://www.switch-science.com/catalog/6262/)(スイッチサイエンス) >M5Stackシリーズの中で最もコンパクトな開発モジュールで、サイズはわずか24 x 24 mmです。。 メインコントローラとして、ESP32-PICO-D4チップを搭載しているので、Wi-FiとBluetooth、4 MBの内蔵SPIフラッシュメモリを利用できます。 >本製品は、赤外線LED、 RGB LED、ボタン、Grove互換コネクタを備えています。 さらに、六つのGPIOを介してセンサやモーターなどに接続できます。 - 超音波距離センサー(HC-SR04互換) [https://eleshop.jp/shop/g/gJ9K122/](https://eleshop.jp/shop/g/gJ9K122/)(共立エレショップ) >標準的な超音波距離センサモジュールです。 超音波の反射を検出することで、前方にある物体までの距離を測定します。電源は3V、5Vどちらでも使用可能です。 測定距離は2~450cmです。 - 電池ボックス(006P用) [https://eleshop.jp/shop/g/g26B451/](https://eleshop.jp/shop/g/g26B451/)(共立エレショップ) - 5Vレギュレータ [https://akizukidenshi.com/catalog/g/gM-11188/](https://akizukidenshi.com/catalog/g/gM-11188/)(秋月電子通商) - Googlehome mini ※旧型 下記リンクは新型のNestMini [https://store.google.com/jp/product/google_nest_mini](https://store.google.com/jp/product/google_nest_mini)(Google) - その他小物 ピンヘッダ ユニバーサル基板 電源スイッチ等 # ハード 回路は至って単純でM5AtomLiteに電池より作成した5Vを供給 超音波距離センサーをIOピンに接続 ![キャプションを入力できます](https://camo.elchika.com/a1cbf33d74afc82e2b2737fac97c9b0af363b083/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39326264396664302d363263312d343039332d383234382d6532633630396638623839392f34326334343963332d383531612d343638612d393830342d333037626138663166393732/) 筐体は3DCADで設計し3Dプリンターにて出力した。 ![キャプションを入力できます](https://camo.elchika.com/34008f593b9ba12a147b3ea4f44710bf4e5e5b65/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39326264396664302d363263312d343039332d383234382d6532633630396638623839392f65366161396134382d626332302d346261342d616235362d326331333765363031326662/) ![キャプションを入力できます](https://camo.elchika.com/8d720f4bd48db380bff92078ab76712179d52442/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39326264396664302d363263312d343039332d383234382d6532633630396638623839392f30666537383236312d666532342d346235322d383164302d616531386465396230633133/) 組み立てるとこんな感じになりました。 ![キャプションを入力できます](https://camo.elchika.com/66c7ad1c7e52e7f652b273e91139d69ded9aed56/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39326264396664302d363263312d343039332d383234382d6532633630396638623839392f32333930616530622d383339342d346230322d383734642d343162373437623930363033/) # ソフト 重要な部分のみ解説しコード全体は最後に掲載する。 - まずは距離の測定のコード ```arduino:sendTrigger() #define Trigger_Pin 22 #define Echo_Pin 21 void setup(){ pinMode(Trigger_Pin, OUTPUT); //超音波センサーのトリガーに接続したピンを出力 pinMode(Echo_Pin, INPUT); //超音波センサーのエコーに接続したピンを入力 digitalWrite(Trigger_Pin, LOW); } //距離測定関数 int sendTrigger() {  digitalWrite(Trigger_Pin, HIGH); delayMicroseconds(10); digitalWrite(Trigger_Pin, LOW); while (!digitalRead(Echo_Pin)) { } unsigned long t1 = micros(); while (digitalRead(Echo_Pin)) { } unsigned long t2 = micros(); unsigned long t = t2 - t1; return 340 * t / 20000; } ``` HC-SR04のトリガーピンを一定時間Highにすることで、パルスを送信 送信完了してから反射したパルスが帰ってくるまでの時間は、エコーピンがHighになるためその時間を計測 その時間に音速(340[m/s]=340*100/1000000[cm/μs]=340/10000[cm/μs])をかけて2で割れば距離となります。 - 次にgoogle-homeでの通知 ```arduino:google-home-notifier #include <esp8266-google-home-notifier.h> const char displayName[] = "リビングルーム"; //googlehomeの名前 GoogleHomeNotifier ghn; void setup(){ // google-homeのサーチ if (ghn.device(displayName, "ja") != true) { Serial.println(ghn.getLastError()); return; } Serial.println("found Google Home"); } // 通知用関数 void notify_messege(String filename) { Serial.println("playfile file:" + filename); if ((WiFi.status() == WL_CONNECTED)) { String mp3url = "http://" + WiFi.localIP().toString() + "/" + filename; Serial.println("GoogleHomeNotifier.play() start"); if (ghn.play(mp3url.c_str()) != true) //GoogleHomeNotifierに通知ファイルのURLを渡す { Serial.print("GoogleHomeNotifier.play() error:"); Serial.println(ghn.getLastError()); return; } } else { Serial.println("Error in WiFi connection"); } } ``` こちらは先駆者によって作成されたESP32及びESP8266においてgooglehomeを喋らせるためのライブラリを使用させていただいた。 [ESP8266 から Google Home に喋らせるライブラリ](https://qiita.com/horihiro/items/4ab0edf415916a2cd542)(@horihiro様) 詳しくはリンク先を参照していただくとして、このライブラリではgoogle-ttsを使用してテキストからmp3ファイルを作成し、 そのURLを投げることで、googlehomeを喋らせるようになっているようです。 今回のデバイスは、通知パターンは少ないのでgoogle-ttsを使用せずに音声ファイルを事前に用意し、SPIFFS領域に書き込んでおくこととした。 音声ファイルは、HOYA株式会社様の[VoiceText Web API](https://cloud.voicetext.jp/webapi)を使用し3パターンのmp3を作成, ![](https://camo.elchika.com/5b46d84acd06035e907e2689dffbf0a9a4348b9a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39326264396664302d363263312d343039332d383234382d6532633630396638623839392f65663939626336332d646638362d343161622d393736622d353938646266383161666639/) こちらを使用して、SPISFFS領域に書き込んでおいた。 [arduino-esp8266fs-plugin](https://github.com/esp8266/arduino-esp8266fs-plugin) - サーバーコード ```arduino:WebServer #include <WiFi.h> #include <WebServer.h> #include <FS.h> #include <SPIFFS.h> const String fileList[3] = {"finish_soon.mp3", "finish.mp3", "warning.mp3"}; void setup(){ // WebServer起動 通知音声ファイルを配置 for (int i = 0; i < 3; i++) { server.on("/" + fileList[i], handlePlay); } server.begin(); } void loop(){ server.handleClient(); } //音声ファイルにアクセスがあったら再生する void handlePlay() { String path = server.uri(); SPIFFS.begin(); if (SPIFFS.exists(path)) { Serial.println("handlePlay: sending " + path); File file = SPIFFS.open(path, "r"); server.streamFile(file, "audio/mp3"); file.close(); } else { server.send(200, "text/html", "<meta charset=\"utf-8\"><h1>text2speech</h1><br>SPIFFS file not found:" + path); } SPIFFS.end(); } ``` 先程のgoogle-home-notifierライブラリはmp3ファイルではなく再生用URLを渡す必要があるので、WebServerライブラリで鯖立て SPIFFS領域の音声ファイルを配置しておく。 # 完成 動作の様子は以下の動画をご覧ください @[youtube](https://youtu.be/20Tso1sb8R8) # 全体コード ```arduino:コード全体 #include <M5Atom.h> #include <WiFi.h> #include <WebServer.h> #include <FS.h> #include <SPIFFS.h> #include <esp8266-google-home-notifier.h> #define Trigger_Pin 22 #define Echo_Pin 21 int Threshold[3] = {20, 10, 5}; //各通知しきい値(ほぼ満水、満水、限界)単位:cm int Stage = 0;   //水面までの距離ステージ const char *ssid = "SSID"; //WifiのSSID const char *password = "Password"; //Wifiのパスワード unsigned long crr = 0; //起動後の経過時間 unsigned long prev = 0; //前回距離測定時間 unsigned long interval = 5000;   //測定インターバル 単位:ms const char displayName[] = "リビングルーム"; // GoogleHomeの名前 const String fileList[3] = {"finish_soon.mp3", "finish.mp3", "warning.mp3"};   //通知音声ファイルリスト GoogleHomeNotifier ghn; WebServer server(80); void setup() { //各ピン等初期設定 M5.begin(true, false, true); //(シリアル通信,I2C,LED) pinMode(Trigger_Pin, OUTPUT); //超音波センサーのトリガーに接続したピンを出力 pinMode(Echo_Pin, INPUT); //超音波センサーのエコーに接続したピンを入力 digitalWrite(Trigger_Pin, LOW); delay(10); M5.dis.drawpix(0, 0x00FF00);//LEDを赤へ(初期設定開始) //Wifi接続 Serial.println(""); Serial.print("connecting to Wi-Fi"); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); int n; while ((n = WiFi.status()) != WL_CONNECTED) { delay(1000); Serial.print("."); if (n == WL_NO_SSID_AVAIL || n == WL_CONNECT_FAILED) { WiFi.reconnect(); } } Serial.println(""); Serial.println("connected."); Serial.println("IP address: " + WiFi.localIP().toString()); // WebServer起動 通知音声ファイルを配置 for (int i = 0; i < 3; i++) { server.on("/" + fileList[i], handlePlay); } server.begin(); // google-homeのサーチ Serial.println("connecting to Google Home..."); if (ghn.device(displayName, "ja") != true) { Serial.println(ghn.getLastError()); return; } Serial.println("found Google Home"); M5.dis.drawpix(0, 0x0000FF);//LEDを青へ(初期設定完了) } void loop() { server.handleClient(); curr = millis(); //起動時間取得 if ((curr - prev) >= interval) { //前回測定時間よりinterval経過しているか if (sendTrigger() <= Threshold[Stage]) {//距離を測定、しきい値と比較 notify_messege(fileList[Stage]); //しきい値以下なら通知 if (Stage < 2) Stage++; //ステージをすすめる } else { interval = 10000; //最終ステージのみインターバルを10秒に設定 } prev = millis(); //測定時間更新 } } //音声ファイルにアクセスがあればその音声ファイルを再生する void handlePlay() { String path = server.uri(); SPIFFS.begin(); if (SPIFFS.exists(path)) { Serial.println("handlePlay: sending " + path); File file = SPIFFS.open(path, "r"); server.streamFile(file, "audio/mp3"); file.close(); } else { server.send(200, "text/html", "<meta charset=\"utf-8\"><h1>text2speech</h1><br>SPIFFS file not found:" + path); } SPIFFS.end(); } // 通知用関数 void notify_messege(String filename) { Serial.println("playfile file:" + filename); if ((WiFi.status() == WL_CONNECTED)) { String mp3url = "http://" + WiFi.localIP().toString() + "/" + filename; Serial.println("GoogleHomeNotifier.play() start"); if (ghn.play(mp3url.c_str()) != true) //GoogleHomeNotifierに通知ファイルのURLを渡す { Serial.print("GoogleHomeNotifier.play() error:"); Serial.println(ghn.getLastError()); return; } } else { Serial.println("Error in WiFi connection"); } } //距離測定関数 int sendTrigger() {  digitalWrite(Trigger_Pin, HIGH); delayMicroseconds(10); digitalWrite(Trigger_Pin, LOW); while (!digitalRead(Echo_Pin)) { } unsigned long t1 = micros(); while (digitalRead(Echo_Pin)) { } unsigned long t2 = micros(); unsigned long t = t2 - t1; Serial.print(340 * t / 20000); Serial.println("cm"); return 340 * t / 20000; } ```