WebAPI A2

 semi2018で開発したWebAPI A2をsemi2020用に移植した。WebAPI A2には次の機能がある。

  1. Googleカレンダーを服用済みに更新する
  2. 服薬開始時刻を指定した時間(単位:分)だけ遅らせる
  3. ngrokが払い出したurlをGoogleスプレッドシートに書き込む
  4. Google Home発話サーバに指定したメッセージ(テキスト)の発話を依頼する
  5. Google HomeのIPアドレスを取得する

上記の1~4はPOSTリクエスト、5はGETリクエストになっている。

Googleカレンダーを服用済みに更新する

Googleカレンダーを服用済みに更新するcurlコマンドを以下に示す。

curl -X POST -d '{"timing":"朝"}' https://script.google.com/macros/s/AKfycb...lmfTEz/exec

リスト1.Googleカレンダーを服用済みに更新するcurlコマンド

ここで、JSON形式のパラメタ"timing"は、""朝, "昼", "夜", "就寝前"という値をとり、各々「朝の食*の薬」、「昼の食*の薬」、「夜の食*の薬」、「就寝前の薬」に対応している。

服薬開始時刻を指定した時間(単位:分)だけ遅らせる

服薬開始時刻を指定した時間(単位:分)だけ遅らせるcurlコマンドを以下に示す。

curl -X POST -d '{"delay":"10"}' https://script.google.com/macros/s/AKfycb...lmfTEz/exec

リスト2.服薬開始時刻を指定した時間(単位:分)だけ遅らせるcurlコマンド

ここで、JSON形式のパラメタ"delay"には服薬開始時刻を遅らせる時間を分単位で指定する。このリクエストを受け取ったWebAPIは、該当する服薬スケジュールをdelayで指定した時間(単位:分)だけ遅らせる。なお、該当する服薬スケジュールは、カレンダーイベントの開始時刻~終了時刻にリクエストを受け取った時刻が入っているイベントが選ばれる。

ngrokが払い出したurlをGoogleスプレッドシートに書き込む

ngrokが払い出したurlをGoogleスプレッドシートに書き込むcurlコマンドを以下に示す。

curl -X POST -d '{"ngrok_url":"https://xxxxxxxx.ap.ngrok.io/google-home-notifier"}' https://script.google.com/macros/s/AKfycb...lmfTEz/exec

リスト3.ngrokが払い出したurlをGoogleスプレッドシートに書き込むcurlコマンド

ここで、JSON形式のパラメタ"ngrok"はGoogle Home コントローラ YakuShare.js が起動時に払い出すngrokのURLである。これによってコントローラの外部公開用URLがGoogleスプレッドシートの「設定」シートに書き込まれ、発話依頼アプリケーションが参照してこのURLに向けてリクエストを送ることになる。

Google Home発話サーバに指定したメッセージ(テキスト)の発話を依頼する

Google Home発話サーバに指定したメッセージ(テキスト)の発話を依頼するcurlコマンドを以下に示す。

curl -X POST -d '{"speak":"こんにちは"}' https://script.google.com/macros/s/AKfycb...lmfTEz/exec

リスト4.Google Home発話サーバに指定したメッセージ(テキスト)の発話を依頼するcurlコマンド

ここで、JSON形式のパラメタ"speak"は発話を依頼したいメッセージである。Google Homeには強制発話する機能が標準で備わっていないので、非同期で発話させたいときはこのリクエストを送るとよい。

Google HomeのIPアドレスを取得する

Google HomeのIPアドレスを取得するcurlコマンドを以下に示す。

curl -L -X GET https://script.google.com/macros/s/AKfycb...lmfTEz/exec?ip=google_home_ip

リスト5.Google HomeのIPアドレスを取得するcurlコマンド

Google HomeのIPアドレスはGoogleスプレッドシートの「設定」シートにマニュアルで設定する(IPアドレスそのものはスマホアプリGoogle Homeで確認しなけれわからない)。その設定されたIPアドレスを取得するのがこのWebAPIである。


WebAPI A2のソースプログラム

ドキュメントID

トラブル時、デバッグを容易に進めるためにログをGoogleドキュメントに吐き出すようになっている。リスト6で定義するDEBUG_IDはそのGoogleドキュメントのIDである。

また、ngrok払い出しURLやGoogle HomeのIPアドレスはGoogleスプレッドシートの「設定」シートで管理しているが、そのファイルを識別するのがPILL_REMINDER_IDである。

/***********************************************************************
 *
 * ドキュメントID
 *
 ***********************************************************************/
const DEBUG_ID = "1B0gks...K52rys";
const PILL_REMINDER_ID = "1qSb5r..._fH-sk";

リスト6.ドキュメントIDの宣言

HTTPリクエストのエンドポイント関数

リスト7にPOSTメソッドのエンドポイント関数doPostおよびGETメソッドのエンドポイント関数doGetを示す。

