Google Nest Hubの画面対応方法

2019年6月12日に日本でも[Google Nest Hub](https://store.google.com/jp/product/google_nest_hub)が発売されました。日本初のGoogleアシスタント画面付きスマートスピーカーです。
やはり手に入れたからには、スマートスピーカーのアクション（スキル）を作りたいですよね！

開封の儀の記事はこちらを見てください。
https://note.mu/gaomar/n/n855952289aeb

画面付き対応はAlexaなら[Alexa Presentation Language](https://developer.amazon.com/ja/docs/alexa-presentation-language/apl-overview.html)(APL)を使ってリッチなコンテンツを作成することができます。Google Nest Hubも同様にSDKが公開されています。

Google Nest Hubでは`Interactive Canvas`という機能を使って実装していきます。
https://developers.google.com/actions/interactivecanvas/

■2019年7月22日
仕様が変わったので修正しました。
https://www.eisbahn.jp/yoichiro/2019/07/interactive_canvas_changes.html

# 完成動画
実際に組み込んだ動画です。BMI測定ボタンの画面はVue.jsで作成しています。身長と体重はDialogflowで処理を任せています。
結果画面で再度Vue.jsの画面に戻ってDialogflowから結果の値を取得して画面に反映させるという流れ。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Google Nest HubでBMI測定してみた。Interactive Canvas使えばリッチな表現が出来ますね！表示しているのでVue.jsで作っています<a href="https://twitter.com/hashtag/nesthub?src=hash&amp;ref_src=twsrc%5Etfw">#nesthub</a> <a href="https://t.co/Bu6dnFYPLQ">pic.twitter.com/Bu6dnFYPLQ</a></p>&mdash; がおまる@スマートスピーカーアプリ開発入門発売中！ (@gaomar) <a href="https://twitter.com/gaomar/status/1141256990892494848?ref_src=twsrc%5Etfw">June 19, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 1. Dialogflowの設定
## 1-1. Intentの設定
アクションを起動したら必ず最初に呼ばれるIntentを作成します。
Intent名は`welcome`としました。Webhookを行うので、②のように有効化します。

<img width="998" alt="s100.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/41802d43-8c8b-2fbd-6689-504131caca05.png">

続いてBMI測定を行いたいので、BMIのIntentを作成します。
Intent名は`bmi`としました。Training phrasesは`bmi`という言葉に反応させます。

パラメータは下記の通り

| PARAMETER NAME       |       ENTITY |    VALUE    |PROMPTS|
|:-----------------|------------------|:------------------|:---|
|height|@sys.number|$height|身長をお答えください|
|weight|@sys.number|$weight|体重をお答えください|

![s101.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/0933b3f8-9bd4-3a41-3676-a36041c8dbd8.png)


## 1-2. Fulfillmentの設定
Inline Editorを有効にしてプログラムを記述します。`HtmlResponse`にスマートディスプレイ用の設定を記述します。

|キー名|説明|
|:---|:--|
|url|画面に表示させたいホームページのURLを指定します|
|suppress|trueにするとマイクの応答が無くなります。falseにすると引き続きマイクの入力が有効になります|
|data|ここに渡したい値を設定します|

```javascript:index.js
'use strict';
 
const functions = require('firebase-functions');
const {dialogflow, HtmlResponse} = require('actions-on-google');

const app = dialogflow({debug: true});

// 起動時Intent
app.intent('welcome', (conv) => {
  conv.ask('あなたのBMIを測定します。測定開始ボタンを押してください。');
  conv.ask(new HtmlResponse({
    url: `https://{表示させたいホームページのURL}`, /* 動作確認したい場合はこちらのURLを指定してください 「https://gaomar-nesthub-demo.netlify.com/」 */
    suppress: true
  }));
});

// BMI測定処理
app.intent('bmi', (conv, {height, weight}) => {
  const bmiVal = (parseFloat(weight) / (parseFloat(height)/100 * parseFloat(height)/100)).toFixed(1);
  var speechText = `あなたのBMIは${bmiVal}です。`;
  conv.ask(speechText);
  conv.ask(new HtmlResponse({
    url: `https://{結果画面のURL}`, /* 動作確認したい場合はこちらのURLを指定してください 「https://gaomar-nesthub-demo.netlify.com/result」 */
    suppress: true,
    data: {
      bmi: speechText,
    },
  }));
});

exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

```
## 1-3. package.jsonを編集
package.jsonは下記の設定をします。2019年6月現在、actions-on-googleは`preview`版でしか動作しない模様です。
その他のバージョンやライブラリは下記を指定します。

※2019年8月1日に2.10.0が正式リリースされました。

```json:package.json(抜粋)
  "dependencies": {
    "actions-on-google": "2.10.0",
    "firebase-admin": "~7.0.0",
    "firebase-functions": "^2.2.0"
  }

