☔️

【SwitchBot】雨が降りそうなときに廊下ライトの色を変えてお知らせする機能を作る

2023/01/07に公開約9,500字

やったこと

「午後から雨予報なのに、天気予報を見逃していて、家を出る時に傘を持って行くのを忘れてしまった!」

そんな経験はありませんか?(私はよくあります。朝に晴れている時によくやります 😇)というわけで今回は、雨の予報を絶対に見逃さないように、午後の降水確率に応じて廊下のライトの色を変える機能を作ってみました。さすがにライトの色が違えば気づくはず!

スマートライトは SwichBot のライトを利用します。API経由で色を制御可能で、それでいて他社製のスマートライトより値段が安いのでオススメです。

完成イメージ

以下のイメージのように、降水確率が高くなるにつれてライトが青くなっていきます。これを毎朝7時に定期実行し、その時点での午後の降水確率を取得してライトを点灯させます。

app image
スマートライトの点灯イメージ

実装の流れ

以下のような流れで実装します。スマートライトの初期登録は完了済みとします。

  1. SwitchBotスマートライトの初期登録を行う(説明省略)
  2. 天気予報APIを利用して降水確率を取得する
  3. SwitchBot APIをコールするPyhtonスクリプトを実装する
  4. 降水確率に応じてスマートライトの色を変更する処理を実装する
  5. 作成したスクリプトをGitHub Actionsで定期的に実行する

ソースコードは以下のGitHubリポジトリからも確認できます。
https://github.com/tanny-pm/switchbot-rainylight


降水確率を取得する

はじめに、指定した地点の降水確率を取得する関数を実装します。今回は天気予報 API(livedoor 天気互換)を利用して降水確率を取得しました。認証が不要なため、非常に使いやすいです。

APIをコールする処理は以下の記事を参考にさせていただきました。
https://creepfablic.site/2021/11/14/python-weather-api/#index_id1

実装した関数は以下のようになります。CITY_CODEこちらの一覧表から自宅のコードを取得し、環境変数に設定しています。
chanceOfRainフィールドから6時間ごとの降水確率が取得できるため、今回は12時~24時の降水確率のうち、大きい値を返すようにしました。

rainylight.py
DEVICE_ID = os.environ["SWITCHBOT_DEVICE_ID"]
WEATHER_URL = "https://weather.tsukumijima.net/api/forecast/city"

# 中略

def get_pm_rainy_percent(city_code: str = CITY_CODE) -> 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)
    else:
        weather_json = r.json()
        logger.info(weather_json["forecasts"][0]["chanceOfRain"])  # 0:今日 1:明日 2:明後日

        rain = weather_json["forecasts"][0]["chanceOfRain"]
        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をあとで利用します。
https://github.com/OpenWonderLabs/SwitchBotAPI

認証キーの取得と生成

まず、SwitchBotのスマホアプリからAPIトークンとシークレットを取得します。以下のような手順で取得できます。

  1. 「プロフィール」タブ >「設定」メニューを開く
  2. 「アプリバージョン」欄を10回タップすると「開発者向けオプション」が表示される
  3. 「開発者向けオプション」を開いてトークンとシークレットを取得する

このトークンとシークレットから認証情報を生成します。公式ドキュメントにPython3での生成方法が記載されています。再利用しやすいように、以下のような関数として実装しておきました。

get_devide_list.py
def generate_sign(token: str, secret: str, nonce: str = "") -> tuple[str, str, str]:
    """SWITCH BOT APIの認証キーを生成する"""

    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()
    )

    return (str(t), str(sign, "utf-8"), nonce)

デバイスリストの取得

つぎに、デバイスリストを取得する関数を実装します。以下のAPIをコールすると、自分のアカウントに登録しているデバイスの一覧を取得できます。

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

デバイス一覧の取得は最初に1回だけ実行すれば良いので、単独のスクリプトとして実装しました。

