🎃

【GAS】Nature Remoの温度や直近の操作をLINEに通知する

2021/08/24に公開

はじめに

祖母の認知症が進み、エアコンの操作が難しくなってきました。そこで、Nature Remo mini2を導入し、母や他の身内が遠隔でエアコンの操作を行えるようにしました。

ここで、以下の問題が発生。

  • たまに部屋が冷えない
  • だれが、どんな操作をしたか履歴が残らない

⇒Nature Remo mini2の動作履歴を取りつつ、操作履歴はLINEで通知することにしました。
LINEのイメージ

追記:たまに部屋が冷えない問題は、エアコンのリモコン自体が故障していた模様。別のエアコンのリモコンで登録したら、問題なく動くようになりました。

方針

管理費用等の負担がほぼないこと。
⇒サーバーなしで定期実行できる、Googleスプレットシート&GASで作成することにしました。

必要なもの

今回の開発に必要なものは以下となります。

  • Nature Remo (Nature Remo mini2を使用しました。約6000円)
  • LINE ID
  • Googleアカウント
  • スマートフォン
  • PC(開発用)

開発手順

  1. Nature APIを使えるようにする
    以下の記事を参考に、アクセストークンを発行しました。
    https://qiita.com/sohsatoh/items/b710ab3fa05e77ab2b0a

  2. Line Notifyを使えるようにする
    以下の記事を参考に、アクセストークンを発行しました。
    https://qiita.com/iitenkida7/items/576a8226ba6584864d95

  3. Googleスプレットシートを作成する
    Nature Remoがどのような動きをするかわからなかったこともあり、以下の情報をスプレットシートに記入するようにしています。
    B~F列がDevices(今回の場合は、Nature Remo mini2)のデータであり、G~K列はAppliances(今回の場合はエアコン)のデータです。
    Nature Remo Miniを使用しているため、湿度(D列)と照度(E列)は取得できていません。

  4. GoogleスプレットシートのIDを確認する
    IDは、GoogleスプレットシートのURL:https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxx/editの xxxxxxxxxxxxxxxx の部分です。

  5. スクリプトを作成する
    作成したGoogleスプレットシートを開き、「ツール」⇒「スクリプトエディタ」をクリックします。スクリプトエディタが開いたら、ソースコードを書いていきます。⇒(参考)作成したスクリプト

  6. スクリプトを定期実行するように設定する
    作成したスクリプトを1時間おきに実行するように設定します。
    スクリプトエディタの「トリガー」をクリックし、以下のトリガーを追加しました。

スクリプト

参考までに作成したスクリプトを紹介します。言語はGAS(Google Apps Script)です。
※アクセストークンは各自のものに置き換えてください。

参考:https://qiita.com/t-chi/items/01b9a9b98fbccef880c3

const access_token_remo = 'xxx'; //Nature Remoのアクセストークン
const access_token_line = 'xxx'; //LINE Notifyのアクセストークン
const spreadsheetId     = 'xxx'; //スプレッドシートのIDを入れる(URL参照)

const COL_DEVICE_TEMP         = 3;
const COL_DEVICE_HUMIDITY     = 4;
const COL_DEVICE_ILLUMINANCE  = 5;
const COL_DEVICE_UPDATE       = 6;

const COL_APPLIANCE_NAME      = 7;
const COL_APPLIANCE_VOL       = 8;
const COL_APPLIANCE_TEMP      = 9;
const COL_APPLIANCE_BUTTON    = 10;
const COL_APPLIANCE_UPDATE    = 11;

const COL_APPLIANCE2_NAME     = 12;
const COL_APPLIANCE2_UPDATE   = 13;

// Main Function
function remo() {
  // Stage 1
  var data = getNatureRemoData();    //data取得
  var data_appliances = getNatureRemoData_Appliances() //家電ごとのデータを取得

  // Stage 2
  var lastData = getLastData();     //最終date取得
  setLaremoData(
  {
    // Devices
    date:convertToJapanTime(data[0].newest_events.te.created_at),        //日時
    te:data[0].newest_events.te.val,                 //温度
    hu:'No data',  //data[0].newest_events.hu.val,   //湿度
    il:'No data',  //data[0].newest_events.il.val,   //照度
    updated_at:convertToJapanTime(data[0].updated_at),                   //更新日時

    // Appliance No.1
    appliances1_name        : data_appliances[0].nickname,
    appliances1_vol         : data_appliances[0].settings.vol,
    appliances1_temp        : data_appliances[0].settings.temp,
    appliances1_button      : data_appliances[0].settings.button,
    appliances1_updated_at  : convertToJapanTime(data_appliances[0].settings.updated_at),
  },
  lastData.row + 1//最終data追加作業
  );

  // Stage 3
  // Line Notifyで通知
  var temperature = data[0].newest_events.te.val;
  var nowData = getLastData();     //最終date取得

  Logger.log(nowData.data_deviceUpdate);
  Logger.log(lastData.data_deviceUpdate);
  let text = '';

  // (1)エアコンの操作があった
  if (nowData.data_applianceUpdate.getTime() > lastData.data_applianceUpdate.getTime()){
    // var text = '';
    if (nowData.data_applianceButton == 'power-off'){
      text = '※エアコン操作あり\n'
          + '操作:' + nowData.data_applianceButton + '\n' 
          + '設定温度:' + '\n' 
          + '現在室温:' + temperature + '℃\n' 
          + '日時:' + Utilities.formatDate(nowData.data_applianceUpdate,'JST', 'MM/dd HH:mm');
    }
    else{
      text = '※エアコン操作あり\n' 
          + '操作:' + 'power-onなど' + '\n'
          + '設定温度:' + nowData.data_applianceTemp + '℃\n' 
          + '現在室温:' + temperature + '℃\n' 
          + '日時:' + Utilities.formatDate(nowData.data_applianceUpdate,'JST', 'MM/dd HH:mm');     
    }
    lineNotify(text);
  }
  // (2)エアコンの操作はなかったが、その他の操作があった(Remoのアップデートがあった)
  else if (nowData.data_deviceUpdate.getTime() > lastData.data_deviceUpdate.getTime()){
    // テスト中。手動で割り当てたボタンの操作があった場合は、ここを通る。
    // ただし、ボタンのON/OFFの判別はできそうにない。
  }
}

