🤖

レンタルスペース x SwitchBot = ∞の可能性

に公開

はじめに

スペースマーケットのエチケットリーダーseoと申します。
普段はAndroidやFlutterを中心に開発していますが、
最近はかねてより興味のあったバックエンド開発にも挑戦しています。

30歳を過ぎてからの異業種へのチャレンジは一般的にハードルが高いとされていますが、
そうした挑戦を応援してくれるスペースマーケットの環境には、感謝カンゲキ雨嵐な毎日です!

レンタルスペース運営始めました

昨年11月に社内制度を活用してレンタルスペース運営を始めてみました。
こちらがオープンしたレンタルスペースです。

下記リンクから予約すると今なら10%オフで利用できますのでぜひご利用してみてください!!
https://spacemarket.com/p/BJrDoClM4O0CdJ0A

これまで利用者としてレンタルスペースを使うことは多かったのですが、
サービス事業者として、スペースを運営するホストの気持ちをもっと身近に知りたいという思いから始めてみました。

こちらのスペースは「シネマスペース」として、
映画鑑賞やスポーツ観戦、大画面でのゲームプレイなどにご利用いただいています。

比較的部屋が汚れにくい用途ではありますが、前の利用者の髪の毛やほこりなどが残っていることも。そこで、清掃作業をロボット掃除機で自動化できないかと考えました。

SwitchBotのロボット掃除機がよさそう

導入したのはSwitchBotロボット掃除機K10+Proです。

https://www.switchbot.jp/products/switchbot-robot-vacuum-cleaner-k10-pro?srsltid=AfmBOoojfmiHGfe_AXsUU86rvswIlI4P4lnQwL3gmM_30z7WRLeU2HKK

他社製品に比べてコンパクトで、スペースの隅に置いても邪魔にならないちょうど良いサイズ感。
レンタルスペース向きだと感じました。

さらにうれしいことに、APIがとても充実しているのです。
これは、運営と自動化を組み合わせるには理想的なIoTデバイス!
https://github.com/OpenWonderLabs/SwitchBotAPI

今回のユースケースでは、
Googleカレンダーに連携されているスペースの予約情報を見て、
利用完了後10分が経過したら、SwitchBot APIを使って清掃を開始する。
というワークフローをGASで組んでみたいと思います!

いざ実装!!

まずはSwitchBot APIでロボット掃除機を動かすところを作っていきましょう!

Step 0: APIの下準備

APIを使うためには、アクセストークンクライアントシークレットが必要になりますので、
こちらの公式ガイドの手順に従って、
SwitchBotアプリの開発者向けオプションを有効にしましょう。

Step 1: deviceIdを取得する

対象のロボット掃除機を操作するにはdeviceIdが必要です。
GET /v1.1/devicesのエンドポイントで現在連携済みのデバイス一覧を取得しましょう。

SwitchBot APIのv1.1のリクエストヘッダーにこちらの情報が必要となります。

key Type 説明
Authorization String 0で取得したSwitchBotのアクセストークン
t Long 13桁のUNIXタイムスタンプ(現在時刻でOK)
nonce String ランダムな一意の値(UUID)
sign String token + t +nonceに秘密鍵で署名した値

GASでこちらのメソッドを実行し、t/nonce/signの値を取得しましょう。

generateSwitchBotHeaders
function generateSwitchBotHeaders() {
  const token = '【アクセストークン】';
  const secret = '【クライアントシークレット】';

  const t = Date.now().toString(); // 13桁のUNIXミリ秒タイムスタンプ
  const nonce = Utilities.getUuid(); // ランダムUUID
  const data = token + t + nonce;

  // HMAC-SHA256で署名(Uint8Array → base64)
  const signBytes = Utilities.computeHmacSha256Signature(data, secret);
  const sign = Utilities.base64Encode(signBytes);

  Logger.log('t: ' + t);
  Logger.log('nonce: ' + nonce);
  Logger.log('sign: ' + sign);
}

