Google Home、IFTTT、Googleスプレッドシートを使って独自音声コマンドでログをとる(ついでにNode.jsやngrokやらも使ってLINEやGoogle Homeに通知する)

はじめに

Google Homeを買ったので実用すべく、薬の服用ログを作ってみました。
朝昼夜の食後に薬を飲んだらGoogle Homeへ話しかけて記録をつけ、飲み忘れてたらLINEに通知が飛んでくるって動きになります。
IFTTTやらGoogle Apps Scriptやら初めて触ってみましたが、手軽に使えるのにとても便利でびっくりです。

※2017/10/18追記 飲み忘れ通知でGoogle Homeが教えてくれるようにもしました。

処理の流れ

image

独自音声コマンドでGoogleスプレッドシートに記録

独自の音声コマンドをがっちり作るためには「Actions on Google」を使うみたいですが、「IFTTT」を使えばさくっと簡単に作れちゃいました。
IFTTTはif文でおなじみの「if this then that」の略で、「this」と「that」にあたる部分に色んなサービスを割り当てることができます。
IFTTTはいつまで経っても一向に日本語化されず敬遠してたのですが、いざ使ってみると想像以上に簡単でこりゃ便利です。

「this」のGoogle Assistantレシピ

まず「this」にあたる部分ですが、こちらはGoogle Assistantを使います。

「this」のサービス選択画面で「assi」ぐらいまで打てば「Google Assistant」のみでフィルタされます。
image

Google Assistantの中にも色んな条件がありますが、今回は固定文言がトリガーとなる「Say a simple phrase」を使います。
image

そしてレシピが以下になります。
image

「What do you want to say?」にトリガーとなる文言を入れるんですね。
朝、昼、夜の3パターンがあるので「Say a phrase with a text ingredient」を使えばワイルドカード($)が使えてまとめることができるのですが、制限としてワイルドカードを1語目に持ってくることが出来ませんでした。
「今 $ の薬飲んだよ」のように2語目以降にもってくればいけますが、これだと冗長だし誤認識の確率も増えてしまいます。
なので今回はトリガーとなるレシピを朝、昼、夜の3パターン作成しています。
※もっとスマートに出来る方法あったら教えてください…

「that」のGoogle Driveレシピ

続いて「that」のレシピはGoogle Sheetsを使います。

「that」のサービス選択画面で「she」ぐらいまで打つと「Google Sheets」のみでフィルタされます。
image

レコードをどんどん追記していくので「Add row to spreadsheet」を選択します。
image

レシピはこうです。
image

「Formatted row」にスプレッドシートに記録する内容を記載します。
「|||」が区切り文字になります。
今回スプレッドシートに記載する内容は「日付」「時刻」「時間帯(朝、昼、夜)」としています。
ここでは「{{CreatedAt}}」と入力する事で日時の変数が得られるはずなのですが、バグというか現在の仕様で取得できないようです。
詳しくは→ Google Home IFTTT Sheets

なのでここでは時間帯情報以外は空とし、スプレッドシート側にて日時情報を埋め込むようにしています。

以上でIFTTTのレシピは完了です。

Googleスプレッドシート

Googleスプレッドシートでは以下のように記録されていきます。
薬ログ - Google スプレッドシート_20171014_171217.png

「Timezone」の値はIFTTTより入力されたものですが、「Date」「Time」は前述した通りIFTTTでは取得できません。
なのでGoogle Apps Script(以下GAS)を使って取得します。
コードは以下のようになります。

var sheet = SpreadsheetApp.getActiveSheet();

function addDate() {
  setDate(1, "yyyy/M/d");
  setDate(2, "H:m:s");
}

function setDate(col, format) {
  var lastrow = sheet.getLastRow();
  if (sheet.getRange(lastrow, col).getValue() == "") 
    sheet.getRange(lastrow, col).setValue(formatDate(new Date(), format));
}

function formatDate(date, format) {
  return Utilities.formatDate(date, 'Asia/Tokyo', format)
}

