スマートスピーカーとdialogflowを使って〇〇ライフをちょっと捗らせる話

この記事は
「DeNA IPプラットフォーム事業部 Advent Calendar 2017」
14日目の記事です。

こんにちは。 @hirashunshun です。
[マンガボックス](https://www.mangabox.me/)という、スマホ向け電子書籍サービスのサーバーサイド/フロントエンドの担当をしています。
あと、狂ったように[パデル](https://www.youtube.com/watch?v=vhVsoQmnAAg)を愛するパデラーです。社内では名前よりも**パデル**と呼ばれることが多いです。

# はじめに
2017年は一般家庭への普及という点では間違いなくスマートスピーカー元年となりました。
そんな良き年の瀬にGoogle Home Miniが3000円で手に入るという幸運に恵まれましたので、
IoT機器との連携や簡単なAssistantアプリ作成の過程ご紹介したいと思います。
皆様のライフが捗るご参考になれば幸いです。

おしながきはこちらです。

- 家電と連携する
    - テレビと
    - 電球と
- Google Assistant対応アプリを作ってみる
    - パデルコートの予約状況を確認する

では行ってみましょう！

# 家電と連携する
## テレビと
自宅のテレビがAndroidが内蔵された[AndroidTV](https://www.android.com/intl/ja_jp/tv/)なので、
Google Home Miniをセットアップするときに使った[Google Homeアプリ](https://play.google.com/store/apps/details?id=com.google.android.apps.chromecast.app&hl=ja)を使うことで連携可能でした。
同一のネットワークに接続すれば、自動でHomeアプリがテレビを検出してくれてそのままセットアップ完了です。

一点注意ですが、このセットアップ完了の時点ではGoogle Homeに「テレビをつけて」と語りかけてもテレビを認識することができません。
私の場合、初期の名称が型番である「KJ-...」になっていたことが原因で、
テレビのホームメニューから設定->端末情報->端末名->変更する
で「テレビ」に名称変更することで無事にGoogle Homeから認識されるようになりました。
![Screenshot_20171214-084224.jpg](https://qiita-image-store.s3.amazonaws.com/0/8296/6670ed2e-76f4-d66a-d3da-ff108ee09e38.jpeg)


いままでテレビでYouTube等の動画を見るには、

- 手元のスマホで探してからCastボタン
- テレビのリモコンでYouTubeアプリを開いて探して・・・

と手間がかかりましたが、
Google Homeがあれば、「おっけーグーグル、テレビで〇〇の動画を流して」といえば再生を開始してくれるので、
ご飯中でも洗濯物を干してる最中でも、布団に入ってからでもすぐに再生することができます。

実際の動画が[こちら](https://www.youtube.com/watch?v=eVeewdJL47g)です。


ほんの若干捗った気がします。ってかそれパズルやんけ。
テレビという単語がゲシュタルト崩壊する前に次に進みたいと思います。

## 電球と
スマートライトというと話は早いでしょうか。
世間では[hue](http://www2.meethue.com/ja-jp/)が有名ですが、私は[LIFX](https://jp.lifx.com/)を数年前から愛用しています。
hueは親機(ハブ)を中心に子機(電球)を連携させる形でしたが、
LIFXは電球単体がそれぞれwi-fiにつながるので自由度が高いというのが選定理由でした。

こちらもGoogle Homeを通せば声のみでON/OFFが実現可能です。

- 点/消灯させるためにスイッチまで歩くそして押す
- スマホでLIFXアプリを開いてON/OFF

というかつての使い方が嘘のようです。

連携方法は先にLIFXアプリ上で設定後、こちらも同じくHomeアプリでの設定となります。
![Screenshot_20171214-084339.jpg](https://qiita-image-store.s3.amazonaws.com/0/8296/30bcdbae-5635-51d4-5536-be78aedacbe6.jpeg)

このように電球を部屋、その中での場所と細かくアサインでき、アサイン先の名前で点/消灯することができます。


また、話は少し変わりますが、このLIFXは単体でIFTTTと連携可能で、

- Facebookの通知受信時に青く光らせる
- 朝6:00 ~ 7:00にかけてじわじわと明るくする

等の使い方ができます。

私はそれに加えて
「運用しているサービスのアラートをGMailで受信したら、部屋のLIFXを全て赤く光らせる」
というレシピを作りましたが
![11068089_859081384135353_5907156615649739593_n.jpg](https://qiita-image-store.s3.amazonaws.com/0/8296/e2624ca0-6851-1082-f79c-ea4689b314d5.jpeg)
精神衛生上本当におすすめできません。（夜の3時


# Google Assistant対応アプリを作ってみる
それでは本題、アプリの制作に入りましょう。
作り方の前に全体外観を先に簡単にまとめたいと思います。

## 処理フロー
![無題のプレゼンテーション.png](https://qiita-image-store.s3.amazonaws.com/0/8296/c014ed86-3a4b-4c60-e170-c56928802791.png)


### Google Home / Phone
Google Homeは音声入力、Android/iOSのGoogle Assistantアプリではそれに加え文字入力が可能です。
入力された情報をGoogle Assistantに送ります。

### Google Assistant
入ってきた値によって、web検索や電話の発信等を行います。
また値がAssistant対応のアプリを開くトリガーになっていた場合、それを開きます。

### Action On Google
Google Assistantと各アプリケーションのつなげ役。
ここで言う各アプリケーションというのはDialogflowで作られたアプリをはじめ、Personality QuizやFlash Cardsといったテンプレートも用意されています。

### Dialogflow
入力された自然言語の解析処理機能を使ってアプリの実装ができます。
Google Assistantはもちろん、LINE、Slack、TwitterなどのIn/Outに対応しています。
以下で実際の設定を見つつ詳しく説明します。

### Application Server
Dialogflowで処理された入力を元に処理を行ういわばビジネスロジックがある部分。
Dialogflowで作ったアプリのIn/Outは固定化されているので、既存のビジネスロジックとの間に一段処理を挟む場合のそれもここに含まれると考えて良いと思います。
基本的に実装が必要なのはこの部分のみです。

## Dialogflowの設定
ではパデルコートの空き状況を確認するアプリの制作に入りましょう。
Action On GoogleではProjectを作ってDialogflowでビルドするということを指定するのみですので割愛します。

### Agentの作成
Agentの作成=アプリの作成と考えてよさそうです。わかりやすい名前をつけてCREATEしましょう。後から変更可能です。
DEFAULT LANGUAGEは後から変更ができません。今回は日本語で行きます。
<img width="1069" alt="スクリーンショット 2017-12-14 10.01.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/8296/f68a21b1-3fc3-c04d-771e-8212c6774449.png">

### Intents
ユーザーの入力を解析して、どの処理に回すかを司る部分です。
こちらを見ていただくとわかると思いますが、一定ルールに基いて、人間の言葉を抽出しています。
<img width="802" alt="スクリーンショット 2017-12-14 10.33.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/8296/b5d46f6b-0d50-153d-431d-48100f0e92d8.png">
今更ですがそもそもパデルとは何なのかということに関しては完全にスルーで進みます。

自然言語で入ってくる以上、文章のゆらぎはどうしても発生してしまうため、
このようにある程度想定できる文章を指定して、その枠に収まった入力の場合はこのIntentで設定された次の処理へと進みます。

ここで注目なのが色の着いた部分です。
これは以下のように変数として抽出され、後の処理で使われることになります。
<img width="806" alt="スクリーンショット 2017-12-14 10.17.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/8296/06a6f5ae-e2cb-18e8-2805-fbc1ab957aed.png">
このように`$court`、`$date`さえあればとある日時にコートが空いているかどうかの問い合わせは簡単にできそうです。

ですがこのcourt、dateであっても自然言語でのゆらぎは発生します。
それを吸収できるように設定をするのが以下のEntitieseです。

### Entities
こちらでは先程のENTITYの定義を行っています。
<img width="796" alt="スクリーンショット 2017-12-14 10.24.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/8296/2e3b7690-2b50-3f8d-8b12-189fce2c49e6.png">
同じ東京という意味でも人によっては荻窪や善福寺と言ってしまう可能性は十分に考えられますが、アプリケーションサーバー側でそれらを吸収する責務を負うのは非常に重いです。
ここで定義をすることでそれらの吸収を図っています。


### Fulfillment
それでは抽出した単語を使ってアプリケーションサーバーに問い合わせを行いましょう。
今回はIntentsの設定部分でFulfillmentにチェックを入れてますので、こちらのWebhookに処理が流れてきます。
URLと認証情報を記載します。URLはHTTPSしか受け付けていないのでご注意ください。
<img width="805" alt="スクリーンショット 2017-12-14 10.36.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/8296/b315192e-049c-98c2-3da1-3e028c6ce14b.png">

また、こちらに書いてあるとおり、リクエストとレスポンスの型は決まっていますので、
それに合わせてアプリケーションサーバーを実装します。
https://dialogflow.com/docs/fulfillment

今回実装コードは割愛しますが、該当コートの予約サイトをスクレイピングして、
該当日の何時から何時が空いているかを返す実装をしています。(信じられないくらい雑多になりました・・・)

準備ができましたら画面横のTry It Nowに値を入力します。
今回はIntentの設定に乗るように「今日の荻窪の予約状況」とします。

```json
{
  "id": "0f4dd57b-4881-4b23-969b-c43ad5bd5f9c",
  "timestamp": "2017-12-14T01:39:01.038Z",
  "lang": "ja",
  "result": {
    "source": "agent",
    "resolvedQuery": "今日の荻窪の予約状況",
    "action": "check_court_open",
    "actionIncomplete": false,
    "parameters": {
      "court": "東京",
      "date": "2017-12-14",
      "reserve": "空き状況照会"
    },
    "contexts": [],
    "metadata": {
      "intentId": "b4960b08-7605-4450-bce7-0a00ed1d0912",
      "webhookUsed": "true",
      "webhookForSlotFillingUsed": "false",
      "webhookResponseTime": 701,
      "intentName": "コート空き確認"
    },
    "fulfillment": {
      "speech": "パデル東京は本日、12時から17時が空いています",
      "source": "",
      "displayText": "パデル東京は本日、12時から17時が空いています",
      "messages": [
        {
          "type": 0,
          "speech": "パデル東京は本日、12時から17時が空いています"
        }
      ],
      "data": {}
    },
    "score": 1
  },
  "status": {
    "code": 200,
    "errorType": "success",
    "webhookTimedOut": false
  },
  "sessionId": "0f4d5788-b443-4a30-b24e-6caea6990014"
}
```

parametersの中に値が正しく入っていることが確認できると思います。

それではAction On GoogleのSimuratorでの挙動を見てみましょう。
<img width="390" alt="スクリーンショット 2017-12-14 10.53.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/8296/037df955-2ce3-7080-e831-0634ccc9a3bc.png">
よし、パデル行こう。（仕事




# おわりに
パデルライフは若干捗りましたでしょうか？
かなりざっくりした解説にはなりましたが、
Dialogflowは一見複雑なGUIですが、多くのゆらぎがある人間の言葉を機械に通じる言葉に変換してくれるツールという点で非常に優秀なツールだと感じました。
今後、機械学習の力が加わってゆらぎをさらに高精度で丸められるんだろうな・・・と考えられると本当にGoogle恐ろしいです。

また、今回はFullfilmentのwebhook先の処理を手っ取り早くPerl on 手元のサーバーで実装をしてしまいましたが、せっかくなのでfirebase上でサーバーレスっぽく書いてみたかった・・・

それでは以上となります。
次回は @daponta さんよりkubernetesに関するお話ですー！お楽しみに！


# 宣伝

マンガボックスでは一緒に働くメンバーを熱烈に募集しています。

- アーキテクチャ設計や言語選定から関わるエンジニアリングをしたい方
- マンガに関わる仕事に興味がある方
- エンジニアとパデルを両立したい方

などなど、「ちょっと興味ありそう」「もっと色々聞いてみたい」という方も
是非[こちら](https://www.wantedly.com/projects/97773)から応募頂くか、 [@hirashunshun](https://twitter.com/hirashunshun/) までゆるっとご連絡いただければと思います。

お待ちしております！