出力されたt/nonce/signを使って、postmanなどでK10+ ProのdeviceIdを確認してください。

レスポンス
{
    "statusCode": 100,
    "body": {
        "deviceList": [
            {
                "deviceId": "360TXYZ", <<<<<<<今回使用するdeviceId
                "deviceName": "ロボット掃除機K10+ Pro",
                "deviceType": "K10+ Pro",
                "enableCloudService": true,
                "hubDeviceId": ""
            },
            {
                "deviceId": "XXXXXXX",
                "deviceName": "お風呂ボッド",
                "deviceType": "Bot",
                "enableCloudService": true,
                "hubDeviceId": ""
            },
            {
                "deviceId": "XXXXXXX",
                "deviceName": "ハブ2 E4",
                "deviceType": "Hub 2",
                "enableCloudService": true,
                "hubDeviceId": ""
            }
        ],
        "infraredRemoteList": [
            {
                "deviceId": "XXXXXXXXXX",
                "deviceName": "テレビ",
                "remoteType": "TV",
                "hubDeviceId": ""
            },
            {
                "deviceId": "XXXXXXXXX",
                "deviceName": "エアコン",
                "remoteType": "Air Conditioner",
                "hubDeviceId": ""
            }
        ]
    },
    "message": "success"
}

ロボット掃除機K10+ ProのdeviceId: 360TXYZが取得できましたね🎉(実際のidは異なります)

Step 2: 掃除を開始する

先ほど作成したt/nonce/signのヘッダー情報を生成するメソッドを使い回せるように修正しましょう!

