Google Homeで変身する

この記事は、Tech KAYAC Advent Calendar 2017 の21日目の記事です。

こんにちは!17新卒HTMLファイ部の入江です。

技術の無駄遣いをモットーに日々最新技術を追っています。今回は今流行りのAIスピーカーGoogle Homeを使って変身できるようにしてみました。

できたもの

まずはこちらご覧ください。


Google Home と Firebase Realtime databaseを連携して顔を変える

使った技術

  • Dialogflow
  • firebase
  • Fusion 360

仕様

Google Homeに「変身〇〇」というと〇〇の部分をfirebaseのrealtime databaseに保存するAgentをつくる。realtime databaseをブラウザから監視し、変わった値に紐づいた画像を画面(iPad)に表示する。

f:id:kiyoshidainagon:20171220121825p:plain
システム図

DialogflowでAgentをつくる

Entitiesの設定

まずEntityを作ります。 「変身〇〇」の〇〇の部分に当たるやつですね。

f:id:kiyoshidainagon:20171220142709p:plain
Entity

右側が特定の単語。左側が特定の単語に対しての類義語になります。

Intentsの設定

続いてintentの設定していきます。 「変身〇〇」という単語を理解できるようにしてあげます。

f:id:kiyoshidainagon:20171220142724p:plain
Intent

User saysで認識してもらいたい文言を追加します。今回は変身したいので、変換が間違っても問題ないように「変身ブルー」と「返信レッド」を追加しました。レッド、ブルーの単語を選択して、先ほど作ったEntityを設定してあげると、黄色くなります。

FulfillmentのUse webhookにチェックを入れます。

Integrationの設定

f:id:kiyoshidainagon:20171220125932p:plain

一番上のGoogle AssistantのINTEGRATION SETTNIGSをクリックすると上図のようなポップアップが出ます。最近UI変わったみたいですね。 Additional triggering intentsの欄に先ほど作ったintentを設定して、TESTを押して問題ないようだったら、MANAGE ASSISTANT APPを押します。

Fulfillmentの設定

firebase cloud funcitonの作成

firebaseのcloud functionをローカル環境で作成します。手順としては以下です。

  1. yarn global add firebase-tools
  2. firebase login
  3. firebase int functions
  4. Select a default Firebase project for this directory: (Use arrow keys)と聞かれるので、Dialog flowで作成中のAgentを選択する
  5. JavaScript と TypeScriptどっちにするかとか聞かれますが、そこはお好みで
  6. できたらfirebase deploy --only functionsでデプロイする

今回書いたfunctionは↓になります。内容はFullfillmentのInline Editor内に書いてあるサンプルスクリプトに、FirebaseのRealtime Databaseにデータを追加するプログラムを書き足しただけです。

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const DialogflowApp = require('actions-on-google').DialogflowApp;

admin.initializeApp(functions.config().firebase);

exports.dialogflowWithRealtimeDetabase = functions.https.onRequest((request, response) => {
  if (!request.body.result) {
    console.log('Invalid Request');
    return response.status(400).end('Invalid Webhook Request (expecting v1 or v2 webhook request)');
  }

  let action = request.body.result.action;
  const parameters = request.body.result.parameters; 
  const requestSource = (request.body.originalRequest) ? request.body.originalRequest.source : undefined;
  const path = "/faceName";

  // realtime databaseにデータを突っ込む 
  admin.database().ref(path).set(parameters.change_name);

  const googleAssistantRequest = 'google'; 
  const app = new DialogflowApp({request: request, response: response});

  const actionHandlers = {
    'input.welcome': () => {
      if (requestSource === googleAssistantRequest) {
        sendGoogleResponse('Hello, Welcome to my Dialogflow agent!'); 
      } else {
        sendResponse('Hello, Welcome to my Dialogflow agent!'); 
      }
    },
    'input.unknown': () => {
      if (requestSource === googleAssistantRequest) {
        sendGoogleResponse('I\'m having trouble, can you try that again?'); 
      } else {
        sendResponse('I\'m having trouble, can you try that again?'); 
      }
    },
    'default': () => {
      if (requestSource === googleAssistantRequest) {
        let responseToUser = {
          speech: 'シャキーン', 
          text: 'シャキーン'
        };
        sendGoogleResponse(responseToUser);
      } else {
        let responseToUser = {
          speech: 'シャキーン', 
          text: `シャキーン`
        };
        sendResponse(responseToUser);
      }
    }
  }

  if (!actionHandlers[action]) {
    action = 'default';
  }

  actionHandlers[action]();

  function sendGoogleResponse (responseToUser) {
    if (typeof responseToUser === 'string') {
      app.ask(responseToUser); 
    } else {
      let googleResponse = app.buildRichResponse().addSimpleResponse({
        speech: responseToUser.speech || responseToUser.displayText,
        displayText: responseToUser.displayText || responseToUser.speech
      });
      if (responseToUser.googleRichResponse) {
        googleResponse = responseToUser.googleRichResponse;
      }
      if (responseToUser.googleOutputContexts) {
        app.setContext(...responseToUser.googleOutputContexts);
      }
      app.ask(googleResponse); 
    }
  }
  function sendResponse (responseToUser) {
    if (typeof responseToUser === 'string') {
      let responseJson = {};
      responseJson.speech = responseToUser; 
      responseJson.displayText = responseToUser; 
      response.json(responseJson); 
    } else {
      let responseJson = {};
      responseJson.speech = responseToUser.speech || responseToUser.displayText;
      responseJson.displayText = responseToUser.displayText || responseToUser.speech;
      responseJson.data = responseToUser.data;
      responseJson.contextOut = responseToUser.outputContexts;
      response.json(responseJson); 
    }
  }
});

