SwitchBotプラグミニとGASでテレビのON/OFFを検知しLINEに通知する

ちょっとしたきっかけから「テレビのON/OFFの状態を外出先から検知したい」ということになり実装することに。
スマートリモコンだと「現在の電源ON/OFFの状態」は確認できないし、専用のモバイルアプリで操作できる最新のテレビであってもスマホとテレビが同じネットワーク内にいないと使えないし、という具合に意外にもハードルが高そうだったのですが、最終的に「テレビの電流量から判定」という方法に着地しました。

# 電流量はSwitchBotプラグミニで確認できる
「SwitchBotプラグミニ」はWifiやBluetoothの機能を持つ電源プラグです。

https://www.switchbot.jp/products/switchbot-plug-mini?currency=JPY&variant=41437146054831

テレビの電源をこの電源プラグを介して取ることによりテレビに流れる電流量の値を取得することができます。
![yaritaikoto.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/476b3d41-4c96-806f-6089-b43d150f29ea.png)

ちなみにこの商品はコネクタ(テレビ等のプラグを差し込む穴)も刃(穴に差込むプラグ)とも、左右の大きさが異なります。
差込先のプラグの穴が左右同じ場合、左側の刃のサイズが合わず差し込めませんので、事前によくご確認ください。
![plughikaku.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/dbd7e16d-a38b-df1c-8734-c4be72e82ee0.png)


なぜ左右比が異なる場合があるのかについても豆知識程度に掲載しておきます。

https://www.jewa.or.jp/qa/receptacle.html#006

# SwitchBotアプリから電流量を確認する
上述の点を注意しつつ、テレビ → SwitchBotプラグミニ → 電源プラグ等に接続します。

そのうえでお手元のモバイル端末にSwitchBotアプリをアプリストアからDLしインストールします。

(Apple Store)

https://apps.apple.com/us/app/switchbot/id1087374760

(Google Play)

https://play.google.com/store/apps/details?id=com.theswitchbot.switchbot&hl=ja


インストール以降のセットアップ方法については機器の取扱説明書や以下をご確認ください。

(プラグミニのセットアップ方法)

https://support.switch-bot.com/hc/ja/articles/12593444141591-プラグミニのセットアップ方法

これらの準備ができたら、動作を確認してみましょう。

(iPhoneアプリで確認)
![appmonitor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/8c6f1b83-8cd8-d78c-5e59-7e6ca599e2a1.png)

画像の「0.2A」或いは「2.2A」が今回確認したい電流量になります。
左側の図がテレビの電源をOFFにしている時のもの、右側の図が電源をONにしている時のものです。
当然のことながら、電源をONにしている時の方が電流量の値が大きくなっていることが分かります。
これでテレビに流れる「現在の」電流量を測ることができるようになりました。

# 電流量のログを蓄積
アプリから確認できるのはリアルタイムの電流量なので、このままではずっとアプリ画面と睨めっこしておかなければなりません。
そういうわけにはいかないので、今回は取得した電流量をGoogleスプレッドシートに記録し蓄積していきます。

### 手順1: スプレッドシートの設定
1. 新しいスプレッドシートを作成  
(ここでは「スマートミニプラグ監視」という名前にする)
2. 画像のようなテーブルを作成  
(今回の場合、テーブルは単なる見栄えなので、必ずテーブルを作成する必要はない)
A列: 取得日時
B列: 起動状況
C列: 電流(mA)
D列: 電圧(V)
![スクリーンショット 2024-09-02 21.22.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/db3ee1fa-ccd6-f07c-d5f2-30bd92360dad.png)

### 手順2: SwitchBot APIの認証情報を設定する
Googleスプレッドシートに蓄積するデータはAPI経由で取得します。
SwitchBotのAPIの利用には事前に認証情報の設定が必要です。
#### ①SwitchBotアプリでの設定
まずは先ほどインストールしたSwitchBotのモバイルアプリを使い、以下の手順でトークンを発行します。
※2024/8/24時点。アプリバージョン: V8.8.1
1. SwitchBotアカウントを作成しサインイン
2. 「プロフィール」へ移動
3. 「設定」を選択
![スライド1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/3d777aa7-a7b7-885e-44ee-c19948812d8b.jpeg)
4. 「アプリのバージョン」を10回タップすると、「開発者向けオプション」メニューが表示される
5. 表示された「開発者向けオプション」を選択
6. 「トークンを取得」をタップすると、トークンとクライアントシークレットが発行される
![スライド2.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/f4f1695a-0ffa-04d3-4ab6-e3c28f09db59.jpeg)

