RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのエンジニアブログ

Google Homeにプレゼンさせてみた。

こんにちは。楽楽精算開発チームの岡本です。

会社で購入したGoogleHomeで自由に遊べる権利を頂いたので、色々と遊ばせてもらいました。
先日その内容を社内でプレゼンする機会があったのですが、せっかくなのでGoogleHome自身にプレゼンしてもらいました。

今回は、その際に実施した設定を説明しようと思います。
※設定方法についてはwest-cさんが既に書かれているので、設定した内容だけ書いていきます。

今回やりたいこと

  1. スマートフォンでプレゼン開始を指示する。
  2. ディスプレイにスライドが表示される。
  3. GoogleHomeがスライドの内容を喋り始める。
  4. スライドの内容を喋り終えると、次のスライドに切り替わる。
  5. スライドが終了するまで2~4を繰り返す。

準備するもの

Wan側

  • Action on Google
  • Dialogflow
    • 自然言語の解析エンジンです。受取った音声データを解析してテキストに変換してくれます。
    • また、Firebaseホスティングを用いたスクリプト実行も可能なので、変換したテキストを他のWebサービスに連携させたりもできます。
  • Firebase
    • いわゆるBaasです。
    • 今回はホスティング機能とDB機能を使用します。

Lan側

  • RaspberryPi
    • 超小型のコンピューターです。
    • 今回は以下の機能を実装しています。
      • ディスプレイへのスライド切替え通知
      • GoogleHomeへのメッセージ通知
  • GoogleHome

ざっくりとした処理の流れ

f:id:okana-yg:20171223153335j:plain

  1. スマートフォンGoogleアシスタントからAction on Googleで構築したチャットアプリを呼び出す。
  2. チャットアプリで入力した音声データをDialogflowに連携する。
  3. Dialogflowの解析結果テキストをFirebaseのDBに保存する。
  4. 保存したテキストをRaspberryPiに通知する。
    (通知されたテキストがプレゼン開始キーワードの場合は、プレゼンを開始する。)
  5. スライドの表示/切り替えをディスプレイに通知する。
  6. 喋らせるメッセージをGoogleHomeに通知する。

Action on Googleの設定

Action on Googleで新規プロジェクトを作成しActionApp informationを設定します。

Action

ADD ACTIONSからDialogflowを選択します。
f:id:okana-yg:20171224003323j:plain

App information

重要なのはAssistant app nameの項目だけです。
Assistant app nameで設定した名前でGoogleアシスタントチャットアプリを呼び出すことになります。
今回は「発表アプリ」にしたので、スマホに「OK Google 発表アプリにつないで」と話しかけるとチャットアプリが起動します。
それ以外の項目については適当に設定してしまって問題ないです。
f:id:okana-yg:20171224001105p:plain

以上でAction on Googleの設定は完了です。

Dialogflowの設定

Action on GoogleAction設定から、Dialogflow画面に遷移できるので、新規プロジェクトを作成しIntentsFulfillmentを設定します。

Intentsの設定

チャットアプリで入力した音声データに対して、どのように振舞うのかを設定します。今回は以下の2つを設定します。
f:id:okana-yg:20171224132107j:plain

  • Default Welcome Intent
    チャットアプリが起動した時の振舞いを設定します。
    今回は、アプリ起動時にチャットアプリが「発表アプリを起動しました。」と応答するようにText responseを設定しました。
    f:id:okana-yg:20171224132545j:plain
  • プレゼン開始
    チャットアプリにプレゼン開始を指示した時の振舞いを設定します。今回は以下のような振舞いを想定しています。
    1.チャットアプリに「プレゼン開始」と音声入力する。
    2.「プレゼン開始」のキーワードをFirebaseに保存する。
    3.チャットアプリが「プレゼンを開始しました。」と応答する。

    「プレゼン開始」の音声入力でsaveDataというアクションが実行されるように設定します。
    saveDataの内容についてはFulfillmentで設定します。
    f:id:okana-yg:20171224140841j:plain
    アクション実行後、チャットアプリが「プレゼンを開始しました。」と応答するように設定します。
    f:id:okana-yg:20171224144012j:plain

Fulfillmentの設定

Inline EditorENABLEDに変更し、index.jspackage.jsonを以下のように設定します。

/** index.js **/

'use strict';

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