generateSwitchBotHeaders
function generateSwitchBotHeaders() {
   const token = '【アクセストークン】';
   const secret = '【クライアントシークレット】';

   const t = Date.now().toString(); // 13桁のUNIXミリ秒タイムスタンプ
   const nonce = Utilities.getUuid(); // ランダムUUID
   const data = token + t + nonce;

   // HMAC-SHA256で署名(Uint8Array → base64)
   const signBytes = Utilities.computeHmacSha256Signature(data, secret);
   const sign = Utilities.base64Encode(signBytes);
-  Logger.log('t: ' + t);
-  Logger.log('nonce: ' + nonce);
-  Logger.log('sign: ' + sign);
+  return {
+    'Authorization': token,
+    'sign': sign,
+    't': t,
+    'nonce': nonce,
+    'Content-Type': 'application/json'
+  };

次に、K10+Proを発進させましょう!
ドキュメントを見ると、K10+Proを動かすコマンドは4つありますね。

deviceType commandType Command Command parameter Description
K10+ Pro command start default 清掃開始
K10+ Pro command stop default 清掃中断
K10+ Pro command dock default 充電ドックへ戻る
K10+ Pro command PowLevel {0-3} 吸引力変更: 0 (Quiet), 1 (Standard), 2 (Strong), 3 (MAX)

K10+Proは登録されたマップの清掃が完了すると、自動で充電ドックへ戻りますので、
今回は'start'のコマンドだけで十分ですね!
実際にコードを書いてみましょう!

startCleaning
function startCleaning() {
  const deviceId = '【1で取得したdeveiceId】';
  const url = `https://api.switch-bot.com/v1.1/devices/${deviceId}/commands`;
  
  const payload = {
    command: 'start',
    parameter: 'default',
    commandType: 'command'
  };
  
  const headers = generateSwitchBotHeaders();

  const options = {
    method: 'post',
    headers: headers,
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };
  
  const response = UrlFetchApp.fetch(url, options);
  Logger.log(response.getContentText());
}

startCleaningメソッドを実行すると...

successが返り、実際にK10+Proが清掃を開始しました🎉

Step 3: Googleカレンダーと連携

掃除機をAPIで動かすことを確認できたので、
次はGoogle Calendarと連携して、利用完了に連動させて清掃開始させてみたいと思います!

スペースマーケットでスペースを予約すると、
【予約完了】【片付け時間】という2つの予約がカレンダーに自動作成されます。

今回は【片付け時間】の予定をフックにして清掃開始してみましょう。
ただ、利用完了後すぐに清掃開始してしまうと、まだゲストが退出していない可能性もありますので、
【片付け時間】の開始時間10分後に清掃開始するようにしてみましょう⏱️

checkCleaningEvent
function checkCleaningEvent() {
  const calendar = CalendarApp.getDefaultCalendar();
  const now = new Date();
  const oneMinuteAgo = new Date(now.getTime() - 1 * 60 * 1000);
  const oneMinuteLater = new Date(now.getTime() + 1 * 60 * 1000);

  // 今日のすべてのイベントを取得
  const events = calendar.getEventsForDay(now);

  for (const event of events) {
    const title = event.getTitle();
    if (!title.startsWith("【片付け時間】")) continue;

    const start = event.getStartTime();
    const targetTime = new Date(start.getTime() + 10 * 60 * 1000); // 開始+10分

    // 「今」が targetTime ±1分 以内か確認
    if (targetTime >= oneMinuteAgo && targetTime <= oneMinuteLater) {
      Logger.log(`条件一致:「${title}」の開始10分後 → 掃除開始`);
      startCleaning();
      break;
    }
  }
}

この関数をGASの時間主導型トリガーで毎分実行するように設定すれば、常に最新のイベント状況を監視できます。

【片付け時間】のついた予定を検知すると、清掃を開始しましたね🎉

GAS/SwitchBot APIの使用制限について

毎分実行するのはGASの使用制限に引っかからないのか...🤔
確認してみました!

機能 制限
トリガーの合計実行時間 1日90分
URL取得の呼び出し 20,000件/日

*https://developers.google.com/apps-script/guides/services/quotas?hl=ja

今回のユースケースで考えると、1つのトリガーの実行時間は約1~2秒。
それが毎分行われるので、2秒 X 1440回 = 2,880秒(48分)
90分には収まりそうですね!

また、URLの呼び出し、つまりSwitchBot APIへのリクエストは、
1日に入る予約数とイコールなため、約0~2件なのでこちらも問題なし!

ちなみに、SwitchBot APIにも利用制限がありますが、
こちらにも引っかかることはないと思います!

Request limit
The amount of API calls per day is limited to 10000 times. Going over that limit will return "Unauthorized."

*https://github.com/OpenWonderLabs/SwitchBotAPI?tab=readme-ov-file#request-limit

レンタルスペース x SwitchBot = ∞の可能性

今回は清掃を自動化を試してみましたが、他にもSwitchBotにはさまざまなIoTデバイスがあります!
オートロック、カメラ、照明、人感センサー、温度湿度計、赤外線リモコン、スマートプラグetc...

これらのIoTデバイスを組み合わせて、いろんなアイデアを考えるのが楽しいですね!
例えば:

  • 利用終了と同時にエアコン・TVの電源をオフにする(SwitchBot Remote Hub)
    • 消し忘れ+省エネを実現!
  • SwitchBotロック用の一時解錠キーの発行
    • 現状、同じキーを使いまわして、定期的にキーを変更するという運用が多いと思いますので、よりセキュリティが向上しますね!
  • SwitchBotカメラ/人感センサーを使って、無断延長や未退出を検出
    • 検出したらAlexaが「スペースマーケットアプリから予約時間を延長できます」とか話したらおもしろそうですね!

他にも面白いアイデアがあれば、ぜひコメントで教えてください!

最後に

スペースマーケットでは一緒に働く仲間を募集しています!
カジュアルに話を聞きたいだけという方でも大歓迎ですので、ちょっとでも興味があれば以下からご応募お待ちしております!

スペースマーケット Engineer Blog

Discussion