/**
 * POSTメソッドエンドポイント関数
 * @param  {Object}   e     POSTメソッドで送られてくるデータ
 *  delay: 10 (開始を10分間遅らせる)
 *  timing: [朝|昼|夜|就寝前]
 *  ngrok_url: https://xxxxxxxx.ap.ngrok.io/google-home-notifier
 *  speak: こんにちは(Google Home発話サーバにメッセージを送る)
 *
 */
function doPost(e) {
  var params = JSON.parse(e.postData.getDataAsString());  // ※
  
  if (params.timing) {
    //カレンダーのタイトルと色を変える
    var timing = params.timing;  // => [朝|昼|夜|就寝前]が取れる
    NotifyNotTaking(timing);
  } else if (params.delay) {
    //開始時刻を遅らせる
    var delay = parseInt(params.delay);  // => 遅らせる時間[分]
    // どうやって遅らせる対象のイベントを見つけるか?
    delayStartTime(delay);
  } else if (params.ngrok_url) {
    //ngrokの払い出したurlをGoogleスプレッドシートに書き込む
    setNgrokUrl(params.ngrok_url);
  } else if (params.speak) {
    //Google Homeに発話依頼を送る(2021.2.17追加)
    GOOGLE_HOME_notify(params.speak);
  }
  
  var out = ContentService.createTextOutput();
  
  //Mime TypeをJSONに設定
  out.setMimeType(ContentService.MimeType.JSON);
  
  //JSONテキストをセットする
  result = {
    status: 'OK'
  };
  out.setContent(JSON.stringify(result));
  
  return out;
}

/**
 * HTTP GETをハンドリングする。呼び出される。
 * @param {object} e query string
 */
function doGet(e) {
  
  var response = {};
  
  // idの有無に応じて呼び出す関数を変える
  if(e.parameter.ip == 'google_home_ip') {
    response.ip = getGoogleHomeIp();
  }
  
  var out = ContentService.createTextOutput();
  
  //Mime TypeをJSONに設定
  out.setMimeType(ContentService.MimeType.JSON);
  
  //JSONテキストをセットする
  out.setContent(JSON.stringify(response));
  
  return out;
}

リスト7.HTTPリクエストのエンドポイント関数doPost, doGet

POSTメソッドでは、関数e.postData.getDataAsString()によってJSON形式のパラメタ(curlコマンドで-dオプションに設定したデータ)が取得できる。doPost関数内では、そのパラメタの項目によって処理を分岐する。例えばリスト1に示すようにtimingというパラメタがあればNotifyNotTaking()関数を呼び出して、カレンダーの服薬イベントの色を変更する(同時にタイトルを「未」から「済」にする)。

GETメソッドでは、クエリストリングスが連想配列(辞書)e.parameterでアクセスできる。

Googleカレンダーを服用済みに更新する

リスト8にGoogleカレンダーを服用済みに更新する関数NotifyNotTaking()を示す。

/**
 * 「[朝|昼|夜|就寝前]のお薬飲んだよ」に対して服用済みに変えるファンクション
 *
 * @param  {String}   timing     [朝|昼|夜|就寝前]
 *
 */
function NotifyNotTaking(timing) {
  
  // 当日の予定の中から timing (朝|昼|夜|就寝前)を含む予定を検索
  var events = CalendarApp.getDefaultCalendar().getEventsForDay(
    new Date(),
    {search: timing}
  );  

  // 取得した予定からターゲットの予定を探してタイトルと色を変更
  events.forEach(function(event,i,array){ 
    var title = event.getTitle();
    if(title.match(/^.の食.の薬\(.+\)$/) || title.match(/就寝前の薬\(.+\)$/)) {
      // 現在時刻と開始時刻(start_time)~終了時刻(end_time)を比較し、時間内であればタイトルを(済)にし、色を変え、LINEへ通知する
      if (withinTime(event.getStartTime(), event.getEndTime())) {
        // タイトルの変更
        // ”(”の文字の位置を取得する(http://cya.sakura.ne.jp/js/string.htm#indexOf)
        result = event.getTitle().indexOf("(");
        // "("以降の文字を切り取る。
        results = event.getTitle().substr(0,result);
        // タイトルを○○の薬(済)に変更する。  
        event.setTitle(results+'(済)');
        // 色を青に変更する。
        event.setColor(CalendarApp.EventColor.BLUE);
        //LINEに送るメッセージ
        LINE_notify(timing+'のお薬を飲みました。');
      } else {
        // 現在時刻と開始時刻(start_time)~終了時刻(end_time)を比較し、時間外であればベアボーン上で稼働している発話PUSHサーバ(example.js)へ発話依頼する。
        var message = '時間外に「' + timing + 'のお薬をのみました」とのメッセージを受け取りました。この要求を無視します。';
        LINE_notify(message);
        GOOGLE_HOME_notify(message);
      }
    }
  });
}

