仕事部屋の電灯を操作するキーボードショートカット = Switchbot Bot を Mac の Shortcutsで操る 〜Shortcuts 小ねた 2/2〜

# 3行まとめ
* Switchbot API を使って Switchbot Bot を操る Mac の "ショートカット" を作った
* Mac の Shortcuts には WebAPI リクエストを発行するのに必要な機能を備えたアクションが用意されてる
* Mac の Shortcuts ではアクション間のデータのやり取りに JSON (やリスト)が使えて便利



# きっかけ
在宅勤務が日常と化したころから、仕事部屋の環境をこまごまいじってきました。そんな改善ねたの１つに天井のシーリングライト用の壁面スイッチに、[Switchbot Bot](https://www.switchbot.jp/products/switchbot-bot)を取り付けた、というのがあります。これは夕方の時間帯のミーティング中などで席を外したくないけど部屋の明かりをつけたいと思うことが度々あったので、取り付けました。
Switchbot Bot はスマホのアプリから操作することができます。なのでアプリをインストールしたスマホが手元にあれば、席をたたずとも部屋の明かりをつけることができるようになりました。けれど、スマホを開いてアプリ立ち上げてアイコンクリックするのって、やっぱちょっと手間なんですよね。特にミーティング中に活発に議論してるときなどは、わずらわしかったりします。
Mac の中から Switchbot Bot を操作できれば、手間が少し減ります。そしてそれがキーボードショートカット一発で済めば手間がすごく減ります。ということで調べてみたところわりあい簡単に実現できました。



# How
キーボードショートカットを Mac に仕込むのは、[昨日の記事](https://qiita.com/ykktsmklb/private/5117c30bfbcf2041468a)でも使用した [Shortcuts](https://support.apple.com/ja-jp/guide/shortcuts-mac/welcome/mac) を使います。
Switchbot Bot をスマホアプリを使わずに操作するのは、[Switchbot が用意してくれている Web API](https://github.com/OpenWonderLabs/SwitchBotAPI) を使えばできそうです。親切にもいくつかの言語のサンプルも用意されています(さすがに AppleScript のサンプルはありませんでした)。この中のどれかの言語のスクリプトを Shortcuts のアクションとして実行できれば、ほとんどコピペで済ませられそうです。
最近の Mac では AppleScript の他に Javascript も標準でサポートしています。なので Sortcuts の中でも Javascript が使えるのですが、それ以外にもシェルスクリプトや Python、Perl も使えます。今回は手慣れていてサンプルコードもある Python3 を使うことにしました。



# 事前準備
Switchbot Bot は同じ部屋の中からスマホで操作する分には Bluetooth で操作できるので、Switchbot の Hub２ や Hub Mini は不要だったりします。しかしスマートスピーカーなどから操作するためにはインターネットから部屋の中の Switchbot Bot にリーチするために中継してくれる Hub２ か Hub Mini が必要になります。
Switchbot API を使用して Switchbot Bot を操作する上でも、Hub２/ Hub Mini による中継が必要になります。ですので、事前に Hub２/ Hub Mini と Switchbot Bot を接続しておく必要があります。「[ボットをハブミニ/ハブ2に接続する方法](https://support.switch-bot.com/hc/ja/articles/16299286523927-ボットをハブミニ-ハブ2に接続する方法)」に従って準備しておいてください。



# To
Switchbot API を通じて Switchbot デバイスを操作するためには

1. API リクエストを発行する際の認証データ
2. 操作する対象デバイスのデバイスID

の２つが必要になります。１つ目の認証データに関しては次の Step0 で取得します。２つ目の操作するデバイスのデバイスIDは、Switchbot API を使ってデバイスの一覧を取得しないとわからないので、"ショートカット"を作成する過程で動作テストがてら取得します。



## Step0: Switchbot の Web API リクエストに必要なトークンとクライアントシークレットを取得する
Switchbot の Web API にリクエストを送信する上で必要になるトークンとクライアントシークレットを取得します。[こちらのページ](https://blog.switchbot.jp/announcement/api-v1-1/)にやり方が掲載されていますので取得し、控えておきます。



## Step1: ショートカットを作成して認証データを生成するスクリプトを追加する
今回の “ショートカット” は、[昨日の記事](https://qiita.com/ykktsmklb/private/5117c30bfbcf2041468a)で作成したものより少し複雑で、コアの処理を２段階に分けています。１段階目は Web API にリクエストを送信する際に必要な認証データを Python で生成します。そして生成した認証データを使って２段階目で Web API にリクエストを送信します。1段階目で生成した認証データは JSON 形式にして2段階目のアクションに渡します。


ということで早速作成していきます。まずは新しい “ショートカット” を作成し、「シェルスクリプト」のアクションを追加します。「シェル」の部分は「Python3(/usr/bin/python3)」に変更します。

![image26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/0ff880a8-bd32-b790-f7f4-484617378547.png)

:::note
ちなみに「シェル」の部分で選択できるインタプリタの候補には /usr/bin/python3 だけではなく HomeBrew でインストールした /usr/local/bin/python3 もリストされていました。ちょっとびっくりです。
:::
  
コードの部分には以下のコードを入力してください。ほとんど [Switchbot API の README.md のサンプルコード](https://github.com/OpenWonderLabs/SwitchBotAPI#python-3-example-code)のままですが、 `print()` 文で出力する認証データを JSON 形式に変更してます。
**９行目の `token` と 11行目の `secret` 変数に入れる値は、Step0 で取得したものに書き換えてください。**

```python
import json
import time
import hashlib
import hmac
import base64
import uuid


# open token
token = 'XXXXXXXXXXXXXXXXXXX'
# secret key 
secret = WWWWWWWWWWWWWWWW
nonce = uuid.uuid4()
t = int(round(time.time() * 1000))
string_to_sign = '{}{}{}'.format(token, t, nonce)


string_to_sign = bytes(string_to_sign, 'utf-8')
secret = bytes(secret, 'utf-8')
sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest())


print(json.dumps({ 
    'Authorization': token,
    't': str(t),
    'sign': str(sign, 'utf-8'),
    'nonce': str(nonce),
}))
```

一旦ここまでで動作テストをします。結果として↓のように JSON が出力されていれば成功です。

![image38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/b4444f2e-d948-a7cc-ce9b-a000085b6bbb.png)



## Step2: Web API リクエストを発行するアクションを追加する
Step1 で生成した認証データを使って Switchbot API へリクエストを発行するアクションを追加します。
「web」カテゴリの中から「URLの内容を取得」アクションを追加してください。

![image19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/138056e8-d5c1-019e-e15a-b540f754879c.png)

リクエストする Web API のアドレスの設定は一旦おいておいて、先にヘッダの設定をしてしまいます。
「表示を増やす」をクリックして表示される「ヘッダ」をクリックします。

 ![image30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/fdf3f166-ecaa-a817-0b81-348401662566.png)

Switchbot API にリクエストを送信する上で必要なヘッダを以下のように設定していきます。

| キー | 値 |
|:-:|:-:|
| `Content-Type` | `application/json; charset=utf8` で固定 |
| `Authorization` | Step1 の python スクリプトの実行結果の JSON の `Authorization` の値 |
| `t` | Step1 の python スクリプトの実行結果の JSON の `t` の値 |
| `sign` | Step1 の python スクリプトの実行結果の JSON の `sign` の値 |
| `nonce` | Step1 の python スクリプトの実行結果の JSON の `nonce` の値 |

左下の `+` アイコンをクリックするとヘッダが追加されるので、それぞれキーと値を設定していきます。 python スクリプトの実行結果をヘッダの値に設定するには、値欄でマウスを右クリックして「変数を挿入」→「シェルスクリプトの結果」を選択します。

![image3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/d0a1a1ea-9f12-1258-3b39-609d73e8a6d8.png)

表示されたメニューの中にある「種類」から「辞書」を選択します。

![image14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/5db087c5-7fba-c9c9-4c27-d6d10ae34257.png)

「入手」は「辞書」を選択し、「キーの値を取得」にはヘッダのキーと同じ文字列を指定します。つまり例えば「Authorization」ヘッダの値には、「シェルスクリプトの結果」の JSON の中の「Authorization」項の値を使用します。
  
![image36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/9b4f470e-e476-fa96-74f6-b8c10a80d2a0.png)


ちょっとめんどくさいですが、すべてのヘッダの設定を済ませます。最終的には以下のようになっていればOKです。
  
![image33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/5c54e9d1-4afa-fb8a-7e4f-4aa4922af7f3.png)



## Step3: Switchbot Bot のデバイスID を取得する
さて Switchbot Bot を操作するために API リクエストする上では、「どの」 デバイスを操作したいのかを指定する必要があります。Switchbot API ではデバイスIDを使って指定します。なので操作対象の Switchbot Bot のデバイスIDを取得する必要があります。デバイスIDを取得するためには[自分のデバイスの一覧を取得する API](https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-list) リクエストをする必要があります。ということで、動作確認がてらデバイスの一覧を取得します。

Step2 で追加した「URLの内容を取得」アクションの上部にある「シェルスクリプトの結果の内容を取得」の「シェルスクリプトの結果」の部分を右クリックして表示されるメニューの中から「消去」を選択します。

![image22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/7bb94a9c-1a16-f06e-39ae-dbe1fb4d694c.png)

すると「シェルスクリプトの結果」の部分が「URL」に変わりますので
  
![image32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/1c6c664f-f675-1985-b77f-3b10353f22cc.png)

そこをクリックして

    https://api.switch-bot.com/v1.1/devices

と入れます。この状態で "ショートカット"を実行してください。すると以下のような警告メッセージが表示されますので、「常に許可」を選択します。
  
![image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/46dc0827-a204-5a52-4de6-93efc1b7667b.png)

問題なくリクエストが受け付けられれば、以下のように [Web API の実行結果](https://github.com/OpenWonderLabs/SwitchBotAPI#bot)が返ってきます。
  
![image21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/78836ef1-e9f8-8269-9b2a-95ed4fff6d09.png)

これは自分のアカウントに紐づいているデバイスの一覧が JSON 形式でリストされたものです。ちょっと見にくいですが、この中から今回操作したい Switchbot Bot の deviceId を見つけてください。そしてその デバイスID をメモしておきます。



## Step4: ショートカットを完成させる
Switchbot Bot を操作するための Web API の URL は以下になります。

    https://api.switch-bot.com/v1.1/devices/{deviceId}/commands

`{deviceId}` の部分は Step3 でメモした Switchbot Bot のデバイスIDに置き換えます。

「URLの内容を取得」アクションに先程設定した `https://api.switch-bot.com/v1.1/devices` の URLを上記の URL に置き換えます。さらに「方法」を「POST」に変更してください。
  
![image15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/df482d48-718c-8181-8c4b-f63cb1b3b46e.png)

すると、「本文を要求」という項目が増えて、その下に request body を入力する欄が表示されます。（「JSON」の部分はそのままにしておきます。）

![image18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/052c65be-d04b-7766-eabe-5caa52d7271c.png)

[Switchbot API のマニュアルの例](https://github.com/OpenWonderLabs/SwitchBotAPI#bot-example)に従って以下のようにキーと種類と値を設定します。
  
![image29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/0b73c2cf-64ae-a907-6090-e88a43cab4f8.png)

:::note
ここでは command の「値」を press にしてますが、壁面スイッチの種類によっては turnOn に変更してください。
:::


この状態で "ショートカット"を実行すると、問題なければ Switchbot Bot が動作するはずです。


動作が確認できたら、Switchbot API のリクエスト結果をデスクトップ通知に表示する部分を追加します。


([昨日の記事](https://qiita.com/ykktsmklb/private/5117c30bfbcf2041468a#step2-%E5%AE%9F%E8%A1%8C%E7%B5%90%E6%9E%9C%E3%81%AE%E9%80%9A%E7%9F%A5%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)を参考にして)「通知を表示」アクションを追加し、「こんにちわ」を右クリックして表示されるメニューの中から「URLの内容」を選択します。
  
![image10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/43e03c10-4e7d-f333-ab88-c3897a3eb7a7.png)

「URLの内容」に変わった箇所を、今度は左クリックして表示されるメニューの中の「種類」から「辞書」を選択します。
  
![image5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/003ea7a2-59f7-2877-bf18-a04e822247c9.png)

「入手」は「辞書」のままにしておき、下部の「キーの値を取得」の欄に message と入力します。
 
![image6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/beebe7e7-1fe1-d658-d018-e879119a51fb.png)

ショートカットを実行すれば、Switchbot API へのリクエスト結果がデスクトップ通知に表示されます。
  
![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/89116c80-b898-05f8-f4c9-113d415ee9f2.png)

最後に、[昨日の記事](https://qiita.com/ykktsmklb/private/5117c30bfbcf2041468a#step3-%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89%E3%82%B7%E3%83%A7%E3%83%BC%E3%83%88%E3%82%AB%E3%83%83%E3%83%88%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)を参考にしてキーボードショートカットなどを追加すれば、Switchbot Bot を操作するキーボードショートカットは完成です。


# まとめ
今回は外部の Web API と連携する "ショートカット" を作成しました。今回の例のように「URLの内容を取得」アクションでは、POSTメソッドが使えたり、送信データに JSON が使えるので、REST 的 Web API との連携が実現できます。またスクリプトアクションを使うことで、標準のアクションだけでは難しいような複雑な計算もでき、連携させたい外部のサービスの仕様にも簡単に合致させられます。
アクション間のデータのやり取りにも JSON を活用することで、複数のデータを効率的にやり取りできました。Shortcuts では JSON だけではなくリストも扱えるので、複雑な処理を実現する下地が揃っています。アイデア次第で色々なことができそうです。

# こぼれ話
今回のネタでは扱いませんでしたが、Shortcuts では辞書(JSON)だけでなくリストも扱えます。リスト化されたデータに対して、いわゆる `foreach` 的な動作をする「各項目を繰り返す」アクションが用意されているので、"ショートカット" の中でデータ処理する上ではリスト化しておくと便利です。
"ショートカット"に追加したスクリプトから処理結果をリストとして次のアクションに渡すことを考えた場合、どういう形に整形して渡せばいいのか分からなかったので少々実験してみたところ、JSON のリストを辞書の値としたものを渡せば良さそうでした。つまり、以下のような JSON です。

```JSON
{
  "a": [
    "hoge",
    "fuga",
    "foo",
    "bar"
  ]
}
```

この `a` 項目の値を「リストから項目を取得」アクションを使って Shortcuts のリストに変換します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/485a3c75-d089-5dbe-7fad-9cf26d2e106a.png)

JSON のリストを辞書の値にして返すのは無駄のように見えるんですが、辞書の値にせずに生の JSON リストをスクリプトから返すと↓のようにエラーになってしまうので、苦肉の策です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/1541562a-9423-7d77-5d80-71018c1c2a37.png)

ちなみに、↑の例では「結果を表示」アクションを使ってシェルスクリプトの実行結果を受け取っていますが、受け取るアクションを何も置かずに、生の JSON リストを返すスクリプトのアクションのみの状態にして "ショートカット" を実行すると↓のようになりました[^1]。
[^1]: Intel Mac / Mac OS 13.6.1（22G313）/ Shortcuts バージョン6.0（1505.3.1.1）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622121/4bb06237-2c20-bef9-54ca-c6d046eba94b.png)