ESP8266 から Google Home に喋らせるライブラリ

再発明シリーズ第二弾。
**[前回](https://qiita.com/horihiro/items/d64b699d06605ad44646)**のパクリ元は、**[node.js モジュールの google-tts-api](https://github.com/zlargon/google-tts)** でしたが、今回も 同じく **[node.js モジュールの google-home-notifier](https://github.com/noelportugal/google-home-notifier)** を全力でパクります。

**[horihiro/esp8266-google-home-notifier](https://github.com/horihiro/esp8266-google-home-notifier)**

# できること
処理内容もオリジナルとほぼ同じで、

1. **[mDNS](https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266mDNS)** で同じネットワークにある Google Home を探す
2. **[esp8266-google-tts](https://www.arduinolibraries.info/libraries/esp8266-google-tts)** でテキストから mp3 の URL を作る
3. mp3 の URL を Google Home に投げつける

これだけです。

最後の「投げつけ」が castv2 というプロトコルが必要なので、ここの実装は必要でしたが、オリジナルの **[google-home-notifier](https://github.com/noelportugal/google-home-notifier)** に同梱されている **cast_channel.proto** という castv2 プロトコルの定義ファイルと **[nanopb](https://github.com/nanopb/nanopb)** のおかげで、ゼロから頑張る必要がありませんでした。

## 作例集
\# 全部自分じゃないですが。。。

 - Debashish Sahu @ YouTube
   - https://www.youtube.com/watch?v=aWBr0WYP6Zs
 - mgo-tec電子工作
   - https://www.mgo-tec.com/blog-entry-google-home-notifier-m5stack-esp32.html
   - https://www.mgo-tec.com/blog-entry-bme680-graph-m5stack-gas-alarm.html
 - DSAS開発者の部屋 @ KLabBlog
   - http://dsas.blog.klab.org/archives/2018-08/52297379.html
   - http://dsas.blog.klab.org/archives/2018-08/52297568.html

## 注意事項
~~EPS32 では動作しません。依存ライブラリの関係上、コンパイルに失敗します。~~
バージョン 1.0.2 (2018/06/08 公開) 以降で ESP32 に対応しました。

# インストール
今回も調子に乗って、ライブラリ マネージャーに**[登録申請しました](https://github.com/arduino/Arduino/issues/7547)**。
申請が通れば、Arduino IDE からインストール可能です。

[GitHub リポジトリ](https://github.com/horihiro/esp8266-google-home-notifier)から [zip 形式](https://github.com/horihiro/esp8266-google-home-notifier/archive/master.zip)でダウンロードし、[スケッチ] - [ライブラリをインクルード] - [.ZIP形式のライブラリをインストール...] からインストーるすることもできると思います。
試してませんが。

<img src="https://qiita-image-store.s3.amazonaws.com/0/90361/012bb03a-44d9-18b6-fc79-adc856e70142.png" width="600px">


上記の通り、**[esp8266-google-tts](https://www.arduinolibraries.info/libraries/esp8266-google-tts)** を使っているので、このライブラリもライブラリ マネージャーからインストールしてください。

----(2018/05/11 追記)----
申請が通ったので、Arduino IDE からインストール可能になりました。
検索ボックスに「esp8266-google」と入力すると出てきます。
**[esp8266-google-tts](https://www.arduinolibraries.info/libraries/esp8266-google-tts)** も併せてどうぞ。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/90361/6a4c34a9-8b58-ba1c-a8e6-68f74279de98.png)
----(追記ここまで)----

**※以下の手順は、バージョン 1.0.2 (2018/06/08 公開) 以降では不要です。**

また、ESP8266 のボード ライブラリのバージョン **[2.4.1](https://github.com/esp8266/Arduino/releases/tag/2.4.1)** 以前に同梱されている **[mDNS](https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266mDNS)** では、指定の Google Home を特定するために使っている TXT レコードが取得できないので、**[mDNS の最新版](https://github.com/mblythe86/Arduino/tree/master/libraries/ESP8266mDNS)** (ESP8266mDNS.cpp/ESP8266mDNS.h) を、このライブラリをインストール後に、`src` ディレクトリ [^srcdir] に保存してください。

以下のような配置になります。

```
$LIBRARIES_DIR
  └── esp8266-google-home-notifier/
      ├── LICENSE
      ├── README.md
      :
      └── src/
          ├── ESP8266mDNS.cpp  # <- これを追加
          ├── ESP8266mDNS.h    # <- これも追加
          :
          ├── esp8266-google-home-notifier.cpp
          └── esp8266-google-home-notifier.h
```

[^srcdir]: Windows : %USERPROFILE%\Documents\Arduino\libraries\esp8266-google-home-notifier\src<br>macOS : ~/Documents/Arduino/libraries/esp8266-google-home-notifier/src

この ESP8266mDNS は、ESP8266 のボード ライブラリのバージョン 2.5.0 で[マージされる予定](https://github.com/esp8266/Arduino/pull/3107)なので、2.5.0 がリリースされれば、この追加作業は不要です。

# 使い方
オリジナルとほぼ同じにしています。

1. `device` で Google Home を探す<br>
　※第一引数に Google Home の名前<br>
　　<img width="300" src="https://qiita-image-store.s3.amazonaws.com/0/90361/5cc13afe-b842-f572-da7c-2683af5a5c4a.png"><br>
　※第二引数に TTS 用の言語情報<br>
　　日本語をしゃべらせたい場合は `ja`<br>
　　省略した場合は `en` 
2. `notify` で、喋らせたいテキストを指定して実行

という感じです。
失敗した時は `getLastError` をすると、何かわかるかもしれません。

# サンプル
\# 上記の [作例集](#作例集) の方が実践的で参考になります。

## 一言喋るだけのサンプル

```c++
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <esp8266-google-home-notifier.h>

const char* ssid     = "<REPLASE_YOUR_WIFI_SSID>";
const char* password = "<REPLASE_YOUR_WIFI_PASSWORD>";

GoogleHomeNotifier ghn;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println("");
  Serial.print("connecting to Wi-Fi");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(250);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //Print the local IP
  
  const char displayName[] = "ファミリー ルーム";

  Serial.println("connecting to Google Home...");
  if (ghn.device(displayName , "ja") != true) {
    Serial.println(ghn.getLastError());
    return;
  }
  Serial.print("found Google Home(");
  Serial.print(ghn.getIPAddress());
  Serial.print(":");
  Serial.print(ghn.getPort());
  Serial.println(")");
  
  if (ghn.notify("Hello, World!") != true) {
    Serial.println(ghn.getLastError());
    return;
  }
  Serial.println("Done.");
}

void loop() {
  // put your main code here, to run repeatedly:

}
```
## Web フォームからテキストを指定して喋らせるサンプル
\# Web フォームはおまけなので、全く力を入れていません。

```c++
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <esp8266-google-home-notifier.h>

const char* ssid     = "<REPLASE_YOUR_WIFI_SSID>";
const char* password = "<REPLASE_YOUR_WIFI_PASSWORD>";

ESP8266WebServer server(80);
GoogleHomeNotifier ghn;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println("");
  Serial.print("connecting to Wi-Fi");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(250);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //Print the local IP
  
  const char displayName[] = "ファミリー ルーム";

  Serial.println("connecting to Google Home...");
  if (ghn.device(displayName , "ja") != true) {
    Serial.println(ghn.getLastError());
    return;
  }
  Serial.print("found Google Home(");
  Serial.print(ghn.getIPAddress());
  Serial.print(":");
  Serial.print(ghn.getPort());
  Serial.println(")");
  
  server.on("/speech", handleSpeechPath);
  server.on("/", handleRootPath);
  server.begin();
 }

void handleSpeechPath() {
  String phrase = server.arg("phrase");
  if (phrase == "") {
    server.send(401, "text / plain", "query 'phrase' is not found");
    return;
  }
  if (ghn.notify(phrase.c_str()) != true) {
    Serial.println(ghn.getLastError());
    server.send(500, "text / plain", ghn.getLastError());
    return;
  }
  server.send(200, "text / plain", "OK");
}

void handleRootPath() {
  server.send(200, "text/html", "<html><head></head><body><input type=\"text\"><button>speech</button><script>var d = document;d.querySelector('button').addEventListener('click',function(){xhr = new XMLHttpRequest();xhr.open('GET','/speech?phrase='+encodeURIComponent(d.querySelector('input').value));xhr.send();});</script></body></html>");
}

void loop() {
  // put your main code here, to run repeatedly:
  server.handleClient();
}
```

# 今後
とりあえず、ESP8266 と Google Home で任意(多分)の音声出力が可能になりました。
さて、これで何ができるやら。

----