LoginSignup
19
16

[Switchbot API v1.1が登場] APIでSwitchBotロックを遠隔操作する

Last updated at Posted at 2022-09-17

Switchbot APIのv1.1が登場

この記事を書いているのが2022/09/08ですが2日前くらいにSwitchbotのAPIがv1.0 -> v1.1に移行した模様です。

僕的に一番大きな変更点としてSmart Lockを制御できるようになったことです。
今までセキュリティ上の観点から公開されているAPIでは、SwitchBotロックを制御することはできませんでしたが(なぜか一時期使用できていましたが)、v1.1の登場により制御ができるようになりました。

他にも変更点があると思いますが、本記事ではSwitchBotロックに着目し、Pythonを使って制御してみようかと思います。なおSwitchbotのSwitchBotロックを制御したりするためにはSwitchbotのHubが必要になるので注意してください。

Switchbotに関するコードをGitHubに公開しています。本記事に関するコードはsrc/utils.py,src/lock.pyになるので良ければ参考にしてください。

署名の追加

v1.0からv1.1への一番大きな変更は署名が必要になったことです。
この署名の追加でセキュリティ面でのリスクが減ったことにより、SwitchBotロックを制御できるようになったと考えられます。
今までSwitchbotのAPIを使用するためには、専用のAPI用のtokenが必要でした。
このtokenを使うことにより、自分が持っているデバイスの見れたり制御できたりしました。
しかしv1.1のAPIではtokenだけでなく、secretキーが必要になります。
そしてこの2つを使用して作成した署名を使って制御することになります。

署名の作成は公式ドキュメントに載っていますがPython3用の署名作成コードを以下にも記述しておきます。

import time
import hashlib
import hmac
import base64

# open token
token = '' # copy and paste from the SwitchBot app V6.14 or later
# secret key
secret = '' # copy and paste from the SwitchBot app V6.14 or later
nonce = ''
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 ('Authorization: {}'.format(token))
print ('t: {}'.format(t))
print ('sign: {}'.format(str(sign, 'utf-8')))
print ('nonce: {}'.format(nonce))

ここで作成したt,sign,nonceをAPIのheaderとして新たに追加します。
次からはこのコードを絡めたデバイス一覧の取得からSmartLockの制御まで説明していきます。

手順

アプリをアップデートする

Switchbot API v1.1を使用するためには、secretキーが必要になります。
そのためにアプリのバージョンをv6.14以降にする必要があります。
まだ行っていない方はアップデートをしましょう。

tokenとsecretを取得する

次にSwitchbotアプリ内からtokensecretを取得します。
以下の手順に沿って取得しましょう。

1. SwitchBotのアカウント登録し、ログインする
2. アプリ内の下にあるタブからプロフィール > 設定 の順にタップ
3. アプリバージョンを10回タップ > 開発者向けオプションが表示される
4. 開発者向けオプションを選択
5. 上のtoken、下のsecretが表示されるのでコピーする

ここで表示されるtokensecret絶対に公開しないでください
他人が制御できるようになってしまいます。もし公開してしまった場合はアプリ内のボタンからキーをリセットしましょう。

デバイス一覧を取得する

v1.0でもデバイス一覧を取得することができていましたが、今回v1.1になったということで取得の仕方が変更されています。上記にもありますが、署名が必要になります。

以下にtoken,secretを使用して署名作成、HTTPリクエストヘッダーの作成までのコードを書いていきましょう。

import os
import time
import hashlib
import hmac
import base64

def make_sign(token: str,secret: str):
    nonce = ''
    t = int(round(time.time() * 1000))
    string_to_sign = bytes(f'{token}{t}{nonce}', 'utf-8')
    secret = bytes(secret, 'utf-8')
    sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest())
    return sign, str(t), nonce

def make_request_header(token: str,secret: str) -> dict:
    sign,t,nonce = make_sign(token, secret)
    headers={
            "Authorization": token,
            "sign": sign,
            "t": str(t),
            "nonce": nonce
        }
    return headers

署名作成はmake_sign関数、HTTPリクエストヘッダー作成はmake_request_header関数になります。
おそらくリクエストの度に署名を生成する必要があるため、関数化しておいた方がいいと思います。
make_request_header関数に引数としてtokensecretを入れてあげると返り値としてheadersが返ってきます。リクエストを送る際にはこのheadersを使用するだけです。そのまま流用しても問題ないと思います。

次にこのheadersを利用して自分のSwitchbotデバイスの一覧を取得してみましょう。

import requests
import json

base_url = 'https://api.switch-bot.com'