addDateにより最終行の1列目に日付、2列目に時刻が入ります。
この関数をGASのトリガー機能よりイベント駆動させます。
trigger.png

↑のように設定することでシート上で値の変更があった際にaddDateが実行されます。
IFTTTによってTimezoneの追記が行われた際に同じ行のDate、TimeがaddDateによって埋まるという仕組みです。

以上で独自音声コマンドでのGoogleスプレッドシート記録機能が完成です。

実際の動作

飲み忘れ通知

飲み忘れ通知は逆の流れでGoogleスプレッドシート(GAS)にて記録を定時チェックし、飲み忘れていた場合にIFTTTを経由しLINEへの通知と、「google-home-notifier」というnode.jsモジュールを経由したGoogle Homeからの音声通知を行います。

Googleスプレッドシートの定時チェック

GASによって実装しています。

var sheet = SpreadsheetApp.getActiveSheet();



//一日の通知スケジュール
function setSchedules() {
  setTrigger("checkMorning", 8, 30);
  setTrigger("checkNoon", 13, 0);
  setTrigger("checkNight", 20, 30);
}

//その日の指定時刻にトリガーを設定
function setTrigger(funcName, h, m) {
  var triggerDay = new Date();
  triggerDay.setHours(h);
  triggerDay.setMinutes(m);
  ScriptApp.newTrigger(funcName).timeBased().at(triggerDay).create();
}