以下の公式ドキュメントもぜひご確認ください。

https://blog.switchbot.jp/announcement/api-v1-1/

#### ②Googleスプレッドシートでの設定
①で取得したトークンとクライアントシークレットをGoogleスプレッドシートで使えるよう設定します。
直接コード上にトークン等を記述するハードコーディングはセキュリティ上の理由から避ける必要があります。  
そこで、以下の手順のとおりスプレッドシートの「スクリプト プロパティ」(一般的な「環境変数」に相当)にトークン等を格納し内部的に呼び出せるようにします。
1. スプレッドシート上部のメニューにある「拡張機能」の配下にある「Apps Script」を選択。すると、新しいタブでコードエディタが表示される
![スクリーンショット_2024-08-31_22_34_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/0c60f887-6941-962e-90c9-f0f5772fd002.png)
2. エディタの左側にある歯車アイコンにカーソルを合わせると表示される「プロジェクトの設定」を選択
![スクリーンショット_2024-08-31_22_33_34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/1658be42-964c-359c-ede5-afa4f3f4fa62.png)
3. 設定画面の一番下に「スクリプト プロパティ」という項目がある。これを設定するため「スクリプト プロパティを追加」を選択
![スクリーンショット_2024-08-31_22_43_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/315a6d56-373a-1452-ef0b-5dcfbb1b0705.png)
4. スクリプト プロパティ(環境変数と同義)の設定欄が表示されるので、以下のとおり設定
    (1)「プロパティ」欄で適当な名前を入力
    (2) ①の6.で取得したトークンを貼り付ける
    (3) クライアントシークレットも続けて登録するため「スクリプト プロパティを追加」を選択
![スクリーンショット_2024-08-31_22_49_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/2bc42fb8-fd96-d810-73dd-aa736fbf6d1a.png)
    (4) (1)〜(2)と同様、クライアントシークレットの情報を入力
    (5) ここまで入力した内容を保存するため「スクリプト プロパティを保存」を選択
![スクリーンショット_2024-08-31_22_58_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/bcb2de76-8441-ba25-6766-ded32cef7c58.png)

これでSwitchBot APIを利用するために必要な認証情報の設定が完了しました。

### 手順3: Apps ScriptでSwitchBot APIを操作
#### ①SwitchBot API でデバイスIDを取得
先程設定した認証情報のほかにも、電流量の取得にはSwitchBotプラグミニの"デバイスID"が必要です。
デバイスIDはSwitchBot APIから取得できます。
APIには認証情報を付してデバイスIDを含む情報の呼び出し(リクエスト)を行いますが、このとき、リクエストが正しいユーザーから送信されたことを証明するための「署名」が必要となります。
この署名の方法について以下のドキュメントで説明されています。

https://github.com/OpenWonderLabs/SwitchBotAPI?tab=readme-ov-file#how-to-sign

> ##### How to Sign?
> We have attached some scripts for you to quickly generate a sign. If you prefer to write your own script or routine, here is the procedure.
> 1. Print the 13 digit timestamp and concatenate it with your token
> 2. Create a signature using your secret and the string produced in the previous step
> 3. Convert the signature to upper case

以下に意訳も載せておきます。

> ##### 署名の方法について
> 署名の生成については以下の手順に従ってください。
> 1. 13桁のタイムスタンプを取得し、それをトークンと連結します。
> 2. 1.で作成した文字列を使い、秘密鍵で署名を生成します。
> 3. 署名を大文字に変換します。

この署名ルールを踏まえてAPIに対してデバイスIDのリクエストを行ったのが以下のコードです。