// Firebaseへの接続情報
var config = {
    apiKey: "XXXXXXXXXXXXXXXXXXXXXX",
    authDomain: "XXXXXXXXXXXXXXXXXXXXXX",
    databaseURL: "XXXXXXXXXXXXXXXXXXXXXX",
    projectId: "XXXXXXXXXXXXXXXXXXXXXX",
    storageBucket: "XXXXXXXXXXXXXXXXXXXXXX",
    messagingSenderId: "XXXXXXXXXXXXXXXXXXXXXX"
};
firebase.initializeApp(config);

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
    var app = new DialogflowApp({request, response});
    let actionMap = new Map();
    // saveDataアクションを登録
    actionMap.set("saveData", function(app) {
        // Firebaseにキーワードを保存する
        firebase.database().ref("/googlehome").set({word:"プレゼンを開始します"});
    });
    app.handleRequest(actionMap);
});

Firebaseへの接続情報Firebaseの画面でウェブアプリに Firebase を追加をクリックして表示されるコードを使用します。

/** 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": "~6.0"
  },
  "scripts": {
    "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
    "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
  },
  "dependencies": {
    "actions-on-google": "^1.5.x",
    "firebase": "^4.8.0",
    "firebase-admin": "^4.2.1",
    "firebase-functions": "^0.5.7",
    "apiai": "^4.0.3"
  }
}

設定後、DEPLOYをクリックしFirebaseスクリプトをデプロイします。

以上でDialogflowの設定は完了です。

Firebaseの設定

FirebaseDatabaseにデータ保存場所とアクセスルールを設定します。

データ保存場所の設定

Database > データから保存場所を作成します。今回は/googlehome/wordに作成します。
f:id:okana-yg:20171224151113j:plain
作成した保存場所には、PUTでデータを保存出来ます。

curl -X PUT \
  https://XXXXXX.firebaseio.com/googlehome/word.json \
  -H 'content-type: application/json' \
  -d '"保存キーワード"'

保存したデータはGETで取得できます。

curl -X GET \
  https://XXXXXX.firebaseio.com/googlehome/word.json \
  -H 'content-type: application/json'

アクセススールの設定

Database > ルールからDBへのアクセスルールを設定します。
今回は設定簡易化のため/googlehome配下のデータには認証無しでアクセスできるように設定しています。

{
  "rules": {
    "googlehome": {
      ".read": true,
      ".write": true
    },
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

以上でFirebaseの設定は完了です。

RaspberryPiの設定

以下の内容をスクリプト化し、RaspberryPiのNode.js上で実行します。

  • GoogleHomeへのメッセージ通知
  • ディスプレイへのスライド表示通知
  • FirebaseのDB更新通知の受信

※RaspberryPiに初期インストールされているNode.jsはバージョンが古いので、最新の安定バージョンに更新する必要があります。

GoogleHomeへのメッセージ通知

GoogleHome任意のメッセージをプッシュするNode.js用ライブラリが公開されているので、ありがたく使わせていただきます。
google-home-notifier

  • ライブラリのインストール
$ npm init
$ npm install google-home-notifier
  • 動作確認
/** testMessage.js **/
const googlehome = require("google-home-notifier");
const language = "ja";

// デバイス設定(Google-Homeで始まる全デバイスにメッセージが通知される。)
googlehome.device("Google-Home", language); 
// 通知するメッセージ
googlehome.notify("こんにちは。私はグーグルホームです。", function(res) {
    console.log(res);
});

※test.jsの文字コードはUTF8で作成する。

$ node testMessage.js
Device "Google-Home-XXXXXXXXXXXXXXXXXXXXXX" at 192.168.1.11:8009 <- 応答したGoogleHomeのデバイス名
Device notified

ディスプレイへのスライド表示通知

スライドの表示にはfbiというコマンドを使います。

  • コマンドのインストール
$ sudo apt-get install fbi
  • 動作確認
$ sudo fbi -nocomments -noverbose -a -T 1 -d {出力場所} {画像ファイルのパス}

※コンソール出力の場合は、{出力場所}に/dev/fb0を、HDMI出力の場合は/dev/fb1を設定します。

FirebaseのDB更新通知の受信

Firebaseのドキュメントを元に設定します。

  • ライブラリのインストール
$ npm install firebase
  • 動作確認
/** testFirebase.js **/