//その日のトリガーを削除する関数(消さないと残る)
function deleteTrigger(funcName) {
  var triggers = ScriptApp.getProjectTriggers();
  for(var i=0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == funcName) {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}



//朝の薬チェック
function checkMorning() {
  checkLog("morning");
}

//昼の薬チェック
function checkNoon() {
  checkLog("noon");
}

//夜の薬チェック
function checkNight() {
  checkLog("night");
}



//指定時間帯の薬ログチェック
function checkLog(timezone) {
  //一時トリガーを消す
  deleteTrigger("check" + timezone.charAt(0).toUpperCase() + timezone.slice(1));

  //Date列の値を下からループ
  var datas = sheet.getRange("A2:C" + sheet.getLastRow()).getValues();
  for (;datas != "";) {
    var record = datas.pop();

    //当日日付チェック
    if (formatDate(record[0], "yyyy/MM/dd") == formatDate(new Date(), "yyyy/MM/dd")) {
      //ログチェック
      if (record[2] == timezone) return;

    //指定時間帯のログがなければIFTTT(Webhooks)経由でLINE通知&Google Home Notifier通知
    } else {
      //LINE通知
      var url = "https://maker.ifttt.com/trigger/drug_log/with/key/XXXXXXXXXXXXXXXXXXXXXX?value1=" + getTimezoneStr(timezone);
      UrlFetchApp.fetch(url);

      //Google Home Notifier通知
      var url = "https://xxxxxx.ngrok.io/google-home-notifier";
      var options = {"method": "post", "text": getTimezoneStr(timezone) + "の薬飲んだかな?"};
      UrlFetchApp.fetch(url, options);

      return;

    }
  }
}



//////// functions ////////////////////////

//日付フォーマット
function formatDate(date, format) {
  return Utilities.formatDate(date, 'Asia/Tokyo', format)
}

//時間帯を英和
function getTimezoneStr(timezone) {
  switch(timezone) {
    case "morning": return "朝";
    case "noon": return "昼";
    case "night": return "夜";
  }
}  

GASのトリガーでは厳密な時刻単位のキックが行えないため、以下の記事を参考に時刻単位のスケジューリングをコードで実装しています。
Google Apps Scriptの日毎のトリガーで時間をもっと細かく設定する

8:30、13:00、20:30にそれぞれ実行されるトリガーを生成する関数(setSchedules)を作り、それをトリガーで毎日実行させています。
trigger2.png

スケジューリング以外の処理はcheckLog関数に集約され、当日日付に対象時間帯(朝、昼、夜)のログが無ければIFTTTのWebhooks APIを叩いています。

ここから先はIFTTTの設定になります。

「this」のWebhooksレシピ

「this」にはWebhooksを使います。
Webhooksって単語は今回初めて知ったのですが、単純に外部サービスとかとの連携用Web APIって感じなのかな?
万能感がすごいあって何でもできる気になってくる。

「this」のサービス選択画面で「web」ぐらいまで打つと「Webhooks」のみでフィルタされます。
image

triggerは「Receive a web request」だけ。
image

パラメータも「Event Name」だけ。シンプル。
image

ここで入力した値が生成されるURLに埋め込まれ、一意のURLになります。
さっきのコードでいう、

var url = "https://maker.ifttt.com/trigger/drug_log/with/key/XXXXXXXXXXXXXXXXXXXXXX?value1=" + getTimezoneStr(timezone);

の「drug_log」の部分ですね。
「XXXXXXXXXXXXXXXXXXXXXX」には各々のキーが入ります。
またクエリストリング(?value1=)を3つまで付与することができ、今回はLINE側に渡す変数情報(朝、昼、夜)を付与しています。

このURLにアクセスすることで「that」のサービスが実行されます。

「that」のLINEレシピ

続いて「that」にLINEへの通知を設定します。
LINEじゃなくてIFTTTへのプッシュ通知(Notifications)でもいいけど、折角なのでLINEにしてみました。

サービス選択画面。
image

これもtriggerは「Send message」のみ。
image

パラメータは送り先とメッセージ。
image

「Recipient」にメッセージの送り先を指定するんだけど、これは「LINE Notify」って通知用アカウントかグループしか設定できません。
グループを指定する場合はLINE Notifyがメンバにいることが前提です。
↑の画像の設定にしておくことでLINE Notifyにメッセージを送ってくれます。

「Message」にはそのまま送られるメッセージを入れますが、今回は「{{Value1}}」という変数を使用しています。
この変数はWebhooksのクエリストリングに指定した値が参照されます。
今回でいうとここに朝、昼、夜のいずれかが入ってきます。

以上で飲み忘れ通知機能が完成です。

定時チェック時刻までに記録が無ければ以下のようにLINEに通知が飛びます。
line.jpg

2017/10/18追記 Google Homeで通知する

google-home-notifier」というNode.jsモジュールを使うとGoogle Homeを喋らす事ができます。
これをRaspberry Piに入れてGoogle Homeと同じネットワークに置き、ngrok経由で外からキックさせています。

google-home-notifierには「ngrok」が同梱されているので外からのアクセスも楽ちんです。
ngrokってのはローカルサーバに簡単にグローバルネットワークからアクセス出来るようするサービスです。
無料プラン・有料プランがあり、無料プランではサーバ起動の度にドメインが変わってしまいますのでそこんとこは注意です(google-home-notifierは無料プランぽいです)。

次の記事以降、グローバルネットワークからのアクセスにはFirebaseを利用しておりこっちの方がオススメです。

導入等、詳しくは↓の記事をご覧下さい。
GoogleHomeスピーカーに外部からプッシュして自発的に話してもらいます
Google Homeで時報を知らせる

おわりに

Google HomeもIFTTTもGoogle Apps Scriptも全部すごいっすね。
すごいけどIFTTTは画面ポチポチじゃなくてコード管理できたりしないんすか?
JSONでインポート・エクスポートとか。
GUIじゃ操作も管理もきついっす…

次は赤外線モジュール買ってきてラズパイと接続して電気、テレビ、エアコンあたりを音声操作できるようにしたいと思います。

※2017/10/22追記 赤外線モジュール「irMagician」を使って電気を音声操作できるようにしました。
Google Home、IFTTT、Firebase、Node.js、irMagicianを使ってシーリングライトを音声操作する

※2017/10/24追記 PS4も音声操作できるようにしました。
Google Homeに話しかけてPS4を操作してみる

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.