🥚
Python(Flask)からSwitchBotAPIを叩いてみる
1. 環境想定
- macOS Sonoma 14.3.1
- Python 3.12.1
- pipenv version 2023.12.0
- SwitchBotAPI 1.1
2. 事前準備
Python
- 下記のコマンドを実行する。
pip install --upgrade pip
pip install pipenv
pipenv install flask flask-restful requests python-dotenv
SwitchBot
- SwitchBotアプリをインストールする
- アプリを起動してSwitchBotアカウントでログインする
- 「プロフィール > 設定」から、「アプリバージョン」を数回タップする
- 「開発者向けオプション」が表示されればOK
- 「開発者向けオプション」を開き、「トークンを取得」をタップする
- トークン値とクライアントシークレット値が表示される
3. Flask準備
-
ディレクトリ想定
. ├── Pipfile ├── Pipfile.lock ├── api.py ├── config.py ├── main.py └── templates └── index.html
-
FlaskでAPI結果を取得できるように準備する
main.pyfrom flask import Flask, render_template from api import api_bp app = Flask(__name__) app.register_blueprint(api_bp) @app.route("/") def index(): return render_template("index.html") if __name__ == "__main__": app.run(debug=True)
config.pyfrom dotenv import load_dotenv import os load_dotenv() # .envファイルの値を取得 TOKEN = os.getenv("TOKEN") CLIENT_SECRET = os.getenv("CLIENT_SECRET")
.envTOKEN=<TOKEN> CLIENT_SECRET=<CLIENT_SECRET>
api.pyfrom flask import Blueprint from flask_restful import Api, Resource import requests import json import time import hashlib import hmac import base64 import uuid import config # config.pyから環境変数値を取得 token = config.TOKEN client_secret = config.CLIENT_SECRET # Flask Appの分割モジュール api_bp = Blueprint("api", __name__, url_prefix="/api") # SwhichBotAPIの共通設定 secret = bytes(client_secret, "utf-8") nonce = uuid.uuid4() charset = "utf8" domain = "https://api.switch-bot.com/v1.1" class Devices(Resource): # Get device status def get(self): t = int(round(time.time() * 1000)) string_to_sign = f"{token}{t}{nonce}" string_to_sign = bytes(string_to_sign, "utf-8") sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest()) headers = { "Authorization": token, "charset": charset, "t": str(t), "sign": str(sign, "utf-8"), "nonce": str(nonce) } res = requests.get(f"{domain}/devices", headers=headers) return res.json() # Send device control commands class Commands(Resource): def post(self): t = int(round(time.time() * 1000)) string_to_sign = f"{token}{t}{nonce}" string_to_sign = bytes(string_to_sign, "utf-8") sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, digestmod=hashlib.sha256).digest()) headers = { "Content-Type": "application/json", "Authorization": token, "charset": charset, "t": str(t), "sign": str(sign, "utf-8"), "nonce": str(nonce) } # Color BulbをturnOnにする例 body = { "command": "turnOn", "commandType": "command" } res = requests.post(f"{domain}/devices/<deviceID>/commands", headers=headers, json=body) return res.json() api = Api(api_bp) api.add_resource(Devices, "/devices") api.add_resource(Commands, "/commands")
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TESTページ</title> </head> <body> <ul> <li><a href="/api/devices">GET /devices</a></li> <form method="POST" name="form1" action="/api/commands"></form> <li><a href="javascript:form1.submit()">POST /commands</a></li> </ul> </body> </html>
4. 結果
GET /devices
{
"statusCode": 100,
"body": {
"deviceList": [
{
"deviceId": "XXXXXXXXXXXX",
"deviceName": "<device name>",
"deviceType": "Color Bulb",
"enableCloudService": true,
"hubDeviceId": "YYYYYYYYYYYY"
},
…(略)…
],
"infraredRemoteList": [
{
…(略)…
}
]
},
"message": "success"
}
POST /commands
{
"statusCode": 100,
"body": {
"items": [
{
"deviceID": "XXXXXXXXXXXX",
"code": 100,
"status": {
"isDelay": false,
"color": "0:0:0",
"dayLightMin": <Min>,
"dayLightWh": <Wh>,
"isPreset": false,
"weekLightMin": <weekLightMin>,
"colorTemperature": <colorTemperature>,
"presetIdx": -1,
"brightness": 100,
"dynamicSpeed": 100,
"weekLightWh": <weekLightWh>,
"power": "on",
"presetMode": "white",
"signal": 0,
"monthLightWh": <monthLightWh>,
"monthLightMin": <monthLightMin>
},
"message": "success"
}
]
},
"message": "success"
}
Discussion