DialogflowのFulfillmentの設定

  1. WebhookをENABLEDにします。
  2. URLに先ほどdeployしたcloud funcitonのURLを指定します。

フロント側でfirebase realtime databaseを監視する

単純にデータを監視するだけなんで、ちょっとの記述で実装できます。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <style>
            body {
              width: 100vw;
              height: 100vh;
              background-color: #000;
              background-repeat: no-repeat;
              background-position: center bottom;
              color: #fff;
            }
      </style>
    </head>
    <body>
        <p id="face-name">ホゲホゲ</p>

        <script src="https://www.gstatic.com/firebasejs/4.2.0/firebase.js"></script>
        <script>
            // Initialize Firebase
            var config = {
                apiKey: "<API_KEY>",
                authDomain: "<PROJECT_ID>.firebaseapp.com",
                databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
                storageBucket: "<BUCKET>.appspot.com",
                messagingSenderId: "<SENDER_ID>",
            };
            firebase.initializeApp(config);

            var db = firebase.database();
            var faceType = db.ref('faceName'); //取得したいデータのパスを指定
            faceType.on('value', function(snapshot) { 
                const value = snapshot.val(); //faceNameに入ってる値を取得
                document.getElementById("face-name").innerText = value;
                document.body.style.backgroundImage = 'url(img/' + value + '.png)';
            });
        </script>
    </body>
</html>

ウェアラブル化する

micro USBで給電してるし、戦隊モノのベルトのバックルに見えなくもないので、ウェアラブル化してみます。

ThingiverseというサイトからGoogle Home Miniのマウントをダウンロードしてきます。

今回ダウンロードしたものは↓ Google Home Mini Wall Mount by tilmansp - Thingiverse

続いて、先ほどダウンロードしたマウントに合うように、ベルトにかけれるフックをモデリングしていきます。 モデリングにはFusion 360を使いました。

f:id:kiyoshidainagon:20171219213531p:plain

Fusino 360でモデリング

このモデルを1個にまとめて3Dプリントで出力するのはうまく行かなそうなので、フックの部分とマウントの部分の2パーツに分けて出力し、出力後に合体させます。 接着剤はアクリサンデーがオススメです。http://www.acrysunday.co.jp/products/article/

合体させたマウントはこんな感じになります。 f:id:kiyoshidainagon:20171219214420j:plain

念のため、フックの部分もあげておきます。ベルトにつけたい方はご気軽にダウンロードしてください。 www.thingiverse.com

モバイルバッテリーも同様にフック付きのケースを用意して、完成したのがこちらになります。

f:id:kiyoshidainagon:20171220173834p:plain
Google Home 搭載ベルト

まとめ

いかがだったでしょうか。firebaseを使うことで、Google Homeでできることが格段に広がる気がしますね。

面白法人カヤックではAIスピーカーを使って家を便利にするだけじゃ飽き足らない人を募集しています。

www.kayac.com www.kayac.com

明日は先日の合宿で新人王を獲ったcommojun.comです。