ドアホンが鳴ったらGoogleHome,Slack,Discordに通知する (ESP32)

# ドアホンが鳴ったらGoogleHome,Slack,Discordに通知する

リモートワークなどで玄関から遠い部屋で作業をしていると、ドアホンのチャイムがちょっと聞こえにくいことがありませんか？
これを解決するために、Google HomeやSlack,Discordなどへ通知する方法です。![無題のプレゼンテーション.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/62048052-2fa0-0d7e-c7b5-81846136dd29.jpeg)

## 特徴
* グループ登録すれば複数のGoogle Homeに対してブロードキャストすることができます。家の各所にGoogle Homeを設置すれば死角なしです。
* Discord,Slackへの通知すれば、ヘッドホンをしながらPCを使っていても安心。
* 外出先からもチャイムの確認ができます。
* チャイムが何日何時何分に鳴ったか、長期間のログになります。

## 使用可能なドアホン
作例ではドアホンはPanasonicのVL-MV38を使っています。この機種は「A接点出力」という機能がついており、チャイムが鳴った時に、２接点が短絡します。パナソニックのドアホンのうち、「電気錠制御（外部機器制御）」機能があるものが、おそらくA接点出力を持っていると思います。各製品の説明書、スペック表に明記されていると思うので確認ください。

https://panasonic.jp/door/comparison.html

https://detail-infomation.com/relay-contact/

このA接点出力をESP32で検知し、WIFI経由でGoogle HomeやAPI経由でSlack,Discordにメッセージ通知します。手順がやや複雑なので今回は実装しませんでしたが、LINEなどにも可能と思います。

## ESP32のプログラム
ESP32は、ここではVSCode/PlatformIO環境で説明しています。

