ひまじんもーど

日々思ったことを適当に。アニメ・ゲーム・ガジェット系になる……かな?

SwitchBot+IFTTT+GAS(GoogleAppsScript)で帰宅時のエアコン稼働を条件判断付きの自動化

himajin-mode.hatenablog.com
導入時に「部屋の温度を元に、帰宅時の冷房やら暖房を制御したいよねー」と言っていました。
単純なAPI叩くだけだと難しそうだし、どこかでロジックを書かなきゃダメだなぁ、
でも自分でそこまで環境準備するのだるいなぁ、と思ってネットをうろうろしていたのですが、
タイトルにある通りGAS(GoogleAppsScript)で出来そうな記事を見かけたので、
暇な休日の2時間ほどを使ってさっくり実現してみました。


個別の情報はあっても組み合わせて仕組みを作ってる記事はあんまり見ないので、
これは良い記事なのでは?(自画自賛


参考にさせていただいたサイトは以下。
GitHub - OpenWonderLabs/SwitchBotAPI: SwitchBot Open API Documents
IFTTTを利用したNature Remoの操作に限界を感じたのでAPIを使う | Be full stack
GASでウェブアプリケーション公開方法と実行・アクセスユーザー設定を解説 | AutoWorker〜Google Apps Script(GAS)とSikuliで始める業務改善入門
GAS超入門⑦ - LINEに通知してみる|スキプラ@元エンジニア|note


実際のソースコードは以下。
各種IDやトークンの取得方法は、上記参考サイトやググれば出てくるので割愛。
SwitchBotのデバイスIDやシーンIDは、APIを参考にPostmanかなんかで別途取得してください。

// グローバル変数
// SpreadSheetの情報。
var SpreadSheetId = "(スプレッドシートのID)";
var SpreadSheetName = "シート1";
// LINEの情報。
var LineDomain = "https://notify-api.line.me/api/notify";
var LineToken = "(LINENotifyから取得したトークン)";
// SwitchBot関係の情報。
var SwitchBotDomain = "https://api.switch-bot.com";
var SwitchBotToken = "(SwitchBotのトークン)";
var MeterId = "(温湿度計のデバイスID)";
var SceneIds = {
  // 冷房(強)
  overCooling : "(冷房(強)のシーンID)",
  // 冷房
  cooling : "(冷房のシーンID)",
  // 暖房(強)
  overHeating : "(暖房(強)のシーンID)",
  // 暖房
  heating : "(暖房のシーンID)"
};

// 室温からのエアコン稼働
function doGet(e) {
  // 部屋情報取得。
  var roomStatus = GetRoomStatus();

  // 取得した部屋情報を元にエアコン起動。
  var resultAircon = ExecuteAirConditioner(roomStatus);

  // 部屋情報、エアコン起動結果をスプレッドシートに書き込み。
  SaveResultToSpreadSheet(roomStatus, resultAircon);

  // 部屋情報、エアコン起動結果をLINEに送る。
  SendResultToLine(roomStatus, resultAircon);
}

// 部屋情報取得
function GetRoomStatus() {
  // 認証情報セット。
  var headers = {
    "Authorization" : SwitchBotToken,
    "Content-type": "application/json; charset=utf-8",
  };
  var options = {
    'headers': headers,
  };

  // レスポンスの取得。
  var response = UrlFetchApp.fetch(`${SwitchBotDomain}/v1.0/devices/${MeterId}/status`, options);
  var parsedInfo = JSON.parse(response.getContentText());

  // 部屋情報として返す。
  return new RoomStatus(parsedInfo['body']['temperature'], parsedInfo['body']['humidity']);
}

// エアコン実行
function ExecuteAirConditioner(roomStatus) {
  // 実行するシーンを判断。
  var sceneId = null;
  if (roomStatus.temperature > 27) {
    sceneId = SceneIds.overCooling;
  } else if(roomStatus.temperature > 25) {
    sceneId = SceneIds.cooling;
  } else if(roomStatus.temperature < 15) {
    sceneId = SceneIds.overHeating;
  } else if (roomStatus.temperature < 18) {
    sceneId = SceneIds.heating;
  } else {
    // 気温が18~25度の場合、何もせず終了。
    return "適温のため、エアコンの稼働なしです。";
  }

  // 認証情報セット。
  var headers = {
    "Authorization" : SwitchBotToken,
    "Content-type": "application/json; charset=utf-8",
  };
  var options = {
    'headers': headers,
    'method' : 'post', // 記載しないとエラー
  };

  // レスポンスの取得。
  var response = UrlFetchApp.fetch(`${SwitchBotDomain}/v1.0/scenes/${sceneId}/execute`, options);
  var parsedInfo = JSON.parse(response.getContentText());

  // 結果の判定。
  var result = "";
  if (parsedInfo['statusCode'] == 100) {
    var scene = Object.keys(SceneIds).filter( (key) => { 
      return SceneIds[key] === sceneId
    });

    result = `エアコンを"${scene}"で稼働しました。`;
  } else {
    result = "エアコンの起動に失敗しました。"
  }

  return result;
}

// 実行結果をスプレッドシートに書き込み。
function SaveResultToSpreadSheet(roomStatus, resultAircon) {
  // シートオープン。
  var sheet = SpreadsheetApp.openById(SpreadSheetId).getSheetByName(SpreadSheetName);

  // 最終実行日時。
  sheet.getRange('C2').setValue((new Date()));
  // 温度。
  sheet.getRange('C3').setValue(roomStatus.temperature);
  // 湿度。
  sheet.getRange('C4').setValue(roomStatus.humidity);
  // エアコン稼働結果。
  sheet.getRange('C5').setValue(resultAircon);
}

// 実行結果をLINEに送る。
function SendResultToLine(roomStatus, resultAircon) {
  var message = 
    `
    エアコン稼働:${resultAircon}
    温度:${roomStatus.temperature}
    湿度:${roomStatus.humidity}`;
 
  // 送信情報作成。
  var options = {
    "method" : "post",
    "headers" : {
      "Authorization" : "Bearer "+ LineToken
    },
    "payload" : {
      "message" : message
    }
  };

  // 送信。
  UrlFetchApp.fetch(LineDomain, options)
}

// 部屋情報クラス
class RoomStatus {
  constructor(temperature, humidity) {
    this.temperature = temperature;
    this.humidity = humidity;
  }
}


Javascript なんか10年ぶりぐらいに見たわ……。(普段はC#WPFアプリ作ってる)
当時も一瞬適当に書いただけだし、かっこいい書き方知らないからシンプルに。


doGetがWebHookのGETで呼ばれるらしい。


流れとしてはシンプルな感じ。
1.部屋情報(温湿度)取得
2.温湿度を元にエアコン用に作成したシーンを条件分岐して実行
3.スプレッドシートに実行結果書き込み
4.LINEに実行結果送信

コメント書いてるし、説明不要かな?
3はいらないかもね。4を後から追加したから、ちょっと機能的に重複。
温度の判断条件は適当に書き換えてください。暖房側とかマジで適当。


バグがあったら教えてください! 細かいテストはしてないです。


実行結果を張り付けるスプレッドシートのイメージはこんな感じ。


LINE通知はこんな感じ。


ソースコードをGASに貼って、Webアプリとして公開。
IFTTTからGPSをトリガにWebHookでリクエストを投げる、って感じで動きます。
テストとしてIFTTTからスマホの充電をトリガにして動作することは確認できました。
IFTTTのGPSトリガとの連携は家を出てないので実際の動きは確認できていないんですよねー。引きこもり。
家出たときに確認します。


ググりながらサクッと終ったのですが、
1点引っかかったのは、GASをWebアプリとしてデプロイした後に、
ソースコードを修正してもIFTTTから呼ぶWebアプリ側には反映されないこと。
デバッグでは動くのになんでや?と思っていたのですが、
単純に新バージョンをデプロイしてなかった、というだけでした。素人!


いやぁ、構築してみて感じますが、最近マジで便利な時代ですね。
クラウドサービス充実しすぎぃ! 自分で環境構築しなくて良いのはマジで楽。
環境構築するだけで1日潰すとか、仕事では当たり前ですからね……。


こういう「楽しい部分だけちょっとつまむ」ってことが出来るのは良いよね!
エアコンが稼働したり、結果がLINEで飛んできたりすると、
おお! 俺なんか仕組みをちゃんと作ってる!ってなりますもんね。


SwitchBot買った人は、せっかくのおもちゃだし色々組んでみるのもいいと思います。
多少はコードの知識がないときついかも知れないけど、ググればなんとかなりますよ!