/**
 * 現在時刻が開始時刻(start_time)~終了時刻(end_time)の間にあるかどうかをチェックする
 * @param  {Date}   start_time     日付
 * @param  {Date}   end_time       日付
 * @return {Boolean}               true:現在時刻が開始時刻(start_time)~終了時刻(end_time)の間にある
 *                                 false: 〃 間にない
 */
function withinTime(start_time, end_time) {
  var current_time = new Date();
  var result = (start_time <= current_time && current_time <= end_time) ? true : false;
  return result;
}

// Lineにメッセージを送るファンクション
function LINE_notify(message){
  var token = "1tKsT4...vnUrqj";
  var options = {
    "method"  : "post",
    "payload" : "message=" + message,
    "headers" : {"Authorization" : "Bearer "+ token}
  };
  UrlFetchApp.fetch("https://notify-api.line.me/api/notify",options);
}

リスト8.Googleカレンダーを服用済みに更新する関数NotifyNotTaking()

服薬開始時刻を指定した時間(単位:分)だけ遅らせる

リスト9に服薬開始時刻を指定した時間(単位:分)だけ遅らせる関数delayStartTime()を示す。

/**
 * 現在時刻の予定を指定した時間だけ遅らせる
 * @param  {Integer}   delay     遅らせる時間(分)
 */
function delayStartTime(delay) {
  // 当日の服薬予定(未)をすべて取得する
  var events = CalendarApp.getDefaultCalendar().getEventsForDay(
    new Date(),
    {search: '(未)'}
  );  

  // 取得した予定から現在時刻が開始~終了時刻に含まれる予定を探してdelay(分)だけ遅らせる
  var current_time = new Date();
  events.forEach(function(event, i, array){
    var start_time = event.getStartTime();
    var end_time = event.getEndTime();
    if (start_time <= current_time && current_time <= end_time) {
      event.setTime(
        delayTime(start_time, delay), 
        delayTime(end_time, delay)
      );
    }
  });
}

// 引数に指定した日時target_timeをdelay分だけ遅らせる
function delayTime(target_time, delay){
  var delay_time = new Date(
    target_time.getFullYear(), 
    target_time.getMonth(), 
    target_time.getDate(), 
    target_time.getHours(), 
    target_time.getMinutes() + delay, 
    target_time.getSeconds() 
  );
  return delay_time;
}

リスト9.服薬開始時刻を指定した時間(単位:分)だけ遅らせる関数delayStartTime()

ngrokが払い出したurlをGoogleスプレッドシートに書き込む

リスト10にngrokが払い出したurlをGoogleスプレッドシートに書き込む関数setNgrokUrl()を示す。

/**
 * ngrokのurlをGoogleスプレッドシートに書き込む
 * Googleスプレッドシート名:pill-reminder
 * セル:7行2列
 * @param  {String}   url     ngrokが払い出したurl
 */
function setNgrokUrl(url) {
  var spreadsheet = SpreadsheetApp.openById(PILL_REMINDER_ID);
  var sheet = spreadsheet.getSheetByName('設定');
  sheet.getRange("B7").setValue(url);
}

リスト10.ngrokが払い出したurlをGoogleスプレッドシートに書き込む関数setNgrokUrl()

Google Home発話サーバに指定したメッセージ(テキスト)の発話を依頼する

リスト11にGoogle Home発話サーバに指定したメッセージ(テキスト)の発話を依頼する関数GOOGLE_HOME_notify()を示す。

// google-home-notifyにメッセージを送るファンクション
function GOOGLE_HOME_notify(message){
  var options = {
    "method"  : "post",
    "payload" : "text=" + message
  };
  var ngrok_url = getNgrokUrl();
  if(ngrok_url == '') {
    debug('GOOGLE_HOME_notify: ngrok_urlが設定されていません!');
    return;
  }
  UrlFetchApp.fetch(ngrok_url, options);
}

/**
  * ngrokのurlをGoogleスプレッドシートから読み込む
  *
  */
function getNgrokUrl() {
  var spreadsheet = SpreadsheetApp.openById(PILL_REMINDER_ID);
  var sheet = spreadsheet.getSheetByName('設定');
  return sheet.getRange("B7").getValue();
}

リスト11.Google Home発話サーバに指定したメッセージ(テキスト)の発話を依頼する関数GOOGLE_HOME_notify()

Google HomeのIPアドレスを取得する

リスト12にGoogle HomeのIPアドレスを取得する関数を示す。

/**
  * google_home_ipのipをGoogleスプレッドシートから読み込む(B8)
  *
  */
function getGoogleHomeIp() {
  var spreadsheet = SpreadsheetApp.openById(PILL_REMINDER_ID);
  var sheet = spreadsheet.getSheetByName('設定');
  return sheet.getRange("B8").getValue();
}

リスト12.Google HomeのIPアドレスを取得する関数

0 件のコメント:

コメントを投稿