/*
   Sub Function #1-1
   Nature Remo API経由でDevicesの情報を取得する関数
   Input:
   Output:
*/
function getNatureRemoData() {
  var url = "https://api.nature.global/1/devices";
  var headers = {
    'accept': 'application/json',
    'Authorization': 'Bearer ' + access_token_remo,
  };

  var postData = {

  };

  var options = {
    "method" : "get",
    "headers" : headers,
  };

  var data = JSON.parse(UrlFetchApp.fetch(url, options));
  Logger.log(data[0])

  return data;
}

/*
   Sub Function #1-2
   Nature Remo API経由でAppliancesの情報を取得する関数
   Input:
   Output:
*/
function getNatureRemoData_Appliances() {
  var url = "https://api.nature.global/1/appliances";
  var headers = {
    'accept': 'application/json',
    'Authorization': 'Bearer ' + access_token_remo,
  };

  var postData = {

  };

  var options = {
    "method" : "get",
    "headers" : headers,
  };

  var data = JSON.parse(UrlFetchApp.fetch(url, options));
  Logger.log(data[0])

  return data;
}

/*
   Sub Function #2-1
   スプレットシートの最終行の情報を取得する関数
   Input:
   Output:
*/
function getLastData() {
  var datas = SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getDataRange().getValues()  //logシートをゲットする
  var data = datas[datas.length - 1]

  return {
    data_deviceUpdate:data[COL_DEVICE_UPDATE-1],
    data_applianceUpdate:data[COL_APPLIANCE_UPDATE-1],
    data_applianceName:data[COL_APPLIANCE_NAME-1],
    data_applianceTemp:data[COL_APPLIANCE_TEMP-1],
    data_applianceButton:data[COL_APPLIANCE_BUTTON-1],

    row:datas.length
  }
}

/*
   Sub Function #2-2
   各種情報をスプレットシートに記入する関数
   Input: data, row
   Output:
*/
function setLaremoData(data, row) {
  // Devices
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, 1).setValue(new Date())//A2にゲットした日時ほりこむ
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, 2).setValue(data.date)  //A2に日時
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, COL_DEVICE_TEMP).setValue(data.te)  //B2に温度追加
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, COL_DEVICE_HUMIDITY).setValue(data.hu)  //C2湿度追加(幅があるけど気にしない)
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, COL_DEVICE_ILLUMINANCE).setValue(data.il)  //D2照度追加
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, COL_DEVICE_UPDATE).setValue(data.updated_at)  //アップデート日時(アプリから指令したら更新される)

  // Appliances No.1
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, COL_APPLIANCE_NAME).setValue(data.appliances1_name)       //名前
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, COL_APPLIANCE_VOL).setValue(data.appliances1_vol)        //vol
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, COL_APPLIANCE_TEMP).setValue(data.appliances1_temp)       //温度
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, COL_APPLIANCE_BUTTON).setValue(data.appliances1_button)       //ボタン
  SpreadsheetApp.openById(spreadsheetId).getSheetByName('log').getRange(row, COL_APPLIANCE_UPDATE).setValue(data.appliances1_updated_at) //更新日時
}

/*
   Sub Function #3-1
   LINE NotifyにPOSTする関数
   Input: LINEに通知する文字列
   Output:
*/ 
function lineNotify(postText){
  try {
    const url  = 'https://notify-api.line.me/api/notify';
    const params = {
      method: 'post',
      headers: {
        'Authorization': 'Bearer '+ access_token_line
      },
      payload: {
        message : postText
      }
    }

   const res = UrlFetchApp.fetch(url, params);
   console.log(res);

  } catch (error) {

    console.log(error);

  }
}

/*
   Sub Function #4-1
   文字列をDate型(日本時間)に変換する。
   Input: '2021-08-09T09:54:08Z'
   Output: +9h
*/
function convertToJapanTime(dateText) {
  let date;
  if (dateText != null){
    date = new Date(dateText);
  }
  else{
    date = 'null';
  }

  return date;
}

さいごに

とりあえず、冒頭で述べた問題は解決できたのでよかったです。
運用していく中で、以下の課題が判明しました。忘れないようにメモ。

  • 祖母が誤ってエアコンをOFFし、それを覚えておらず暑いと電話来るパターン
    ⇒見守りカメラを置き、こちらが視認できるようにする or リモコン隠す
    ⇒リモコンを隠すと、コンセント抜くことがわかりました...(別案検討中)
  • 祖母の暑い/寒いの基準が温度だけではわからない
    ⇒温度だけでなく、湿度も取得できると適切な操作や自動化ができる?
  • 遠隔でエアコンを操作すると、エアコンの音が鳴り、それが祖母の混乱を招く
    ⇒日立のエアコンは、操作音OFFの設定がなさそう。エアコンのブザー外す?

Discussion