ガジェットはお好きですか?
わたしは好きです。
このたび、ガジェット好きがよく持っていると噂のStream Deckを手に入れましたので、ボタンぽちっとすることで、SwitchBotカーテンを設置したカーテンを開閉してみたいと思います。
仕組みは簡単に、以下のとおりです。
Stream DeckのプラグインにはAPIへGETとかPOSTするプラグインが存在するのに、どうして、バッチファイルやPythonプログラムをはさんでいるのか?ですが、これはSwitchBot APIの認証がv1.1になって複雑になったためです。
v1.0までは、SwitchBotアプリで生成するトークンのみでAPIへリクエストができていましたが、どうやらv1.1から、より安全性を高めるために現在時間を認証情報に含む形へ変わったようです。
正しい言い方をわたしは知らないため、各自調べてください。
セキュリティにまつわる複雑な話はさておいて、この変更により、プログラムによる演算を挟む必要が出てきたため、まずはSwitchBot APIを呼ぶPythonプログラムから準備していきます。
Pythonプログラム
デバイスID取得
こちらの「デバイスの一覧を取得する」を使わせていただき、制御したいデバイスのデバイスIDを特定します。
動かしたコード(引用元は上記リンク)
import os
import time
import json
import hashlib
import hmac
import base64
import uuid
import requests
import json
import datetime
from os.path import exists
from dotenv import load_dotenv
load_dotenv()
dir_name = "./responses"
if not exists(dir_name):
os.makedirs(dir_name)
token = os.environ["SWITCHBOT_TOKEN"]
secret = os.environ["SWITCHBOT_SECRET"]
nonce = str(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()
)
apiHeader = {}
apiHeader["Authorization"] = token
apiHeader["Content-Type"] = "application/json"
apiHeader["charset"] = "utf8"
apiHeader["t"] = str(t)
apiHeader["sign"] = str(sign, "utf-8")
apiHeader["nonce"] = nonce
response = requests.get("https://api.switch-bot.com/v1.1/devices", headers=apiHeader)
devices = response.json()
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
response_file = f"{dir_name}/devices_{timestamp}.json"
with open(response_file, "w") as f:
json.dump(devices, f)
print("Success get devices list.")
(トークンや秘密鍵は.env
ファイルを作成し、そちらへ格納してあります)
そして、入手したJSONファイルをVS Codeとかでフォーマットし、
"deviceType": "Curtain3",
を探し、
"deviceId": "CURTAIN3XXX",
を見つけます。(上記のIDそのものはダミーで、本物は英数字の羅列です)
カーテンを右と左でセットにしている場合は、OpenDirection
でカーテンの左右が判別できます。
おそらくどちらか片方にOpen/Closeのコマンドを送ると、連動するようにSwitchBotアプリで設定していれば、両方同時に動きます。
わたしはひとまず、左側のカーテンのデバイスIDに対してコマンドを送ることにします。
これで、デバイスIDは取得できました。
続いて、コマンドを送ります。
カーテン開閉のコマンド送信
引き続き、こちらの「温湿度計のデータを取得する」を参考にしてPythonプログラムを準備します。
カーテン開閉のコマンド送信
import os
import time
import json
import hashlib
import hmac
import base64
import uuid
import requests
import json
import datetime
from os.path import exists
from dotenv import load_dotenv
load_dotenv()
dir_name = "./responses"
if not exists(dir_name):
os.makedirs(dir_name)
device_id = os.environ["SWITCHBOT_CURTAIN3_LEFT"]
print(f"https://api.switch-bot.com/v1.1/devices/{device_id}/commands")
token = os.environ["SWITCHBOT_TOKEN"]
secret = os.environ["SWITCHBOT_SECRET"]
nonce = str(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()
)
apiHeader = {}
apiHeader["Authorization"] = token
apiHeader["Content-Type"] = "application/json"
apiHeader["charset"] = "utf8"
apiHeader["t"] = str(t)
apiHeader["sign"] = str(sign, "utf-8")
apiHeader["nonce"] = nonce
data = {
"commandType": "command",
"command": "turnOn",
# "command": "turnOff",
"parameter": "default",
}
response = requests.post(
f"https://api.switch-bot.com/v1.1/devices/{device_id}/commands",
headers=apiHeader,
json=data,
)
devices = response.json()
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
response_file = f"{dir_name}/status_{device_id}_{timestamp}.json"
with open(response_file, "w") as f:
json.dump(devices, f)
print("End.")
トークンや秘密鍵等セキュリティ情報の準備は、デバイスの一覧を取得したときと同様です。
その後、SwitchBot APIのドキュメントを参考にして、コマンド送信用のデータを準備し、POSTします。
これでPythonプログラム単体を動かすと、すごく長いラグの後、カーテンが動きます。
このラグは、以前はもう少し短かった気がしますが、今この記事を書くための再検証をしていると、けっこう長かったです。
SwitchBot APIが混み合っているか、クラウドとの距離で時間がかかっているのかわかりませんが、長いですね。
バッチファイル
Stream Deckのプログラムを動かすデフォルトは、バッチファイルです。
@echo off
python D:\PythonProgram\CurtainOpen.py
ネットで調べて、こんな感じで書きました。
ひとまずダブルクリックして実行したところ、カーテンが動いたため、Pythonプログラムは正常に実行されています。
Stream Deckの「システム」カテゴリーの「開く」
後は、ここにバッチファイルを設定するだけです。
終わりに
これにより、Stream Deckをぽちっとすることで、カーテンが開閉するようになりました。
応用して(デバイスIDを書き換えれば)赤外線リモコンを制御している電灯のON/OFFもできるようになります。
現時点では、ラグがすごいため、あまり実用性はありませんが、遊んでみるのは楽しいものです。
少なくとも、オンラインゲーム中に部屋が暗くなってきたから、カーテン閉めて電灯をつける、みたいなことが座ったままできるようになったのは、多少感動するのではないでしょうか。
みなさんも、トークンや秘密鍵などのセキュリティ情報の管理にはじゅうぶんに気をつけて、遊んでみてはいかがでしょうか。
202412031700追記
ためしに、家のルーター設定で、ダイナミックDNSになっていたところを、GoogleとCloudflareに向けたスタティックに変えてみたところ、ラグが軽くなりました。