get_devide_list.py
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のデバイスリストを取得する"""

    t, sign, nonce = generate_sign(ACCESS_TOKEN, SECRET)
    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にはcommandTypeCommandcommand parameterの3つのパラメーターを設定します。(API仕様の詳細はこちら

汎用的に使いまわすために、上記3つのパラメーターを引数に持つ関数を実装しました。ACCESS_TOKENSECRETは環境変数から取得します。

rainylight.py
def post_command(
    device_id: str,
    command: str,
    parameter: str = "default",
    command_type: str = "command",
):
    """指定したデバイスにコマンドを送信する"""

    t, sign, nonce = generate_sign(ACCESS_TOKEN, SECRET)
    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"
    body = {"command": command, "parameter": parameter, "commandType": command_type}
    data = json.dumps(body)

    try:
        logger.info(data)
        r = requests.post(url, data=data, headers=headers)
        logger.info(r.text)

    except requests.exceptions.RequestException as e:
        logger.error(e)

    return r

ここで、post_command(device_id, "setColor", "255:0:0")のようにパラメーターを設定して関数を実行すると、スマートライトが赤色に変化します。

スマートライトの色の変更

先ほど実装したpost_command()関数を利用して、スマートライトの色の変更を行います。色を変更して点灯するためには、setBrightnesssetColorturnOnの順にコマンドを送信します。

ちなみに、ライトが消灯した状態でturnOnのコマンドだけを送信した場合は、明るさと色は前回の状態で点灯するようです。前回の明るさに依存しないようにライトを点灯させたいので、setBrightnessのコマンドも送信するようにしました。

3つのコマンドを送信するためのturn_on_light()関数を以下のように実装しました。明るさはデフォルトで100を指定しています。

rainylight.py
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%に近づくにつれて青く点灯します。

rainylight.py
def main():
    """降水確率に基づいてカラーライトの色を変更する"""

    rain = get_pm_rainy_percent()
    logger.info(rain)

    if rain == 0:
        turn_on_light(DEVICE_ID, (255, 127, 0))
    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))

    return True

if __name__ == "__main__":
    main()

このモジュールを実行すると、当日午後の降水確率に基づいてスマートライトの色が変化します。(午前中にだけ実行することを想定しています。)

GitHub Actions でモジュールを毎朝実行する

最後に、完成したモジュールを毎朝実行し、朝出かける前に降水確率をチェックできるようにします。

今回はソースコードをGitHub上で管理しているので、GitHub Actionsで定期実行するように設定しました。リポジトリ上にYMLファイルを記載するだけで定期実行できるようになるので、とてもお手軽です。

作成したYMLファイルの全文は以下を参照してください。

cron.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時となります)

cron.yml
on:
  push:
  schedule:
    - cron: "0 22 * * *"

動作確認のために、PUSHした時にも実行されるようにしています。

環境変数の設定とモジュールの実行

次にモジュールの実行処理を記載します。モジュールではSwitchBotのアクセストークンやデバイスIDなどを環境変数で設定していますが、これらの情報はGitHubのシークレットから呼び出して環境変数に設定します。

cron.yml
      - 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

シークレットの利用方法は以下を参考にしました。

https://docs.github.com/ja/actions/security-guides/encrypted-secrets

完成したYMLファイルをGitHubにPUSHすると、毎朝7時にモジュールが実行されるようになります!これで出かけるときに傘を持って行くのを忘れることもありません 🙌

おわりに

SwitchBot APIは公式のドキュメントも充実しているので、思ったより簡単にスマートライトを制御することができました!スマートライトを何かの通知に使うのは結構便利なので、他にも通知したい内容を思いついたら実装してみようと思います。

また今回はスクリプトの定期実行の方法としてGitHub Actionsを選択しました。ソースコードをGihtub上で管理している場合、別のサービスにデプロイすることなく、定期実行できるようになり、非常にお手軽でした。


参考文献

https://zenn.dev/hosaka313/articles/2c58b586927f79

https://dev.classmethod.jp/articles/switchbot-control-by-api/

https://qiita.com/cardene/items/67d31f13d27865a12ecf

GitHubで編集を提案

Discussion

ログインするとコメントできます