なお、署名には「HMAC-SHA256」という方式を用いています。
HMAC-SHA256は、メッセージの完全性を保証するための暗号技術で、この署名をAPIリクエストに添付することで、第三者が改ざんしていないことを証明しています。

```js: GAS｜デバイスIDを取得するスクリプト
// STEP1: 認証情報設定
// token,seacretは先に設定したスクリプトプロパティから呼び出し
const token = PropertiesService.getScriptProperties().getProperty("SWITCHBOT_API_TOKEN");
const secret = PropertiesService.getScriptProperties().getProperty("SWITCHBOT_API_SECRET");
const baseURL = "https://api.switch-bot.com/v1.1";

// STEP2: 認証パラメータを取得。SwitchBot APIの認証に必要なパラメータを生成
function getAuthParams() {
    // タイムスタンプ: APIがリクエスト時間をチェックするために必要
    const t = Date.now().toString(); 
    // UUID(Universally Unique Identifier): リクエストの再利用防止のための一度限りの値
    const nonce = Utilities.getUuid();
    // 署名生成用の文字列。ここでは、トークン・タイムスタンプ・nonceを結合
    const data = token + t + nonce;
    // dataとsecretから生成したHMAC-SHA256方式の署名
    // Utilities.computeHmacSha256Signature関数でHMAC署名を生成し、それをBase64エンコードして大文字に変換する。
    // この署名をAPIリクエストに含めることで、リクエストが正規のものであることを証明する
    const sign = Utilities.base64Encode(Utilities.computeHmacSha256Signature(data, secret)).toUpperCase();

    return { t, nonce, sign }
}

// STEP3: SwitchBotに登録されているデバイスの一覧を表示する。
function getDeviceInfo() {
    // SwitchBot APIの認証に必要なパラメータを取得
    const { t, nonce, sign } = getAuthParams();
    // リクエストヘッダーの設定
    const headers = {
        "Authorization": token,
        "sign": sign,
        "t": t,
        "nonce": nonce,
    };
    // HTTPメソッドを含めたリクエストが完成
    const options = {
        "method": "GET",
        "headers": headers,
        "muteHttpExceptions": true,
    };
    // リクエストを送信し、そのレスポンスをログに表示する
    const response = UrlFetchApp.fetch(baseURL + "/devices", options);
    Logger.log(response.getContentText());
}
```
Apps Scriptの画面から関数`getDeviceInfo`を選択し実行すると、以下の図のような実行ログが表示されます。
ここに表示されている`"deviceId":"xxxxxxxxxxxx"`がデバイスIDとなります。
![スクリーンショット_2024-09-02_22_16_33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/c9c11bd9-d17d-ecef-0fb3-ed8343004891.png)

こちらも電流量の取得に使用しますので、先ほどのトークンやクライアントシークレットと同様、スクリプト プロパティに登録しておきます。(今回は「SWITCHBOT_API DEVICEID」という名前で登録しました。)
![スクリーンショット_2024-09-02_22_18_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/83d1b845-d282-0410-edac-4c53e962e731.png)

### 手順4: プラグから電流量を取得しスプレッドシートに記録
ここまででようやくプラグから電流量を取得できる準備が整いました。
今回は以下の要件でスプレッドシートに電流量を記録していくこととします。

```txt:要件
1. 1分毎にAPI経由で電流量を取得
2. スプレッドシートの2行目(テーブルの1行目)に行を挿入し、取得した電流量等の情報を記録
3. 2.により過去の情報が1行ずつ下にズレる。101行目は削除
```

これを実装したのが以下のコードになります。
なお、STEP1とSTEP2は上述のデバイスID取得時にも使用した関数なので、実際に書いたものでは共通化しています。
また、リクエストの作り方もデバイスID取得時と同じで、異なるのはそのリクエストを`UrlFetchApp.fetch`メソッドで送信する先のURLのみです。

