みなさんこんにちは。サーバーサイドエンジニアの柿本です。
前回のエントリーにて、リモートシャッターで子供のうんち記録を残すことに成功しました!
しかし、冷静に考えると、
- おむつを交換した手でリモートシャッターを触るのは不衛生
- 私が不在の時に妻が node コマンドを打つのはムリ
- 連絡帳にはうんちの状態(「普/硬/軟/下痢」の4段階)の記入も必要
などなど、実運用に耐えられないことが判明しました。 泣
そこでこれらの問題をまとめて解決するために、 Google Home Mini に記録を残してもらうことにします。
普段は私がカップ麺を作るときのタイマーとして使うのみですが、今こそ家族の役にたってもらうのです!
TL;DR
※音が出ます。音量にお気をつけください。
動画が見れない場合はこちら
目次
構想
IFTTT でつなげることもできますが、会話の柔軟性の点で劣るので、 Actions on Google ( Google Assistant のアプリ開発プラットフォーム)を使うことにします。
親としては子供の「うんちの量」も気になりますので、うんちの「状態」と「量」の両方を記録できるようにします。
Google スプレッドシートへは Google Apps Script で WEB API を公開して追記することにします。ついでにSlackに通知を送ります。
会話の流れはこんな感じでしょうか。
『こんにちは。今日はどんなうんちでしたか?』 =>「今日はかためでした。」 『量はどれくらいですか?』 =>「たくさん出ました。」 『わかりました。かためのうんちがたくさんですね。』
素晴らしい!!これならうんちシャッターの全ての問題が解決されます!
それでは早速作っていきます。
前提
以下はすでに作成済であることを前提とします。
① Google アカウント
② ①に紐づいた Google Home
③ ①に紐づいた Actions on Google のプロジェクト
④ Google スプレッドシート
⑤ Slack のチャンネルと Webhook URL
実装
Actions on Google
Google Assistant アプリを開発する時は、 Actions on Google という開発プラットフォームを利用します。
公式に勝るチュートリアルなし!ということで、以下をざっとこなすと Actions on Google で Dialogflow を使ったアプリ開発の流れが理解できます。
codelabs.developers.google.com
作成した Google Assistant アプリを Google Home とつなげるためにアプリ名を設定する必要がありますので、 Invocation メニューからアプリ名を登録します。
うんちをリポートしてくれるので「うんちリポーター」と命名します。
Dialogflow
それでは Google Assistant アプリの肝となる Actions を Dialogflow を使って実装していきます。
Entity
Entity とは、つまり変数です。
今回の場合、
- うんちの状態
- うんちの量
の2つが Entity になります。
Dialogflow の Entity メニューで、各 Entity が取りうる値とその値を表す単語(複数可)を登録します。
Entity 名をそれぞれ condition
amount
とし、以下の通りに設定します。
Intent
Intent とは、「ユーザーが○と言ったら×と返す」という会話の1ターンを設定し、ユーザーの発話から情報を抽出します。
最初に「今日はどんなうんちでしたか?」と質問をしたいので、アプリ起動時に呼び出される「Default Welcome Intent」の Responses でその文言を設定します。
どんなうんちだったか、をユーザーが回答した時に、その情報を抽出するために「poop condition」 intent を追加します。
Training phrases にユーザーが言うであろう言い回しをいくつか登録します。
Add user expression に追加をしていくと、事前に Entity で登録した値を検出して自動で condition
の値としてハイライトしてくれます。
うんちの量も教えてもらう必要があるので Action and parameters で REQUIRED 項目として amount も登録します。
うんちの量を質問する時の文言を PROMPTS として登録します。これは複数パターンを設定でき、ランダムに選択されるようです。
この Intent が呼ばれた時に Google Apps Script を呼び出したいので、ページ一番下の Fulfillment セクションで Enable webhook call for this intent を On にします。
Fulfillment
Fulfillment では、 Intent では設定できないような動的な返答を作成したり、外部の webhook を呼び出したりできます。
この Fulfillment の実態は Google Cloud Functions のようですが、左のナビゲーションの Fulfillment メニューで Inline Editor を On にすることで、 Dialogflow のコンソールから手軽に実装できます。
エディタに以下を記述します。
// index.js 'use strict'; // Import the Dialogflow module from the Actions on Google client library. const {dialogflow} = require('actions-on-google'); // Import the firebase-functions package for deployment. const functions = require('firebase-functions'); // Instantiate the Dialogflow client. const app = dialogflow({debug: true}); // Import request module to call POST request to the Google Apps Script API. const request = require('request'); // Handle the Dialogflow intent named 'poop condition' with parameters. app.intent('poop condition', (conv, {condition, amount}) => { // Google Apps Script API's URL. const uri = "https://script.google.com/macros/s/<API endpoint>"; let options = { uri: uri, headers: { "Content-type": "application/json", }, json: { "condition": condition, "amount": amount } }; return new Promise((resolve, reject) => { request.post(options, (err, res, body) => { if (err) { // Respond with an error message and end the conversation. conv.close('エラーが発生しました。'); console.log("error: " + err); resolve(); } else { // Respond with the message and end the conversation. conv.close(`わかりました。 ${condition}のうんちが${amount}ですね。`); resolve(); } }); }); }); // Set the DialogflowApp object to handle the HTTPS POST request. exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
前半はおまじないです。中盤の
app.intent('poop condition', (conv, {condition, amount}) => {...});
が poop condition の intent から呼び出される処理です。
後述する Google Apps Script の WEB API に condition
と amount
の値を POST しています。
処理が正常に完了したら「わかりました。〇〇のうんちが××ですね。」と言って会話を終了します。
Googleスプレッドシート
次は Google スプレッドシートへの記録と Slack に通知を送るための処理を Google Apps Script で実装します。
doGet()
関数や doPost()
関数を定義することで、 Google Apps Script を Web サーバーのように実行することができます。
今回はデータを受け取って Google スプレッドシートに記録するので、 doPost()
関数を以下のように定義します。
function doPost(e) { // Parse JSON object if (e == null || e.postData == null || e.postData.contents == null) { return; } let requestJSON = e.postData.contents; let requestObj = JSON.parse(requestJSON); // Get Spreadsheet object let ss = SpreadsheetApp.getActive() let sheet = ss.getActiveSheet(); // Append "date", "condition" and "amount" sheet.appendRow([new Date(), "💩", requestObj["condition"], requestObj["amount"]]); // Call slack webhook to post "condition" and "amount" slackPoopieReport(requestObj["condition"], requestObj["amount"]); let output = ContentService.createTextOutput(); output.setMimeType(ContentService.MimeType.JSON); output.setContent(JSON.stringify({ message: "Successfully saved." })); return output; } function slackPoopieReport(condition, amount) { const postUrl = "https://hooks.slack.com/services/<Webhook URL>"; let jsonData = { "text" : "こんにちは。うんちリポーターです :poop: " + condition + "のうんちが" + amount + "出ました。" }; let payload = JSON.stringify(jsonData); let options = { "method" : "post", "contentType" : "application/json", "payload" : payload }; UrlFetchApp.fetch(postUrl, options); }
事前に準備しておいた Slack の webhook を呼び出して Slack に通知も送れるようにします。
実装完了!!
冒頭の動画ではうんちの「状態」と「量」を分けて会話しましたが、1つの Intent で両方を扱うようにしたので、実は以下のように1回の会話で両方を登録することも可能です。
※音が出ます。音量にお気をつけください。
動画が見れない場合はこちら
最後に
Dialogflow は設定項目も多いので所々で迷子になりますが、全体としては少しのコーディング量で Google Assistant アプリが作れることがわかりました。
Fulfillment を使うことで会話の可能性が無限大に広がりますね!
弊社サービスのキッズリーではうんち以外にもいろいろな連絡帳の項目を扱うことができます。
保育士さんたちが手ぶらで連絡帳を記入出来る日も近いでしょう!
ユニファでは うんち 保育をハックするエンジニアを募集中です。
一緒にスマート保育園を実現しましょう!