siroitori0413 が 2021年12月05日08時34分16秒 に編集
初版
タイトルの変更
M5ATOMでインターホンを別室から応答できるようにする
タグの変更
M5Atom
SG90
Googlehome
メイン画像の変更
本文の変更
# 別室にいてもインターホンに応答したい! たとえばトイレにいてインターホンが鳴ったとき、応答に手間取っているうちに宅配員さんが不在票を置いて帰ってしまった、そんなことありますよね。 どこにいてもインターホンに出られる仕組みを作りました。 今はスマホ連携インターホンなどあるようですが、そういったインターホンに取り換えるのはお金がかかります。今あるインターホンを生かしつつM5ATOM Liteを使ってなるべく安価にできるよう考えました。 ## 材料 - [M5ATOM Lite](https://www.switch-science.com/catalog/6262/) ×2 (インターホン側と別室側の最低2個ですが、別室側は増設可能です。) - [サーボモーター SG90](https://amzn.to/3rgCQKd) ×1 - [Grove 光センサ V1.1](https://www.switch-science.com/catalog/2854/) ×1 - Google Home Mini 我が家のGoogle Home Miniを使いましたが現在販売されているNestでも同じプログラムで大丈夫かどうかはわかりません。多分大丈夫そうな気はしてますが・・? また、Google Home Miniからは音を鳴らしているだけですので、別にスマートスピーカーである必要性はありません。ただしプログラムの修正が必要にはなります。 # 構成 ![構成](https://camo.elchika.com/e6e37c87a854d3021a63cb3c457e07e1f0cd8a68/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f30613439623061322d303637642d343831662d616535342d6662376130303834316537342f62333437386234652d306666362d343365392d623731322d633132326538636531313262/) 1. インターホンが鳴ります。 2. インターホンの来客を検知します。 我が家では、インターホンの画面がついたかどうかをM5ATOM Lite①に接続している光センサで取得しましたが、インターホンによっては状態が取得できる端子があるものもあるので各インターホンにより調整が必要と思います。 3. 別室のM5ATOM Lite②へWi-Fi経由で無線通知します。このとき②は複数台設置されていたら同時にすべて通知される想定です。 4. 別室側のM5ATOM Lite②のボタンが押されたら、①へ押された通知を戻します。 5. インターホンの通話ボタンをサーボモーターを使って物理的に押して通話状態にします。 6. Google Homeへ指示して「しばらくお待ちください」と発話させます。 # 動画 ![動画の中の部品説明](https://camo.elchika.com/c077b7019cbe9b5f5d86130ba9a1297a4a39e8a8/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f30613439623061322d303637642d343831662d616535342d6662376130303834316537342f30376464396566372d356364332d346630612d616238362d396230323765383461623035/) @[youtube](https://youtu.be/o3LgAxJ8LwM) # プログラム M5ATOM Lite①(インターホン側)とM5ATOM Lite②(別室側)のコードがあり、どちらも**PlatformIO**で作成しています。 Wi-Fi経由でWebSocket通信でやり取りをしています。(ソースコード、通信仕様は中学生の息子が作成しました) # 通信仕様 クライアントはAutoInteractionCentral.local mDNSを解決してWS接続する。 WSパケットはJSON形式で、 ``` サーバー: { "type": "connect" | "physicalSensor1", "subType": "accept" | "value" | "trigger", "value": int } ``` ``` クライアント: { "type": "physicalButton1", "subType": "value" | "trigger", "value": int } ``` の形式を使用する。 ### サーバー側(インターホン側)の実装 Sensor1が更新された場合、type physicalSensor1 subType value value (センサーの値) のパケットを送信する。 Sensor1が検知した場合、type physicalSensor1 subType trigger value 1 のパケットを送信する。 その直後、type physicalSensor1 subType trigger value 0 のパケットを送信する。 ```Arduino:M5ATOM①-インターホン側-main.cpp #include "M5Atom.h" #include "ESPmDNS.h" #include "ArduinoJson.h" #include "ESPAsyncWebServer.h" #include "esp8266-google-home-notifier.h" #include "ESP32Servo.h" //TODO SSIDとパスワードは環境に合わせて設定すること const char* ssid = "XXXXXXXXXXXXXX"; const char* password = "XXXXXXXXXXXXXX"; AsyncWebServer server(80); AsyncWebSocket ws("/"); GoogleHomeNotifier ghn; Servo servo; // クライアント側から受信したときのイベント(クライアント側でボタンが押されたときに発火) void websocketEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ if (type == WS_EVT_CONNECT){ Serial.println("Connection request: " + client->remoteIP().toString()); client->text("{\"type\":\"connect\",\"subType\":\"accept\",\"value\":" + String(client->id()) + "}"); } else if (type == WS_EVT_DISCONNECT){ Serial.println("Client disconnected (" + client->remoteIP().toString() + ")"); } else if (type == WS_EVT_DATA) { DynamicJsonDocument doc(1024); deserializeJson(doc, data); String type = doc["type"].as<String>(); String subType = doc["subType"].as<String>(); int value = doc["value"].as<int>(); if (type == "physicalButton1" && subType == "trigger" && value == 1) { servo.write(0); delay(1000); servo.write(90); if (!ghn.notify("しばらくお待ちください")) { Serial.println(ghn.getLastError()); } } } } void setup() { // put your setup code here, to run once: M5.begin(true, false, true); ESP32PWM::allocateTimer(0); servo.setPeriodHertz(50); // standard 50 hz servo servo.attach(33, 1000, 2000); // Attach the servo after it has been detatched servo.write(90); WiFi.begin(ssid, password); Serial.print("Connecting to wifi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(100); } Serial.println(); ws.onEvent(websocketEvent); server.addHandler(&ws); server.begin(); if (ghn.device("その他", "ja") != true) { Serial.println(ghn.getLastError()); abort(); } MDNS.begin("AutoInteractionCentral"); Serial.println("Initialized"); Serial.println(WiFi.localIP()); } int oldPhysicalSensor1Value = 0; void loop() { // put your main code here, to run repeatedly: // 来客がないか調べて、来客の場合通知する(ここでは光センサーの値を取得して前回の値との差分を見て明るくなったと変化がみられたときにインターホンが鳴ったと判定する) int physicalSensor1Value = analogRead(32); ws.textAll("{\"type\":\"physicalSensor1\",\"subType\":\"value\",\"value\":" + String(physicalSensor1Value) + "}"); if (oldPhysicalSensor1Value + 300 < physicalSensor1Value) { ws.textAll("{\"type\":\"physicalSensor1\",\"subType\":\"trigger\",\"value\":1}"); ws.textAll("{\"type\":\"physicalSensor1\",\"subType\":\"trigger\",\"value\":0}"); } oldPhysicalSensor1Value = physicalSensor1Value; delay(1000); } ``` ■ ポイント esp8266-google-home-notifier.h のライブラリを使用することで、ローカルネットワークにあるGoogle Homeをこんなに簡単に発話させられるんですね。 ``` ghn.device("その他", "ja") ``` とGoogle Homeを登録したときのグループ名の指定をすることで自動的に探してくれるようで、Google HomeのIPアドレスの指定も不要です。 ### クライアント側(別室側)の実装 Button1が押された場合、type physicalButton1 subType trigger value 1 と type physicalButton1 subType value value 1 のパケットを送信する。 Button1が離された場合、type physicalButton1 subType trigger value 0 と type physicalButton1 subType value value 0 のパケットを送信する。 ```Arduino:M5ATOM②-別室側-main.cpp #include "M5Atom.h" #include "ESPmDNS.h" #include "ArduinoJson.h" #include "WebSocketsClient.h" //TODO SSIDとパスワードは環境に合わせて設定すること const char* ssid = "XXXXXXXXXXXXXX"; const char* password = "XXXXXXXXXXXXXX"; WebSocketsClient ws; void websocketEvent(WStype_t type, uint8_t * payload, size_t length) { if (type == WStype_TEXT) { DynamicJsonDocument doc(1024); deserializeJson(doc, payload); String type = doc["type"].as<String>(); String subType = doc["subType"].as<String>(); int value = doc["value"].as<int>(); if (type == "physicalSensor1" && subType == "trigger" && value == 1) { M5.dis.drawpix(0, 0xFF0000); } } } void setup() { // put your setup code here, to run once: M5.begin(true, false, true); WiFi.begin(ssid, password); Serial.print("Connecting to wifi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(100); } Serial.println(); mdns_init(); ws.onEvent(websocketEvent); ws.begin(MDNS.queryHost("AutoInteractionCentral"), 80); // M5ATOM①側で設定した識別子を設定 Serial.println("Initialized"); Serial.println(WiFi.localIP()); } int oldPhysicalButton1Value = 0; void loop() { // put your main code here, to run repeatedly: if (M5.Btn.isPressed()) { M5.dis.drawpix(0, 0x000000); if (oldPhysicalButton1Value == 0) { oldPhysicalButton1Value = 1; M5.dis.drawpix(0, 0x000000); ws.sendTXT("{\"type\":\"physicalButton1\",\"subType\":\"trigger\",\"value\":1}"); ws.sendTXT("{\"type\":\"physicalButton1\",\"subType\":\"value\",\"value\":1}"); } } else { if (oldPhysicalButton1Value == 1) { oldPhysicalButton1Value = 0; ws.sendTXT("{\"type\":\"physicalButton1\",\"subType\":\"trigger\",\"value\":0}"); ws.sendTXT("{\"type\":\"physicalButton1\",\"subType\":\"value\",\"value\":0}"); } } ws.loop(); M5.update(); } ``` ■ ご注意 - クライアント側は起動時にサーバーに接続に行くので、サーバー側を先に立ち上げておかないと失敗します。稼働している途中で接続が切れたときの再接続処理は入れていません。 - クライアントが複数台で行えるようにと考えて作っていますがあまり試していないのでうまくいかないことがあるかもしれません。 # さいごに 動画を見ていただいてもわかるように今はまだインターホンへの固定がセロハンテープで貼ってるだけの状態なので最終的にはこれを3Dプリンタで作ってうまく収納させたいです。 また、現在別室側に通知されたときにM5ATOMのLEDが光って表示されるものの音が鳴らないので、来客を知るにはインターホンでなっている音が頼りになります。 別室側にも音が鳴る仕組みを実装したいと思っています。