```js: GAS｜プラグから電流量を取得しスプレッドシートに記録するスクリプト
// STEP1: 認証情報設定
const token = PropertiesService.getScriptProperties().getProperty("SWITCHBOT_API_TOKEN");
const secret = PropertiesService.getScriptProperties().getProperty("SWITCHBOT_API_SECRET");
const baseURL = "https://api.switch-bot.com/v1.1";

// STEP2: 認証パラメータを取得。SwitchBot APIの認証に必要なパラメータを生成
function getAuthParams() {
    const t = Date.now().toString();
    const nonce = Utilities.getUuid();
    const data = token + t + nonce;
    const sign = Utilities.base64Encode(Utilities.computeHmacSha256Signature(data, secret)).toUpperCase();

    return { t, nonce, sign }
}

// STEP3: ミニプラグのステータスを取得
function getDeviceStatus(deviceId) {
    const { t, nonce, sign } = getAuthParams();

    const headers = {
        "Authorization": token,
        "sign": sign,
        "nonce": nonce,
        "t": t,
    }
    // HTTPメソッドを含めたリクエストを作成
    const options = {
        "method": "GET",
        "headers": headers,
        "muteHttpExceptions": true,
    };

    const response = UrlFetchApp.fetch(baseURL + "/devices/" + deviceId + "/status", options);
    return JSON.parse(response.getContentText());
}

// STEP4: スプレッドシートにデータを記録
function appendPowerConsumptionDataToSheet() {
    // デバイスIDをスクリプトプロパティから呼び出し、それを基にプラグののステータスを取得
    const deviceId = PropertiesService.getScriptProperties().getProperty("SWITCHBOT_API DEVICEID");
    const data = getDeviceStatus(deviceId);
    // スプレッドシートに記録するデータを定数に格納
    const d = new Date();
    const power = data["body"]["power"];
    const current = data["body"]["electricCurrent"];
    const voltage = data["body"]["voltage"];
    
    const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    const sheet = spreadsheet.getActiveSheet();
    
    // 2行目にデータを追加
    sheet.insertRowBefore(2);
    sheet.getRange("A2").setValue(d);
    sheet.getRange("B2").setValue(power);
    sheet.getRange("C2").setValue(current);
    sheet.getRange("D2").setValue(voltage);
    
    // 101行目以上を削除
    const lastRow = sheet.getLastRow();
    if (lastRow > 100) {
        sheet.deleteRows(101, lastRow - 100);
    }
  
// ↓↓↓↓↓ このあとこの続きにLINE通知に関するコードを追記します

}
```
APIへのリクエストに対し返却される情報については以下のドキュメントで詳しく説明されています。
他のデータ項目も取得したい場合はこちらを参考にしてください。

https://github.com/OpenWonderLabs/SwitchBotAPI?tab=readme-ov-file#plug-mini-jp-1

さて、上記で作成した関数`appendPowerConsumptionDataToSheet`を実行すると、スプレッドシートに以下のようにデータが登録されます。
![スクリーンショット 2024-09-03 18.17.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/3c937c59-c8de-9557-1d8a-b286e2e6e4d0.png)
アプリがA(アンペア)で表示されていたのに対し、APIではmA(ミリアンペア)で表示されるため少し混乱するのですが、無事値が取得できていることが確認できます。


ただ、これを毎回手動で実行するわけにはいかないので、以下のように定期的に自動実行するよう設定します。
1. Apps Scriptの画面左側の「目覚まし時計」のアイコンにカーソルを当てると、メニュー画面が伸び「トリガー」と表示されるので、それを選択
2. トリガーの画面が表示されるので、左下の「トリガーを追加」を選択
![スクリーンショット_2024-09-03_18_20_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/d60886b3-64df-4a9e-234a-ec30a544cf97.png)
3. トリガーの設定画面が表示されるので、以下のとおり設定し「保存」を選択すれば完了

|設定項目|設定値|
| ---- | ---- |
|実行する関数を選択|appendPowerConsumptionDataToSheet|
|イベントのソースを選択|時間選択型|
|時間ベースのトリガーのタイプを選択|分ベースのタイマー|
|時間の間隔を選択（分）|1分おき|

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/4ed30318-587d-6a5d-0967-67dc688daece.png)


なお、今回は1分おきなので問題ないですが、1日あたりのAPI呼び出し回数は10,000回に制限されています。
この制限を超えると「Unauthorized」が返されるようです。
継続的・連続的に使用する場合はご留意ください。

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