def get_device_list(deviceListJson='deviceList.json'):
    # tokenとsecretを貼り付ける
    token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

    devices_url = base_url + "/v1.1/devices"

    headers = make_request_header(token, secret)

    try:
        # APIでデバイスの取得を試みる
        res = requests.get(devices_url, headers=headers)
        res.raise_for_status()

        print(res.text)
        deviceList = json.loads(res.text)
        # 取得データをjsonファイルに書き込み
        with open(deviceListJson, mode='wt', encoding='utf-8') as f:
            json.dump(deviceList, f, ensure_ascii=False, indent=2)

    except requests.exceptions.RequestException as e:
        print('response error:',e)

if __name__ == "__main__":
    get_device_list()

GETリクエストからデバイス一覧を取得する関数になります。
tokensecretにはアプリから取得した値をそのまま貼り付けましょう。キーをどう管理するかは、各々に任せます。
リクエストが成功していればデバイス一覧が出力されます。失敗している場合、エラーメッセージが表示されると思います。
またjsonファイルにdumpされます。このjsonファイルは関数の引数で指定してあげてください。今回のコードではdeviceList.jsonがデフォルトになっています。
v1.1のAPIを試したところで次にSwitchBotロックを制御してみます。

SwitchBotロックを制御する

それでは本題となるSwitchBotロックの制御になります。ここまで来れたらここからは難しくないと思います。
APIを使用するときは、公式のAPIドキュメントを見ましょう。APIに対してどのようなリクエストが必要か、どのようなレスポンスが返ってくるかがぎっしり書かれています。
公式ドキュメントは英語で書かれていますが、海外産のAPIを使用する機会は多いと思うので、頑張って読み解きましょう。Smart Lockを制御するときはcommandとしてlock,unlockを使用するそうです。

ロックの状態を取得する

def get_lock_status(deviceId: str):
    devices_url = base_url + "/v1.1/devices/" + deviceId + "/status"
    try:
        # ロックの状態を取得
        res = requests.get(devices_url, headers=headers)
        res.raise_for_status()
        return res.json()

    except requests.exceptions.RequestException as e:
        print('response error:',e)

get_lock_status関数の引数にはSmart LockのdeviceIdを指定してあげてください。成功した場合、以下のレスポンスが表示されると思います。

{'statusCode': 100, 'body': {'deviceId': 'xxxxxxxxxx', 'deviceType': 'Smart Lock', 'hubDeviceId': 'xxxxxxxxxx', 'lockState': 'unlocked', 'doorState': 'opened', 'calibrate': True}, 'message': 'success'}

このlockStateがロック/アンロックの状態になります。

ロック/アンロックする

ロック/アンロック用のメソッドを用意しました。
引数には先ほどと同様、deviceIdを指定してあげてください。
postリクエストをする場合はrequestspostメソッドを使用します。
ロック/アンロックする場合は、パラメータとして以下の用に指定してあげる必要があります。
ロックする場合はcommandlockに、アンロックする場合はcommandunlockにしてあげましょう。

data={
        "commandType": "command",
        "command": "lock",
        "parameter": "default",
    }
def lock(deviceId: str):
    devices_url = base_url + "/v1.1/devices/" + deviceId + "/commands"
    data={
            "commandType": "command",
            "command": "lock",
            "parameter": "default",
        }
    try:
        # ロック
        res = requests.post(devices_url, headers=headers, json=data)
        res.raise_for_status()
        print(res.text)

    except requests.exceptions.RequestException as e:
        print('response error:',e)

def unlock(deviceId: str):
    devices_url = base_url + "/v1.1/devices/" + deviceId + "/commands"
    data={
            "commandType": "command",
            "command": "unlock",
            "parameter": "default",
        }
    try:
        # アンロック
        res = requests.post(devices_url, headers=headers, json=data)
        res.raise_for_status()
        print(res.text)
        
    except requests.exceptions.RequestException as e:
        print('response error:',e)

成功すれば以下のようなレスポンスが返ってくると思います。

{"statusCode":100,"body":{},"message":"success"}

最後に

今回APIがアップデートされたということで早速今までAPIで制御できなかったSwitchBotロックの制御を行ってみました。セキュリティが向上したということで今後も色々と追加されていきそうですね。今後もSwitchbotに関するAPIで面白いことができそうだったら、記事をあげていこうと思います。
Switchbotに関するコードをGitHubに公開しています。今回紹介した部分はsrc/utils.pysrc/lock.pyになりますので、よければ参考にしてください。

19
16
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
16