17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

開志専門職大学情報学部Advent Calendar 2023

Day 1

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

Last updated at Posted at 2023-11-23

はじめに

実際に自分で集めたデータを用いて、機械学習の予測モデル構築をしたい。
そのための、第一歩としてswitchBot APIを用いて自室に設置された温湿度計のデータを取得することにしました。
switchBot 室温度計
また、機械学習をpythonで行うことを考慮し、pythonを使用しswitchBot APIを叩きました。

動作環境

  • Dockerfile
    FROM python:3.11
    
    COPY requirements.txt .
    
    RUN pip install -r requirements.txt
    
    WORKDIR /app
    
    COPY ./app .
    
  • requirements.txt
    certifi==2023.11.17
    charset-normalizer==3.3.2
    idna==3.4
    requests==2.31.0
    urllib3==2.1.0
    
  • docker-compose.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 ヘッダー
    • 必要な情報
      key 説明
      Authorization SwitchBotアプリにて取得したtoken
      t UNIXエポック時刻(1970年からのミリ秒)
      nonce 署名する文字列にブレンドするために開発者自身によって生成されたランダムな UUID。
      sign 特定のアルゴリズムを使用してトークンと秘密鍵から生成された署名。
    • t
      • UNIXエポック時刻(1970年からのミリ秒)
      • SwitchBot API v1.1のpythonサンプルプログラム

        t = int(round(time.time() * 1000))
        
    • nonce
      • 署名する文字列にブレンドするために開発者自身によって生成されたランダムな UUID。
      • SwitchBot API v1.1のpythonサンプルプログラム

        nonce = str(uuid.uuid4())
        
    • sign
      • 特定のアルゴリズムを使用してトークンと秘密鍵から生成された署名。
      • 特定のアルゴリズム
        1. token,t,nonceを結合し、一つの文字列を作成
        2. その文字列をバイト列に変換
        3. SwitchBotアプリにて取得したsecretを秘密鍵としてバイト列にHMAC-SHA256署名を行う
        4. 署名をBase64形式の文字列にエンコード
      • 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

    GET https://api.switch-bot.com/v1.1/devices
    
  • プログラム

    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に変更してあります。
    {
        "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

    GET https://api.switch-bot.com/v1.1/devices/ "deviceId" /status
    
  • プログラム

    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に変更してあります。
    {
        "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ヘッダーに認証情報や署名を含めるセキュアな方法を学ぶことができました。

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

参考

17
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?