このようにして1分おきに記録していくと以下の図のようになります。
![スクリーンショット_2024-08-20_22_43_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/7fa1d235-6731-b9dc-4381-7a713c60bc60.png)

これを眺めていると、テレビの電源をON/OFFによる電流量は一定ではないものの、おおよそ4mAあたりを境目にするとON/OFFを判断できそうであることがわかりました。

4mA前後でON/OFFを判定する関数をこのスプレッドシートに追加してもよいのですが、それだと最低でも100分毎にスプレッドシートを覗きにいかないといけない(※100件以上は消去する＝100分でデータが消える)ので、今回はON/OFFが切替わった際にLINEに通知するようにします。

### 手順5: 取得したデータを比較してLINEに通知する
LINEの通知を実装するには色々と事前準備が必要です。
手順は以下記事の「1. 事前準備」にまとめています。

https://qiita.com/S_Kosaka/items/7f9720f6058c4e9bcdc8

なお、今回は各設定項目・値は以下のように設定しました。
LINE Notifyから発行したトークンについても、スクリプトプロパティに追加しておいてください。

|LINE Notifyでの設定項目|設定値|
| ---- | ---- |
|トークン名|tvonoffNotify|
|通知を送信するトークルーム名|テレビON/OFF|

|GASでの設定項目|設定値|
| ---- | ---- |
|スクリプトプロパティのプロパティ名|LINENOTIFY_TOKEN|

ここまで設定できたら、先ほど作成したプラグから電流量を取得しスプレッドシートに記録するスクリプトの`appendPowerConsumptionDataToSheet`関数(STEP4: スプレッドシートにデータを記録)の続きに以下のコードを追記します。
```js: GAS｜取得したデータを比較してLINEに通知するスクリプト

// ↑↑↑↑↑ 上記「SwichBotから電流量を取得しスプレッドシートに記録するスクリプト」の
// "appendPowerConsumptionDataToSheet"関数 (STEP4: スプレッドシートにデータを記録)の続き


    // 起動状況を確認しoffの場合はLINE通知文へアラート文を格納
    const b2value = sheet.getRange("B2").getValue();
    const b3value = sheet.getRange("B3").getValue();
    
    if (b2value == "off" && b3value == "on") {
        lineNotify("SwitchBotが起動していません。");
    }
    else if (b2value == "on" && b3value == "off"){
        lineNotify("SwitchBotが起動しました。");
    }
    
    // 直前の電流と比較してON/OFFの状況を確認しLINE通知文へ格納
    const c2Value = sheet.getRange("C2").getValue();
    const c3Value = sheet.getRange("C3").getValue();
    
    if (c2Value >= 4 && c3Value < 4) {
        lineNotify("電源ON");
    }
    else if (c2Value < 4 && c3Value >= 4) {
        lineNotify("電源OFF");
    }
}

// STEP5: LINE通知
function lineNotify(message) {
    const lineToken = PropertiesService.getScriptProperties().getProperty("LINENOTIFY_TOKEN");
    const options = {
        "method": "post",
        "payload": {"message": message},
        "headers": {"Authorization": "Bearer " + lineToken}
    };

    UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}
```
これで1分おきにプラグの電流量を確認・記録し、直前の電流量が閾値(4mA)を跨ぐとLINEに通知を送ります。(ちなみにプラグの起動状況がOFFだった場合も通知します)
![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123999/86024e17-1e09-717c-7b47-4c9f50b973c0.png)

# おわりに
今回はテレビの電源ON/OFFを例にしましたが、電源ON/OFFで電流量が変化する機器であれば何にでも応用することができますので、例えば高齢の親が住む実家の電気ケトルの使用頻度をカウントして安否を確認したり、電気ストーブのON/OFFを監視して消し忘れを防止したり(万が一の場合はSwitchBotアプリで元電源を切ることも)などなど様々な活用ができそうです。

サーバレスで手軽に実装できますので、実験的に色々な電流量を計測してみるのもいいかもしれません。

ぜひお試しください。

# 参考

https://entropiajp.hatenablog.com/entry/2022/09/25/000820