Google Homeのスキルを自動起動するトリガーデバイスの作製


Amazon Echo(Alexa)には[定型アクション](https://www.amazon.co.jp/gp/help/customer/display.html?nodeId=202203870)というものがあり，定刻にスキルを自動起動できる仕組みがありますが，Google Home(Assistant)には無いようです．

そこで，音声合成LSIとESP8266を使って，Google Homeのスキルを自動起動するトリガーデバイスを作ってみました．これを使うと次のようなことが可能になります．

* 1時間ごとに時刻を教えてもらう
* 朝9時にニュースを読み上げてもらう
* 風邪で声が出ない時に，ボタンを押して今日の予定を教えてもらう

ソースはこちらに置いてあります．
[https://github.com/ottijp/google-home-trigger](https://github.com/ottijp/google-home-trigger)

## 実行の様子

[![](http://i.ytimg.com/vi/WjKQB-7Jg08/mqdefault.jpg)
（クリックで動画を表示）](https://youtu.be/WjKQB-7Jg08)


トリガーデバイス（右側）に「OK Google，今日の東京の天気は？」と喋らせて，スキルを起動しています．
コマンドはリモートからMQTTのメッセージとして発行しています．（後述）

## 構成

![google-home-trigger.png](https://qiita-image-store.s3.amazonaws.com/0/35592/1b1e330d-16a4-fd98-a720-dcb053e0f567.png "google-home-trigger.png")

* ESP8266でAWS IoTのMQTTブローカにつないでおき，MQTTパブリッシャから発行されたメッセージを音声合成LSIで再生します
* AWS IoTはサーバ・クライアント双方の認証ができるMQTTS(MQTT on SSL)ブローカとしてデバイスゲートウェイが使いたかっただけで，デバイスシャドウやルールエンジンなどは使っていません．

トリガーデバイスの作製に使った部品は主にこちらの3つです．

* 音声合成LSI [ATP3011F4-PU](http://akizukidenshi.com/catalog/g/gI-05665/)
* ESP8266搭載モジュール [ESP-WROOM-02](http://akizukidenshi.com/catalog/g/gK-09758/)
* スピーカ 8Ω 0.5W φ50mm
	* 秋月でP-04656という通販コードのものを使いましたが，現在はもう売っていないようです
	* [これ](http://akizukidenshi.com/catalog/g/gP-09013/)が近いと思います
	* ちゃんと音を出すにはアンプのついたスピーカを使うのが良いと思いますが，Google Homeの横でちょっと音がなればよいので，ダイナミックスピーカを使いました．増幅の仕方は後述．

## 音声合成LSI

スピーカをつなぎ，UART/I2C/SPIのいづれかから特定のフォーマットで読み上げる文字列を送信することで，その文字列を音声として合成してくれます．

回路の組み方や文字列のフォーマットは[こちらの説明書](http://akizukidenshi.com/download/ds/a-quest/atp3011f4_datasheet.pdf)を参照してください．

### 合成音声例

* `ki'ita/ni,ki'ji/woto-ko-suruyo-`
	* 「Qiitaに記事を投稿するよー」
	* [こんな風](https://soundcloud.com/ottijp/speech-synthesis-qiita-kiji)に合成されました
* `ohayo-gozaima'su/,kyo'-mo/ge'nkini/ganbarimasyo'-`
	* 「おはようございます．今日も元気にがんばりましょう」
	* [こんな風](https://soundcloud.com/ottijp/speech-synthesis-ohayou)に合成されました

### 音の増幅

大きな音を鳴らすには，アンプの付いたスピーカを使うほうが良いようですが，今回はGoogle Homeの真横において使う想定だったので，トランジスタのみを使い簡単に増幅した信号を直接ダイナミックスピーカにつなぎました．
[さきほどの説明書](http://akizukidenshi.com/download/ds/a-quest/atp3011f4_datasheet.pdf)のp.14「13. 付録」にある「ローコストD級アンプ」の回路を適用しています（トランジスタのベース抵抗は3.3kΩ．）回路全体は後述します．
ただ，それでもダイナミックスピーカ単体では音が小さかったり割れたりして認識されないことも多かったので，電球の空箱を利用して簡易なエンクロージャを作ってみました（正しい作り方がわからなかったので適当ですが．）

![enclosure1.png](https://qiita-image-store.s3.amazonaws.com/0/35592/31951770-65db-b39f-ba76-e0a5fb12e936.png "enclosure1.png") ![enclosure2.png](https://qiita-image-store.s3.amazonaws.com/0/35592/80f08e24-a796-087e-84eb-5d0b01cd877f.png "enclosure2.png")

## ESP8266

簡単に説明するとWi-FiにつながるArduinoです．
Arduino IDEでプログラミングして書き込むことで単体でネットワーク接続＆周辺機器操作も可能です．

### MQTTSに接続する方法

お試し実装ですが，セキュリティは確保したかったので，MQTTSでの接続を実装しました．
なお，ライブラリとして以下の2つを使っています．

* [ESP8266WiFi](https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi)
* [MQTT](https://github.com/256dpi/arduino-mqtt)

AWS IoTの設定例は他に譲りますが，認証情報には次のようなポリシーを設定しました．

```javascript:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}
```

AWS IoTで作成した証明書をESP8266で使えるようにするために，次のように変換しておきます（PEMからDERへ変換．）
クライアント秘密鍵，クライアント証明書ファイル，CA証明書ファイルのファイル名はAWS IoTでダウンロードしたものを指定してください. 


```sh:
# クライアント秘密鍵をDER形式に変換
openssl rsa -inform PEM -in 06bb2b8e40-private.pem.key -outform der -out 06bb2b8e40-private.der.key

# クライアント証明書をDER形式に変換
openssl x509 -inform PEM -in 06bb2b8e40-certificate.pem.crt -outform der -out 06bb2b8e40-certificate.der.crt

# CA証明書をDER形式に変更
openssl x509 -inform PEM -in VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem -outform der -out VeriSign-Class%203-Public-Primary-Certification-Authority-G5.der
```

そして変換されたファイルのデータをchar[]リテラルとして，それぞれ`ClientKey.ino`，`ClientCert.ino`，`CACert.ino`に記述します．

```c:ClientKey.ino
const unsigned char clientKey[] PROGMEM = {
0x30, 0x82, 0x04, 0xa5, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa0, 0x05, 0x37, 0x80,
（中略）
0x87, 0x46, 0x3f, 0x30, 0x95, 0xd3, 0xe3, 0x90, 0x43
};
const unsigned int clientKeyLen = 1193;
```

```c:ClientCert.ino
const unsigned char clientCert[] PROGMEM = {
0x30, 0x82, 0x03, 0x59, 0x30, 0x82, 0x02, 0x41, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x22,
（中略）
0x92, 0x68, 0xcc, 0x1c, 0x9e, 0x95, 0x66, 0xf9, 0x6c, 0x1a, 0xfc, 0xcf, 0x93
};
const unsigned int clientCertLen = 861;
```

```c:CACert.ino
const unsigned char caCert[] PROGMEM = {
0x30, 0x82, 0x04, 0xd3, 0x30, 0x82, 0x03, 0xbb, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x18,
（中略）
0xac, 0x11, 0xd6, 0xa8, 0xed, 0x63, 0x6a
};
const unsigned int caCertLen = 1239;
```

これを，`WiFiClientSecure`の`setPrivateKey_P`，`setCertificate_P`，`setCACert_P`メソッドで設定すればOKです．

```c:google-home-trigger.ino
  // DERフォーマットでSSLに必要な情報を読み込み
  // CA証明書
  bool res = sslClient.setCACert_P(caCert, caCertLen);
  if (!res) {
    Serial.println("Failed to load CA certificate!");
    while (true) {
      yield();
    }
  }
  // クライアント証明書
  res = sslClient.setCertificate_P(clientCert, clientCertLen);
  if (!res) {
    Serial.println("Failed to load client certificate!");
    while (true) {
      yield();
    }
  }
  // クライアント秘密鍵
  res = sslClient.setPrivateKey_P(clientKey, clientKeyLen);
  if (!res) {
    Serial.println("Failed to load client key!");
    while (true) {
      yield();
    }
  }
```

ソース全体は[リポジトリ](https://github.com/ottijp/google-home-trigger)を参照してください．
AWS IoTのMQTTブローカはTLS1.2にしか対応していませんが，この`WiFiClientSecure`はTLS1.2対応しているようです（ライブラリのバージョンはv1.0で確認．）

## 回路とブレッドボード実装

次のように作りました．Fritzingのデータは[リポジトリ](https://github.com/ottijp/google-home-trigger)に含めてあります．

![](https://qiita-image-store.s3.amazonaws.com/0/35592/c1747fdb-0032-6e46-e1f8-68434137165d.png "circuit_schem.png")

* J1の電源は4.5VをACアダプタで入れました
* ATP3011F4-PUへのコマンドの送信はESP8266のUART1を使っています
    * UART1はTXしかないため，ATP3011F4-PUからの応答は無視しています
    * UART0はプログラムの書き込みに使うので，開発時につなぎ替えるのが面倒だったからです
    * 本当はちゃんとATP3011F4-PUからの応答を解釈すべきだと思います
* ESP8266の起動タイミングにあわせてATP3011F4-PUを起動するためにIO14ピンでATP3011F4-PUのRESETをコントロールしています（しないとUART1にノイズが出てATP3011F4-PUのボーレートが正しく設定されない）

![](https://qiita-image-store.s3.amazonaws.com/0/35592/30f0988f-0e25-db1b-4868-336f93b5a5fc.png "circuit_bb.png")

* 黄色いワイヤはプログラムの書き込み時はLOWにつなぎ替えます

## 実行方法

次のようなコマンドをMQTTパブリッシャ側で実行すると冒頭の動画のようになります．

```sh:
$ echo -n "o'-ke-gu-guru. asunoto-kyo-note'nki/wa'" | mosquitto_pub -h <自分のAWS IoTのエンドポイント> -p 8883 -t "google-home-01" -s --cafile VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem --cert 06bb2b8e40-certificate.pem.crt --key 06bb2b8e40-private.pem.key -d -q 1
```

* クライアント秘密鍵，クライアント証明書ファイル，CA証明書ファイルのファイル名はAWS IoTでダウンロードしたものを指定してください
* 注意：`-q 1`をしないとうまく届かない事象が頻発しました．

## 応用

今回は手動でMQTTのパブリッシュを行う方法でしたが，ここは自動化できるので，

* cronによる自動実行
* IFTTTやLambdaを使ったイベントベースのパブリッシュ

など応用が効きそうです．

## 参考

* [AWS IoT Message BrokerのMQTTでpub/subをやってみた #reinvent ｜ Developers.IO](https://dev.classmethod.jp/cloud/aws/pub-sub-with-aws-iot-over-mqtt/)
* [ESP-WROOM-02-AE (with Akizuki-Electronics breakout board) - parts submit - fritzing forum](http://forum.fritzing.org/t/esp-wroom-02-ae-with-akizuki-electronics-breakout-board/1753)
* [ESP8266 MAC Address | Imazeki's Workshop](http://musicdevice.net/iot/2015/11/25/esp8266-mac-address/)
* [音声合成LSI用のオーディオアンプ(再考) | N.Yamazaki's blog](http://blog-yama.a-quest.com/?eid=970162)
* [マイコンの実験：音声合成LSI(ATP3011F4-PU)の実験](http://www.geocities.jp/zattouka/GarageHouse/micon/TalkIC/ATP3011.htm)