【SwitchBot】雨が降りそうなときに廊下ライトの色を変えてお知らせする機能を作る
やったこと
「午後から雨予報なのに、天気予報を見逃していて、家を出る時に傘を持って行くのを忘れてしまった!」
そんな経験はありませんか?(私はよくあります。朝に晴れている時によくやります 😇)というわけで今回は、雨の予報を絶対に見逃さないように、午後の降水確率に応じて廊下のライトの色を変える機能を作ってみました。さすがにライトの色が違えば気づくはず!
スマートライトは SwichBot のライトを利用します。API経由で色を制御可能で、それでいて他社製のスマートライトより値段が安いのでオススメです。
完成イメージ
以下のイメージのように、降水確率が高くなるにつれてライトが青くなっていきます。これを毎朝7時に定期実行し、その時点での午後の降水確率を取得してライトを点灯させます。
スマートライトの点灯イメージ
実装の流れ
以下のような流れで実装します。スマートライトの初期登録は完了済みとします。
- SwitchBotスマートライトの初期登録を行う(説明省略)
- 天気予報APIを利用して降水確率を取得する
- SwitchBot APIをコールするPyhtonスクリプトを実装する
- 降水確率に応じてスマートライトの色を変更する処理を実装する
- 作成したスクリプトをGitHub Actionsで定期的に実行する
ソースコードは以下のGitHubリポジトリからも確認できます。
降水確率を取得する
はじめに、指定した地点の降水確率を取得する関数を実装します。今回は天気予報 API(livedoor 天気互換)を利用して降水確率を取得しました。認証が不要なため、非常に使いやすいです。
APIをコールする処理は以下の記事を参考にさせていただきました。
実装した関数は以下のようになります。CITY_CODE
はこちらの一覧表から自宅のコードを取得し、環境変数に設定しています。
chanceOfRain
フィールドから6時間ごとの降水確率が取得できるため、今回は12時~24時の降水確率のうち、大きい値を返すようにしました。
DEVICE_ID = os.environ["SWITCHBOT_DEVICE_ID"]
WEATHER_URL = "https://weather.tsukumijima.net/api/forecast/city"
# 中略
def get_pm_rainy_percent(city_code: str) -> int:
"""指定した地点の降水確率を取得する"""
try:
url = f"{WEATHER_URL}/{city_code}"
r = requests.get(url)
# status 2xx以外は例外とする
r.raise_for_status()
except requests.exceptions.RequestException as e:
logger.error(e)
return 0
weather_json = r.json()
# forecasts 0:今日 1:明日 2:明後日
rain = weather_json["forecasts"][0]["chanceOfRain"]
logger.info(f"Chance of rain: {rain}")
# 降水確率の「%」部分を除去する
rain_12 = int(re.sub("\\D", "", rain["T12_18"]) or 0)
rain_18 = int(re.sub("\\D", "", rain["T18_24"]) or 0)
return max(rain_12, rain_18)
ここで取得した降水確率に基づいて、スマートライトの色を変化させます。
SwitchBot のデバイスリストを取得する
SwitchBot APIをコールして、自分のデバイスにコマンドを送信する処理を実装します。
まずは以下の公式ドキュメントを参考にして、デバイスリストの取得を行います。ここで取得したデバイスIDをあとで利用します。
認証キーの取得と生成
まず、SwitchBotのスマホアプリからAPIトークンとシークレットを取得します。以下のような手順で取得できます。
- 「プロフィール」タブ >「設定」メニューを開く
- 「アプリバージョン」欄を10回タップすると「開発者向けオプション」が表示される
- 「開発者向けオプション」を開いてトークンとシークレットを取得する
このトークンとシークレットから認証情報を生成します。公式ドキュメントにPython3での生成方法が記載されています。再利用しやすいように、以下のような関数として実装しておきました。
def generate_sign(token: str, secret: str, nonce: str) -> tuple[str, str]:
"""SWITCH BOT APIの認証キーを生成する"""
t = int(round(time.time() * 1000))
string_to_sign = "{}{}{}".format(token, t, nonce)
string_to_sign_b = bytes(string_to_sign, "utf-8")
secret_b = bytes(secret, "utf-8")
sign = base64.b64encode(
hmac.new(secret_b, msg=string_to_sign_b, digestmod=hashlib.sha256).digest()
)
return (str(t), str(sign, "utf-8"))
デバイスリストの取得
つぎに、デバイスリストを取得する関数を実装します。以下のAPIをコールすると、自分のアカウントに登録しているデバイスの一覧を取得できます。
https://api.switch-bot.com/v1.1/devices
デバイス一覧の取得は最初に1回だけ実行すれば良いので、単独のスクリプトとして実装しました。
API_BASE_URL = "https://api.switch-bot.com"
ACCESS_TOKEN: str = os.environ["SWITCHBOT_ACCESS_TOKEN"]
SECRET: str = os.environ["SWITCHBOT_SECRET"]
# 中略
def get_device_list() -> str:
"""SWITCH BOTのデバイスリストを取得する"""
nonce = "zzz"
t, sign = generate_sign(ACCESS_TOKEN, SECRET, nonce)
headers = {
"Authorization": ACCESS_TOKEN,
"t": t,
"sign": sign,
"nonce": nonce,
}
url = f"{API_BASE_URL}/v1.1/devices"
r = requests.get(url, headers=headers)
return json.dumps(r.json(), indent=2, ensure_ascii=False)
以下のように、JSON形式でデバイスの一覧を取得できます。スマートライトのdeviceId
をメモしておきます。
{
"statusCode": 100,
"body": {
"deviceList": [
{
"deviceId": "xxxxxxxx",
"deviceName": "デバイス名",
"deviceType": "Color Bulb",
"enableCloudService": true,
"hubDeviceId": "000000000000"
},
# 省略
]
},
"message": "success"
}
SwitchBot にコマンドを送信する
デバイスのIDが取得できたので、指定したスマートライトの色を変更する処理を実装します。
デバイス制御コマンドの送信
スマートライトの色を変更する場合、1)明るさを設定する、2)色を設定する、3)ライトをONにする、という3つのコマンドを送信する必要があります。そのため、まずはデバイスにコマンドを送信するための関数を実装しておきます。
以下のAPIをPOSTすることでデバイスにコマンドを送信できます。
https://api.switch-bot.com/v1.1/devices/{device_id}/commands
BodyにはcommandType
、Command
、command parameter
の3つのパラメーターを設定します。(API仕様の詳細はこちら)
汎用的に使いまわすために、上記3つのパラメーターを引数に持つ関数を実装しました。ACCESS_TOKEN
とSECRET
は環境変数から取得します。
def post_command(
device_id: str,
command: str,
parameter: str = "default",
command_type: str = "command",
) -> requests.Response:
"""指定したデバイスにコマンドを送信する"""
nonce = "zzz"
t, sign = generate_sign(ACCESS_TOKEN, SECRET, nonce)
headers = {
"Content-Type": "application/json; charset: utf8",
"Authorization": ACCESS_TOKEN,
"t": t,
"sign": sign,
"nonce": nonce,
}
url = f"{API_BASE_URL}/v1.1/devices/{device_id}/commands"
data = json.dumps(
{"command": command, "parameter": parameter, "commandType": command_type}
)
try:
logger.info(f"Post command: {data}")
r = requests.post(url, data=data, headers=headers)
logger.info(f"Responce: {r.text}")
except requests.exceptions.RequestException as e:
logger.error(e)
return r
ここで、post_command(device_id, "setColor", "255:0:0")
のようにパラメーターを設定して関数を実行すると、スマートライトが赤色に変化します。
スマートライトの色の変更
先ほど実装したpost_command()
関数を利用して、スマートライトの色の変更を行います。色を変更して点灯するためには、setBrightness
、setColor
、turnOn
の順にコマンドを送信します。
ちなみに、ライトが消灯した状態でturnOn
のコマンドだけを送信した場合は、明るさと色は前回の状態で点灯するようです。前回の明るさに依存しないようにライトを点灯させたいので、setBrightness
のコマンドも送信するようにしました。
3つのコマンドを送信するためのturn_on_light()
関数を以下のように実装しました。明るさはデフォルトで100を指定しています。
def turn_on_light(
device_id: str,
color: tuple[int, int, int] = (0, 0, 0),
brightness: int = 100,
):
"""指定したパラメーターでカラーライトをオンにする"""
(r, g, b) = color
post_command(device_id, "setBrightness", str(brightness))
post_command(device_id, "setColor", f"{r}:{g}:{b}")
post_command(device_id, "turnOn")
最後に、降水確率に基づいてスマートライトの色を変更するメイン処理を実装します。
今回は、降水確率20%刻みで色が変化するようにしました。0%の時は黄色く点灯し、100%に近づくにつれて青く点灯します。
def main() -> bool:
"""降水確率に基づいてカラーライトの色を変更する"""
rain = get_pm_rainy_percent(CITY_CODE)
if rain == 0:
turn_on_light(DEVICE_ID, (255, 127, 0)) # Yellow
elif rain <= 20:
turn_on_light(DEVICE_ID, (255, 255, 0))
elif rain <= 40:
turn_on_light(DEVICE_ID, (127, 255, 0))
elif rain <= 60:
turn_on_light(DEVICE_ID, (0, 255, 255))
elif rain <= 80:
turn_on_light(DEVICE_ID, (0, 127, 255))
else:
turn_on_light(DEVICE_ID, (0, 0, 255)) # Blue
return True
if __name__ == "__main__":
main()
このモジュールを実行すると、当日午後の降水確率に基づいてスマートライトの色が変化します。(午前中にだけ実行することを想定しています。)
GitHub Actions でモジュールを毎朝実行する
最後に、完成したモジュールを毎朝実行し、朝出かける前に降水確率をチェックできるようにします。
今回はソースコードをGitHub上で管理しているので、GitHub Actionsで定期実行するように設定しました。リポジトリ上にYMLファイルを記載するだけで定期実行できるようになるので、とてもお手軽です。
作成したYMLファイルの全文は以下を参照してください。
cron.yml全文
name: run_script
on:
push:
schedule:
- cron: "0 22 * * *"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run script
env:
SWITCHBOT_ACCESS_TOKEN: ${{ secrets.SWITCHBOT_ACCESS_TOKEN }}
SWITCHBOT_SECRET: ${{ secrets.SWITCHBOT_SECRET }}
SWITCHBOT_DEVICE_ID: ${{ secrets.SWITCHBOT_DEVICE_ID }}
WEATHER_CITY_CODE: ${{ secrets.WEATHER_CITY_CODE }}
run: |
python -m rainylight
定期実行の設定
.github/workflows
ディレクトリ内にYMLファイルを作成し、定期実行の設定を記載します。以下のように、cronコマンドの設定の要領で実行時間を記載します。今回は毎日7時に実行するように設定しました。(設定はUTC時刻で記載するため22時となります)
on:
push:
schedule:
- cron: "0 22 * * *"
動作確認のために、PUSHした時にも実行されるようにしています。
環境変数の設定とモジュールの実行
次にモジュールの実行処理を記載します。モジュールではSwitchBotのアクセストークンやデバイスIDなどを環境変数で設定していますが、これらの情報はGitHubのシークレットから呼び出して環境変数に設定します。
- name: Run script
env:
SWITCHBOT_ACCESS_TOKEN: ${{ secrets.SWITCHBOT_ACCESS_TOKEN }}
SWITCHBOT_SECRET: ${{ secrets.SWITCHBOT_SECRET }}
SWITCHBOT_DEVICE_ID: ${{ secrets.SWITCHBOT_DEVICE_ID }}
WEATHER_CITY_CODE: ${{ secrets.WEATHER_CITY_CODE }}
run: |
python -m rainylight
シークレットの利用方法は以下を参考にしました。
完成したYMLファイルをGitHubにPUSHすると、毎朝7時にモジュールが実行されるようになります!これで出かけるときに傘を持って行くのを忘れることもありません 🙌
おわりに
SwitchBot APIは公式のドキュメントも充実しているので、思ったより簡単にスマートライトを制御することができました!スマートライトを何かの通知に使うのは結構便利なので、他にも通知したい内容を思いついたら実装してみようと思います。
また今回はスクリプトの定期実行の方法としてGitHub Actionsを選択しました。ソースコードをGihtub上で管理している場合、別のサービスにデプロイすることなく、定期実行できるようになり、非常にお手軽でした。
参考文献
Discussion