IFTTTを使わずActions on Google(Dialogflow)でGoogle Homeから家電を音声操作してみる


# はじめに
[前回記事(Google Homeに話しかけてWindowsを操作してみる)](https://qiita.com/miso_develop/items/7a02908aad2a5cf192d0)までで電気、テレビ、PS4、Windowsと家にある家電を大体Google Homeより音声操作できるようにしました。
ただ毎回「OK Google」というウェイクワードが面倒だったり、IFTTT経由だとラグが大きかったりと不満が出てきます。

今回は「Actions on Google(Dialogflow)」を使って音声操作することでそういった不満を解消してみようと思います。

なお当記事では過去記事にて構築していた音声操作環境がベースになっており、IFTTTを使っていた部分のActions on Google(Dialogflow)への置き換えについて説明します。



# 処理の流れ
![image](https://qiita-image-store.s3.amazonaws.com/0/207109/3e519491-c485-9789-c245-3e4c7c757bcd.png)

過去記事にて構築してきた音声操作環境からIFTTTがActions on Googleに置き換わります。



# 環境
* Google Home
* Raspberry Pi3 (Raspbian Lite 9.1)
    * Node.js (v8.6.0)
* Windows10 Pro
    * Node.js (v6.11.4)



# Actions on Googleプロジェクトの作成
「[Actions on Google](https://developers.google.com/actions/)」でできることはプロジェクトの作成、管理、テスト、リリースになります。
IFTTTでやっていた、「○○って言ったら××をする」的なことはプロジェクト内のActionsで選んだサービスにて実装します。

とりあえずプロジェクトを作成してみましょう。
[コンソール画面](https://console.actions.google.com/u/0/)より「Add/import project」を選択してプロジェクトを作成してみて下さい。
なお、以降ところどころで認証を求められるので適宜許可していって下さい。

![Actions on Google_20171102_235504.png](https://qiita-image-store.s3.amazonaws.com/0/207109/e6007a32-fd28-e1a8-3260-e370888ef51c.png)

すると「Overview」画面が開かれます。
ここにある「Dialogflow」「Actions SDK」「Converse.AI」が音声操作の実装部になります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/207109/6ab66797-9678-b24f-effc-d1f7650d84eb.png)

今回は「Dialogflow」を使います。
Dialogflowのカード右下の「BUILD」→ポップアップから「CRETE ACTIONS ON DIALOGFLOW」と選択していきます。
するとDialogflowのページへ飛びます。

![Actions on Google_20171102_235543.png](https://qiita-image-store.s3.amazonaws.com/0/207109/2d2d2640-a51a-c628-02a2-b19261f64d17.png)



# Dialogflow Agentの作成
ここから先はDialogflowのページにて操作を行っていきます。
Dialogflowでは「Agent」という単位で実装を行っていきます。
Agent＝プロジェクトという認識でいいと思います。

Actions on Googleから遷移してきてすぐの画面が早速Agent作成画面になります。
Agent名や「GOOGLE PROJECT」等勝手に埋めてくれてるので「DEFAULT LANGUAGE」あたりを「Japanese - ja」に変えてSAVEしちゃいましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/207109/51052018-b0f9-cc21-a81f-fc5ff87ac84e.png)

## Dialogflowの設定項目
Dialogflowの画面左側に色々項目があります。
ここで必要な項目について簡単に説明しておきます。

### Intents
ここで「○○って言ったら××をする」ってのを定義します。

### Entities
ここは言葉の呼び方を定義します。
例えば「お腹空いた」という言葉は他にも色んな言い方があります。
「腹減った」とか「お腹ペコペコ」とか。
そういった色んな言い方をしても意味は「お腹空いた」だよっていうのを定義します。
様々な言い方を一つの言葉に集約させるんです。

### Integrations
Dialogflowに設定したAgentを他のサービス（今回はGoogle Assistant）へ使えるよう反映します。
Agentを作ったり、更新したときに操作します。

### Fulfillment
Webhookや「Cloud Functions for Firebase」のInline Editorで返答結果をがちゃがちゃするのに使います。

では以上を踏まえ、Agentを設定してみましょう。

## Entityの作成
まずはEntityを2つ作ります。
「○○を××して」の「○○」に相当する「target」と、「××」に相当する「action」って名前のEntityです。
「target」は「テレビ」とか「電気」とかです。
「action」は「起動」とか「スタンバイ」とかになります。

左側のカラムに集約させたい言葉、右側のカラムに色んな言い方を思いつく限り入力していきます。

### target
![image.png](https://qiita-image-store.s3.amazonaws.com/0/207109/f600bb7a-10d9-677c-306f-7aabe36eb0b8.png)

### action
![image.png](https://qiita-image-store.s3.amazonaws.com/0/207109/f89a51a3-d8e9-08f9-6fc4-b479ca4fc305.png)

## Fulfillmentの設定
Webhookを「ENABLED」へ切り替え、URLに「`https://dummy/`」みたいにダミーのURLを入力してSAVEして下さい。
こうしないとIntentの設定時にFulfillmentの項目が設定できないからです。
ここのURLは後ほどFirebaseでWEB APIを作成し、それに置き換えます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/207109/11d099e7-6f3a-f793-2d1f-3beb1c3a6a4f.png)

## Intentの作成
Intentで設定すべき項目は3箇所です。
「Intent名」「User says」「Fulfillment」です。

「Intent名」は適当になんか入れて下さい。
「Fullfillment」は「Use webhook」にチェックつけるだけです。

「User says」は「○○って言ったら××をする」の「○○」の部分の定義です。
ここでは先ほど作ったEntityを使用します。
ここでのEntityの指定方法がちょっと一癖あります。

* まず先ほど作成した「target」と入力します
* 次に入力した「target」の文字を全て選択してハイライトます（文字をダブルクリックしたりとか）
* するとポップアップが出てきて@から始まる変数っぽいのの一覧が出ます
* ここから「@target」を選択します
* すると選択していた「target」の文字が黄色くハイライトされます

以上の方法で「action」のEntityも指定してしてみて下さい。
Entityでは記述しきれなかった接続詞を含めたパターンをいくつか定義してみます。

一通り設定したらSAVEします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/207109/895a9315-562a-f324-8b77-efd9ffea4699.png)



# Firebase Funcitonsの作成
FulfillmentのWebhookに設定したURLへのアクセスはPOSTになります。
そうすると今までIFTTTでやっていたようにPUTでDatabaseを更新することができません。
じゃあどうするかというと「[Firebase Functions](https://firebase.google.com/docs/functions/?hl=ja)」を使い、POSTされたデータをDatabaseへ書き込むWEB APIを作成します。
※DialogflowのInline Editorではありません！

今までの記事ではFirebaseのRealtime Databaseの機能のみを使用していました。
今回はプラスでFunctionsの機能も使用します。

Functionsを作成するためには「firebase-tools」というNode.jsモジュールの管理ツールを使用します。

## firebase-toolsの導入
ここからはメインPCのWindowsで操作していきます。
まずは以下のコマンドでfirebase-toolsをインストールします。

```
npm install -g firebase-tools
```

次に以下のコマンドでFirebaseへのログインを行います。
ウインドウが立ち上がって認証を求められるので許可してって下さい。

```
firebase login
```

そして適当なフォルダをきって以下のコマンドを実行し、Firebase Functionsのプロジェクトを作成します。
途中プロジェクトの選択を求められたりするので適宜選択していって下さい。

```
firebase init functions
```

するとファイルやフォルダが作成されたかと思います。
その中の`funcions/index.js`にWEB APIの処理を記述していきます。

## WEB API処理(index.js)の作成
ソースは以下になります。

内容は単純で、DialogflowからのEntityのパラメータを受け取ってDatabaseへ書き込みます。
同時にDialogflowへ`speech`と`displayText`の情報を返します。

`speech`はGoogle Homeが喋る言葉、`displayText`はスマホとかでGoogle Assistantが返すテキストになります。

```js:index.js
const functions = require("firebase-functions")
const admin = require("firebase-admin")
admin.initializeApp(functions.config().firebase)

//WEB API処理
exports.test = functions.https.onRequest((request, response) => {
    
  //Dialogflowからのパラメータ取得
  const target = request.body.result.parameters.target
  const action = request.body.result.parameters.action
  
  //Databaseへの書き込み情報
  const path = "/googlehome"
  const word = target + " " + action
  
  //Dialogflowへ`speech`と`displayText`の情報を返す
  response.setHeader("Content-Type", "application/json")
  response.send(
    JSON.stringify({
      "speech": "OK", "displayText": word
    })
  );
  
  //Databaseを更新
  admin.database().ref(path).set({word: word})
});
```

## Firebaseへのデプロイ
index.jsができたら以下のコマンドにてデプロイします。

```
firebase deploy --only functions
```

少し時間がかかりますがしばらくすると完了し、出力結果にWEB APIのURLがでてきます。
このURLをコピっておきます。

![Dialogflow_20171103_002000.png](https://qiita-image-store.s3.amazonaws.com/0/207109/5da38b42-f48d-b230-bd44-feb0ea0d434a.png)

## DialogflowのFulfillment設定
続いてDialogflowのFulfillmentの設定画面に戻ります。
WebhookをENABLEDに切り替え、URLに↑にてコピったWEB APIのURLを入力し、SAVEします。

## Integrationsの設定
最後にIntegrationsよりDialogflowの設定をActions on Googleへ反映します。
Google Assistantをクリックするとポップアップウインドウが表示されますので、「Select Intent」より作成したIntentを選択します。
「Additional triggering intents」にIntentが表示されたら右下にある「AUTHORIZE」をクリックして認証通したあとに、「UPDATE DRAFT」をクリックしてDialogflowの設定は完了です。

![Dialogflow_20171103_004709.png](https://qiita-image-store.s3.amazonaws.com/0/207109/7549de31-7bd1-6d7a-acea-cbbb8129a978.png)

UPDATEが成功したらまたポップアップがでてくるので右下にある「VISIT CONSOLE」よりActions on Googleの画面へ戻りましょう。



# Actions on Googleの設定
Actions on Googleへ戻るとアプリケーションの情報入力を求められます。
Assistantアプリを呼び出す時の名称とか決めたかったら入力しましょう。
無視して次へ進めることもできます。
その場合アプリ名称は一時的に「テスト用アプリ」となります。

そして最後に「TEST DRAFT」をクリックしてみましょう。
Simulator画面へ進みます。
※ちなみに隣にある「SUBMIT DRAFT FOR REVIEW」を選択するとGoogleの審査の後作ったAssistantアプリをリリースすることができます

![Actions on Google_20171103_004756.png](https://qiita-image-store.s3.amazonaws.com/0/207109/039ee416-2ddd-f23f-e3c2-d98e1b7d32b9.png)

ここまで来ればGoogle Homeの実機や、スマホのGoogle Assistantから作成したActions on Googleアプリが使えます。
もちろんSimulatorからテストもできるので、思い思いの方法でテストしてみましょう。

ちなみにアプリを終了するときは「停止」と言うと終了されます。
また、終了用のIntentの作成もできます。



# おわりに
アプリに正常につなげたときに「テストバージョンです」とか言われてしまうのがちょっとアレですが、これはしょうがないです。

とはいえIFTTTからActions on Googleに移行(Assistantアプリ化)したことにより以下のメリットがありました。

* 「OK Google」をはじめに一度だけ言えばよくなった
* IFTTTに比べ応答速度が改善された
* IFTTTでは操作対象（電気、テレビとか）が増える度にAppletを作成していたが、DialogflowのEntityの管理で済むようになった
* 言い回しが複数ある場合はindex.jsに複数パターンの記述が必要だったが、DialogflowのEntityで一元管理できるようになった

なおデメリットとしては以下になります。

* 起動するたび「テストバージョンです」と言われてしまう
* Assistantアプリ起動中はあらゆる音声を「OK Google」なしで受け付けてしまうため、騒がしい環境ではGoogle Homeがテンパる
    * 我が家は2歳の娘の騒ぎ声と0歳の長男の泣き声でGoogle Homeがかなりテンパってしまいます
* しばらく話しかけないでいると終了してしまう

結論としては、応答速度の改善やEntityによるワード管理はいい感じなのですが、うちは子供の騒ぎ声でGoogle Homeがテンパりまくってしまい、使い物になりませんでした。

ただGoogle AssistantアプリではカードタイプのGUIが使えるようだったり、Firebaseとの連携も面白かったので、ホームオートーメーション化も一区切りして今度は簡単なAssistantアプリとかも作ってみたいなと思います。



# 参考記事
[Actions on Googleでapi.aiを使ってGoogle Homeに何か言わせてみる](https://qiita.com/syarihu/items/53ea1a65f481f8121109)
[GoogleHomeで使える（not 有用）アプリの作り方](https://qiita.com/out_of_kaya/items/d1158dad435f17dcdab7)
[Build Your First App with Dialogflow](https://developers.google.com/actions/dialogflow/first-app)
[Firebase はじめに: 最初の関数の記述とデプロイ](https://firebase.google.com/docs/functions/get-started?hl=ja)