// Firebaseへの接続情報(Dialogflowの項目で設定したものと同じ)
var config = {
    apiKey: "XXXXXXXXXXXXXXXXXXXXXX",
    authDomain: "XXXXXXXXXXXXXXXXXXXXXX",
    databaseURL: "XXXXXXXXXXXXXXXXXXXXXX",
    projectId: "XXXXXXXXXXXXXXXXXXXXXX",
    storageBucket: "XXXXXXXXXXXXXXXXXXXXXX",
    messagingSenderId: "XXXXXXXXXXXXXXXXXXXXXX"
};
firebase.initializeApp(config);

const path = "/googlehome";
const key = "word";
const db = firebase.database();
// 更新通知を受信した時の処理を記述
db.ref(path).on("value", function(changedSnapshot) {
    // 更新された値をログに表示
    console.log("取得キーワード:" + changedSnapshot.child(key).val());
});
$ node testFirebase.js
# Firebaseに「テスト」をPUTしてDBを更新する
取得キーワード:テスト

index.js作成

実行ファイルとしてindex.jsを作成します。

/** index.js **/

const firebase = require("firebase");
const exec = require("child_process").exec;
const googlehome = require("google-home-notifier");

// メッセージを通知するGoogleHomeの設定
const language = "ja";
const deviceName = "Google-Home-XXXXXXXXXXXXXXXXXXXXXX";
googlehome.device(deviceName, language); 

// Firebaseへの接続情報を設定
var config = {
    apiKey: "XXXXXXXXXXXXXXXXXXXXXX",
    authDomain: "XXXXXXXXXXXXXXXXXXXXXX",
    databaseURL: "XXXXXXXXXXXXXXXXXXXXXX",
    projectId: "XXXXXXXXXXXXXXXXXXXXXX",
    storageBucket: "XXXXXXXXXXXXXXXXXXXXXX",
    messagingSenderId: "XXXXXXXXXXXXXXXXXXXXXX"
};
firebase.initializeApp(config);


// GoogleHomeへメッセージを通知
var notifyGoogleHome = function(word) {
    googlehome.notify(word, function(res) { console.log(res); });
};
var totalSpeakTIme = 0;
var speak = function(word, speakTime) {
    // 前回メッセージの終了後、メッセージを通知
    setTimeout(function() { notifyGoogleHome(word); }, totalSpeakTIme);
    // 今回メッセージの秒数分カウントアップ
    totalSpeakTIme += (speakTime * 1000);
}

// ディスプレイへスライドの表示を通知
var notifyDisplay = function(imgPath) {
    // fbiコマンドを使ってディスプレイに画像を表示
    exec("fbi -nocomments -noverbose -a -T 1 -1 -d /dev/fb0 " + imgPath, function(err, stdout, stderr){
        if (err) {
            console.log(err);
        }
    });
};
var totalDisplayTIme = 0;
var show = function(imgPath, speakTime) {
    // 前回スライドの終了後、表示を通知
    setTimeout(function() { notifyDisplay(imgPath); }, totalDisplayTIme);
    // 今回スライドの表示秒数分カウントアップ
    totalDisplayTIme += (speakTime * 1000);
}

// Firebaseの更新通知を受取った時の処理
const path = "/googlehome";
const key = "word";
const db = firebase.database();
db.ref(path).on("value", function(changedSnapshot) {
    // 更新された値を取得
    const value = changedSnapshot.child(key).val();
    if (value === "プレゼンを開始します") {  // 開始キーワードの場合はプレゼン開始
        // 登録された値をFirebaseから消しておく
        db.ref(path).set({[key]: ""});
        // プレゼン中は何もしない
        if (totalDisplayTIme > 0 || totalSpeakTIme > 0) {
            return;
        }

        // プレゼン開始メッセージをGoogleHomeに喋らせる
        speak("承知しました。" + value, 10);
        // 1枚目のスライドを表示する
        show("./img/001.jpg", 15);

        // 以下、プレゼン用スクリプトを記述
    }
});

ファイルが作成できたら、実行します。

$ sudo node index.js

fbiコマンドの実行時にroot権限が必要なのでsudo付きで実行します。

以上でRaspberryPiの設定は完了です。

実際にやってみる

設定完了後、スマホに対して「発表アプリにつないで」「プレゼン開始」と指示すると、index.jsに書かれた内容でGoogleHomeがプレゼンをしてくれます。

長いので冒頭部分だけですが、実際にこんな感じでGoogleHomeにプレゼンしてもらいました。


参考

Google Home開発入門 / google-home-notifier解説
Raspberry Pi でTFT液晶モジュールにいろいろ表示する


*1:Googleアシスタントと会話できるアプリ

Copyright © RAKUS Co., Ltd. All rights reserved.