```

```json:package.json(全文)
{
  "name": "dialogflowFirebaseFulfillment",
  "description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": "8"
  },
  "scripts": {
    "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
    "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
  },
  "dependencies": {
    "actions-on-google": "2.10.0",
    "firebase-admin": "~7.0.0",
    "firebase-functions": "^2.2.0"
  }
}
```

# 2. Actions on Googleの設定
Actions on Googleの設定でInteractive Canvasを有効にしないと実際に使えません。

## 2-1. Interactive Canvasを有効化にする
左側メニューにある`Directory information`をクリックします。Additional Information部分にある、カテゴリーを必ず`Games & fun`にします。
このカテゴリーにすると、Interactive Canvasのチェックが出てくるのでこれを有効にします。

![s102.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/44ff634e-fa9a-cfd1-804c-7a8ec96b27b6.png)

# 3. 表示する画面の設定
Vue.jsで実装しています。デプロイ先はどこでも構いませんが、Netlifyが素晴らしいのでそちらにデプロイしています。

## 3-1. Vue.jsの設定
画面に表示する部分はVue.jsで実装しました。ドキュメントはこちらに詳しく掲載されています。
https://developers.google.com/actions/interactivecanvas/build/web-app

Interactive CanvasのAPIをindex.htmlに適用します。
12〜13行目でAPIを呼び出しています。UIライブラリーにVuetifyを設定しているので、余計なものもありますが、環境に応じて設定してください。

```html:index.html(抜粋)
<script src="https://www.gstatic.com/assistant/immersivecanvas/js/immersive_canvas_api.js"></script>
```

```html:index.html（全コード）
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>nesthub-demo</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons">
    <!-- Load Assistant JavaScript -->
    <script src="https://www.gstatic.com/assistant/immersivecanvas/js/immersive_canvas_api.js"></script>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but nesthub-demo doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
```

## 3-2. タイトルページ
アクション起動時に表示されているタイトルページの設定例です。単純にボタンを置いてるだけでのシンプルなページです。
キーポイントはボタンを押した時に実行される`interactiveCanvas.sendTextQuery`です。

これは、Google Assistant側に`bmi`という文字列を送っています。これは1-1で設定したbmiという言葉に反応して、BMI測定が開始してほしいためです。

```vuejs:src/components/HelloWorld.vue
<template>
  <v-container>
    <v-layout
      text-xs-center
      wrap
    >
      <v-flex mb-4>
        <h1 class="display-2 font-weight-bold mb-3">
          こんにちはNest Hub！
        </h1>
        <v-btn large color="success" @click="bmiStart">BMI測定開始</v-btn>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
export default {
  methods: {
    bmiStart () {
      // Google Assistantに 「bmi」 という文字列を送る
      interactiveCanvas.sendTextQuery('bmi');
    }
  }
}
</script>
```

## 3-3. 結果画面
測定結果を表示させる画面です。キーポイントは`interactiveCanvas.ready(callbacks)`です。
この画面が呼ばれたらcreated()部分が呼ばれます。これで、毎フレームonUpdate()が実行されます。

1-2で設定したFulfillmentのindex.jsでdataの値を投げている部分があったと思います。
そのdataで指定したbmiという値をここで受け取っています。

```vue:src/components/Result.vue
<template>
  <v-container>
    <v-layout
      text-xs-center
      wrap
    >
      <v-flex mb-4>
        <h1 class="display-3 font-weight-bold mb-3">
          【BMI測定結果】
        </h1>
      </v-flex>

      <v-flex
        xs12
        mb-5
      >
        <h3 class="display-2 font-weight-bold mb-3">
        // 結果を表示する
        {{result}}
        </h3>
      </v-flex>

      <v-flex
        xs12
        mb-5
      >
        <v-btn large color="success" @click="bmiStart">もう一度やる</v-btn>
      </v-flex>

    </v-layout>
  </v-container>
</template>

<style>
</style>
<script>
export default {
  data () {
    return {
      result: ''
    }
  },
  created () {
    var me = this
    const callbacks = {
      onUpdate(data) {
        // bmiの値が飛んできたら取得する
        if ('bmi' in data) {
          me.result = data.bmi
        }
      },
    }
    interactiveCanvas.ready(callbacks)
  },
  methods: {
    bmiStart () {
      interactiveCanvas.sendTextQuery('bmi');
    }
  }
}
</script>
```

# まとめ
シンプルな構成ですが、これでDialogflow ⇔ Actions on Google ⇔ Vue.jsの連携をすることができました。
カテゴリがゲームカテゴリーでないとInteractive Canvasが使えないのが辛いですが、ゲームの演出には使えるかと思いました。

ドキュメントにも記載されていますが、あくまで画面は補助的な役割をするので、メインはVUIということを忘れないようにしましょう！
AlexaのAPLよりやれることが増えているので、個人的にはInteractive Canvasの方が使いやすいと感じました。

今回使用したファイルはGitHubにあがっています。
https://github.com/gaomar/nesthub-demo

システム化のご検討やご相談は弊社までお問い合わせください。
https://i-enter.co.jp/contact/