SwitchBot API 1.1 を使用して、温湿度計のデータを取得する

# はじめに
実際に自分で集めたデータを用いて、機械学習の予測モデル構築をしたい。
そのための、第一歩としてswitchBot APIを用いて自室に設置された温湿度計のデータを取得することにしました。
[switchBot 室温度計](https://www.switchbot.jp/products/switchbot-meter)
また、機械学習をpythonで行うことを考慮し、pythonを使用しswitchBot APIを叩きました。

# 動作環境
- Dockerfile
    ```dockerfile
    FROM python:3.11
    
    COPY requirements.txt .
    
    RUN pip install -r requirements.txt
    
    WORKDIR /app
    
    COPY ./app .
    ```
- requirements.txt
    ```text
    certifi==2023.11.17
    charset-normalizer==3.3.2
    idna==3.4
    requests==2.31.0
    urllib3==2.1.0
    ```
- docker-compose.yml
    ```yml
    version: '3'
    services:
      app:
        container_name: switchbot_api
        build:
          context: .
          dockerfile: Dockerfile
        volumes:
          - ./app:/app
          - ./requirements.txt:/requirements.txt
        env_file:
          - .env
        tty: true
    ```

# SwitchBot API v1.1 について
- 条件
    - SwitchBotアプリでアカウント登録した後、tokenとsecretの発行が必要
    - 1日当たりの呼び出し回数は、1000回まで
    - Http ヘッダーに認証情報の付与が必要
- Http ヘッダー
    - 必要な情報<br>
        |key|説明|
        |:---|:---:|
        |Authorization|SwitchBotアプリにて取得したtoken|
        |t|UNIXエポック時刻(1970年からのミリ秒)|
        |nonce|署名する文字列にブレンドするために開発者自身によって生成されたランダムな UUID。|
        |sign|特定のアルゴリズムを使用してトークンと秘密鍵から生成された署名。|
    - t
        - UNIXエポック時刻(1970年からのミリ秒)
        - >[SwitchBot API v1.1](https://github.com/OpenWonderLabs/SwitchBotAPI)のpythonサンプルプログラム
            >```python
            >t = int(round(time.time() * 1000))
            >```
    - nonce
        - 署名する文字列にブレンドするために開発者自身によって生成されたランダムな UUID。
        - >[SwitchBot API v1.1](https://github.com/OpenWonderLabs/SwitchBotAPI)のpythonサンプルプログラム
            >```python
            >nonce = str(uuid.uuid4())
            >```
    - sign
        - 特定のアルゴリズムを使用してトークンと秘密鍵から生成された署名。
        - 特定のアルゴリズム
            1. token,t,nonceを結合し、一つの文字列を作成
            2. その文字列をバイト列に変換
            3. SwitchBotアプリにて取得したsecretを秘密鍵としてバイト列にHMAC-SHA256署名を行う
            4. 署名をBase64形式の文字列にエンコード
        - pythonプログラム
            ```python
            string_to_sign = "{}{}{}".format(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() )
            ```
# デバイスの一覧を取得する
- url
    ```text
    GET https://api.switch-bot.com/v1.1/devices
     ```

- プログラム
    ```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
    
    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.")
    
    ```
    (https://github.com/taiki-kuraishi/switchBotAPI/blob/main/app/src/getDeviceStatus.py)
    
- Response
    - deviceIdは、XXXXXに変更してあります。
    ```json
    {
        "statusCode": 100,
        "body": {
            "deviceList": [
                {
                    "deviceId": "XXXXXXXXXXXX",
                    "deviceName": "ハブミニ",
                    "deviceType": "Hub Mini",
                    "hubDeviceId": "XXXXXXXXXXXX"
                },
                {
                    "deviceId": "XXXXXXXXXXXX",
                    "deviceName": "温湿度計",
                    "deviceType": "Meter",
                    "enableCloudService": true,
                    "hubDeviceId": "XXXXXXXXXXXX"
                },
                {
                    "deviceId": "XXXXXXXXXXXX",
                    "deviceName": "パソコン",
                    "deviceType": "Bot",
                    "enableCloudService": true,
                    "hubDeviceId": "XXXXXXXXXXXX"
                }
            ],
            "infraredRemoteList": [
                {
                    "deviceId": "XX-XXXXXXXXXXXX-XXXXXXXXXXXX",
                    "deviceName": "エアコン",
                    "remoteType": "Air Conditioner",
                    "hubDeviceId": "XXXXXXXXXXXX"
                },
                {
                    "deviceId": "XX-XXXXXXXXXXXX-XXXXXXXXXXXX",
                    "deviceName": "扇風機",
                    "remoteType": "DIY Fan",
                    "hubDeviceId": "XXXXXXXXXXXX"
                },
                {
                    "deviceId": "XX-XXXXXXXXXXXX-XXXXXXXXXXXX",
                    "deviceName": "ライト",
                    "remoteType": "DIY Light",
                    "hubDeviceId": "XXXXXXXXXXXX"
                }
            ]
        },
        "message": "success"
    }
    ```

# 温湿度計のデータを取得する
- `GET https://api.switch-bot.com/v1.1/devices`で取得したdeviceIdをもとに、温湿度計のデータを取得する
- url
    ```text
    GET https://api.switch-bot.com/v1.1/devices/ "deviceId" /status
    ```
- プログラム
    ```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
    
    dir_name = "../responses"
    device_id = os.environ["SWITCHBOT_METER_ID"]
    
    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(
        f"https://api.switch-bot.com/v1.1/devices/{device_id}/status",
        headers=apiHeader,
    )
    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("Success get device status.")
    
    ```
    (https://github.com/taiki-kuraishi/switchBotAPI/blob/main/app/src/getDevicesList.py)

- Response
    - deviceIdは、XXXXXXXXXXXXに変更してあります。
    ```json
    {
        "statusCode": 100,
        "body": {
            "deviceId": "XXXXXXXXXXXX",
            "deviceType": "Meter",
            "hubDeviceId": "XXXXXXXXXXXX",
            "humidity": 52,
            "temperature": 22.1,
            "version": "V2.6",
            "battery": 64
        },
        "message": "success"
    }
    ```
    - 湿度 : 52%
    - 気温 : 22.1度
# まとめ
switchBot APIを通してcsrf_tokenやJWTとは異なるHTTPヘッダーに認証情報や署名を含めるセキュアな方法を学ぶことができました。<br>

プログラムを書く前に、postmanを使用してswitchBot APIの動作を確認しようと考えていましたが、httpヘッダーに必要な認証情報の付与にはプログラムが必要なため、postmanの使用は断念しました。<br>

https://github.com/taiki-kuraishi/switchBotAPI
# 参考
- [SwitchBot API v1.1](https://github.com/OpenWonderLabs/SwitchBotAPI)