```main.cpp
#include <Arduino.h>

#define USE_SLACK
#define USE_DISCORD
#define USE_GOOGLEHOME
#define WIFI_SSID "SSID"
#define WIFI_PWD "PASSWD"
#define SLACK_TOKEN "xoxb-000000000000-0000000000000-XXXXXXXXXXXXXXXXXXXXXXXX"
#define SLACK_CHANNEL "XXXXXXXX"
#define GOOGLEHOME_TARGET "書斎"
#define DISCORD_WEBHOOK "https://discord.com/api/webhooks/000000000000000000/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
#define PIN_SW  GPIO_NUM_27

#include <HTTPClient.h>

#ifdef  USE_GOOGLEHOME
#include <WebServer.h>
#include <LittleFS.h>
#include <esp8266-google-home-notifier.h>
#define MP3FILENAME "chime.mp3"
String  myURL;
char  *mp3Data;
int mp3DataSize;
WebServer server(80);
GoogleHomeNotifier ghn;
#endif

#ifdef  USE_GOOGLEHOME
void webServer(void *) {
  server.on("/", [](){
    server.send(200, "text/html", "OK");
  });
  server.on("/" MP3FILENAME, [](){
    server.send_P(200,"audio/mp3",mp3Data,mp3DataSize);
  });
  server.begin();
  for(;;){
    server.handleClient();
    delay(1);
  }
}
#endif

#ifdef USE_DISCORD
bool DiscordMessage(const char *text)
{
  bool ret = false;
  HTTPClient  http;
  if( http.begin(DISCORD_WEBHOOK) ==false) {
    return false;
  }
  http.addHeader("Content-Type","application/json; charset=utf-8");
  int httpCode = http.POST(String("{\"content\":\"")+text+"\"}");
  if( (httpCode == 200)||(httpCode == 204) ){
    ret = true;
  }
  else{
    Serial.printf("Error %d\n",httpCode);
  }
  http.end();
  return ret;
}
#endif

#ifdef USE_SLACK
bool SlackMessage(const char *text, const char *channel)
{
  bool ret = false;
  HTTPClient  http;
  if( http.begin("https://slack.com/api/chat.postMessage") ==false) {
    return false;
  }
  http.addHeader("Content-Type","application/json; charset=utf-8");
  http.addHeader("Authorization", "Bearer " SLACK_TOKEN);
  String data = String("{\"text\":\"") + text + "\"," + "\"channel\":\"" + channel + "\"}";
  int httpCode = http.POST(data);
  if( httpCode == 200 ){
    ret = true;
  }
  else{
    Serial.printf("Error %d\nret=%s\n",httpCode,http.getString().c_str());
  }
  http.end();
  return ret;
}
#endif

void setup() {
  Serial.begin(115200);

  // connect to WIFI
  WiFi.begin(WIFI_SSID, WIFI_PWD);
  while(WiFi.status() != WL_CONNECTED){
    delay(1000);
    Serial.print(".");
  }
  Serial.println(WiFi.localIP());
#ifdef  USE_GOOGLEHOME
  /* read mp3 data from littleFS */
  if(!LittleFS.begin()){
    Serial.println("An Error has occurred while mounting LittleFS");
    return;
  }
  File file = LittleFS.open("/" MP3FILENAME, "r");
  mp3DataSize = file.size();
  mp3Data = (char *)malloc(mp3DataSize);
  file.readBytes(mp3Data,mp3DataSize);
  file.close();

  myURL = String("http://") + WiFi.localIP().toString() + "/" + MP3FILENAME;
  Serial.println(myURL);
#endif

  pinMode(PIN_SW,INPUT_PULLUP);
  Serial.printf("Poll GPIO-%d\n",PIN_SW);

#ifdef USE_GOOGLEHOME
  if (ghn.device(GOOGLEHOME_TARGET, "ja") != true) {
    Serial.println(ghn.getLastError());
    return;
  }
  xTaskCreatePinnedToCore(webServer, "webserver", 4096, NULL, 1, NULL, 0); //Core 0
#endif
}

void loop()
{
  static int swBefore=0;
  bool  sw = digitalRead(PIN_SW);
  if( sw != swBefore ){
    swBefore = sw;
    Serial.println(sw);
    if(sw == 0){
      Serial.println("チャイムがなりました");
      #ifdef USE_GOOGLEHOME
      ghn.play(myURL.c_str());
      #endif
      #ifdef USE_SLACK
      SlackMessage("チャイムがなりました", SLACK_CHANNEL);
      #endif
      #ifdef USE_DISCORD
      DiscordMessage("チャイムがなりました");
      #endif
    }
  }

  // 60秒以上 WIFI接続が途切れていたらリセットする
  static int ncTimer = 0;
  if (WiFi.status() != WL_CONNECTED){
    ncTimer++;
    if (ncTimer > 60){
      ESP.restart();
    }
    delay(1000);
  }
  else{
    ncTimer = 0;
  }
  delay(10);
}
```

３行目から、環境設定になります。適宜、変更が必要になります。

### 通知先の設定
```main.cpp
#define USE_SLACK
#define USE_DISCORD
#define USE_GOOGLEHOME
```
それぞれ、通知したくない行をコメントアウトします。

### WIFIの設定
```main.cpp
#define WIFI_SSID "SSID"
#define WIFI_PWD "PASSWD"
```
接続するWIFIのSSIDとパスワードに変更します。Google Homeを使う場合は、同一セグメントにGoogle Homeが接続されている必要があります。
Slack,Discordを使う場合は、外部へのネットワーク接続ができる必要があります。

```main.cpp
#define PIN_SW  GPIO_NUM_27
```
ドアホンのA接続を検知するGPIOのピンを指定します。プルアップ入力可能なピンにする必要があります。(GPIO0～33)

https://lang-ship.com/blog/work/esp32-gpio/

## Google Homeの設定
GOOGLEHOME_TARGETに、音声を再生させたいGoogle Homeの名前を書きます。グループ名を指定すると、グループ全体のGoogle Homeが一斉に音声再生します。
```main.cpp
#define GOOGLEHOME_TARGET "書斎"
```

### Google Home Notifierライブラリのインストール
Google Homeを鳴らすために、esp8266-google-home-notifier ライブラリを使います。

https://github.com/horihiro/esp8266-google-home-notifier

PlatformIOでは、PIOHomeのLibrariesからインストールできます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/4ea27c43-4b6c-5250-9ffe-96b797e5ee13.png)

### LittleFSにmp3ファイルを設置する (mp3ファイルを再生したい場合)
今回はGoogle Homeからチャイム音を流したかったため、mp3ファイルを使うことにしました。mp3ファイルはESP32に置き、ESP32自体がWebサーバとなってGoogle Homeからの要求を受け、mp3ファイルを送信します。mp3ファイルはESP32上のフラッシュに LittleFS (ファイルシステム)として予め書き込んでおきます。（SPIFFSは非推奨になったようですので）

手順は下記を参考にしました。

https://randomnerdtutorials.com/esp8266-nodemcu-vs-code-platformio-littlefs/

platform.ini に以下の1行を追加し、LittleFSを使用することを指定します
```platform.ini
board_build.filesystem = littlefs
```

プロジェクトフォルダ直下に、data フォルダを作成し、その下にmp3ファイルを置きます。
プロジェクトフォルダはWindowsの場合、 **（ドキュメントフォルダ)/PlatformIO/Projects/(プロジェクト名)**  がデフォルトです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/0cb20760-42ae-fef2-c2d8-e299fad9ab51.png)

PlatformIOの **PLOJECT TASKS / (ボード名) / Platform / Upload Filesystem Image** をクリックすると、data フォルダ下のファイルが LittleFSに書き込まれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/15952c7e-c357-6a68-958e-b6cd99c0a66b.png)

### 合成音声を再生したい場合
mp3音声ではなく、Google Homeの合成音声を再生させたい場合、LittleFSの設定とmp3ファイルの設置は必要ありません。
loop()内の1行を以下を参考に書き換えてください

```main.cpp
void loop()
{
 ...(略)
      //ghn.play(myURL.c_str());  ← コメントアウトする
      ghn.notify("チャイムがなりました");
```

## Discordへの通知
PCからDiscordのアプリを起動し、メッセージを投稿したいサーバを右クリック、**サーバー設定＞連携サービス** から **ウエブフック** を作成します。
名前、アイコンは適当に。投稿したいチャンネルを選んで、 **[ウェブフックURLをコピー]** します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/80a6fccd-9609-cc47-5984-ff070f8b6363.png)

コピーしたURLで、DISCORD_WEBHOOKを書き換えます。
```main.cpp
#define DISCORD_WEBHOOK "https://discord.com/api/webhooks/000000000000000000/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

## Slackへの通知

Slackのアプリケーションページにアクセスします。（ワークスペースへのログインが必要です）

https://api.slack.com/apps

**[Create New App] - [From scrach]** でアプリを作成します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/025bc2ec-62a7-7ff9-a50b-e70ff0ec9cb5.png)

App Nameはアプリの名前を適当に、投稿したいワークスペースを選択して、**[Create App]** します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/6c126b6e-fbdb-4c5c-b8d0-da33c4418417.png)

左メニューから **Featues** の **OAuth & Permissions** を選び、 **Scopes** / **Bot Token Scopes** の **[Add an OAuth Scope]** をクリックし、 **chat:write** を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/8a5bd5ee-d08e-c1f4-f815-3d10d910b9d0.png)

[Install to Workspace]します。許可を求めてきますので[許可]します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/5eb60825-06bd-04f0-7d43-ecad40823c40.png)

BotのTokenが生成されるのでコピーします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/70e3ed21-96d0-e854-88b0-79aa93c2ed42.png)

コピーしたトークンで、SLACK_TOKENを書き換えます
```main.cpp
#define SLACK_TOKEN "xoxb-000000000000-0000000000000-XXXXXXXXXXXXXXXXXXXXXXXX"
```

## 動作確認
プログラムの設定が終了したら、ESP32から2本、線を出し、1本はGND,1本は プログラムで指定した PIN_SW につなぎます。
プログラムが起動し、WIFIが無事に接続されたら、2本の線をショートさせて、それぞれの通知がうまく動作するかを確認します。

あとは、ドアホンの裏のA接点端子にこの２本線を接続すれば終了です。（向きはないので逆につないでも大丈夫です）

<img width=200 alt="image1png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/a60c9f42-78d6-e354-00ab-adc26f952f81.png">

<img width=200 alt="image2jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221204/8f896fc5-4aca-2521-5b37-1fc78efbc33e.jpeg">