OpenAPIを用いてSwitchbot Hubのデータを取得するAPIを実装する

### はじめに

[OpenAPI](https://www.openapis.org)はAPI実装における標準化された手法の一つです。OpenAPIには以下のメリットがあります。

- API仕様書のフォーマットの統一
- サーバーへのリクエストテスト機能
- ツールを用いたコードの雛形の生成

本記事では、Switchbotのスマート家電からデータを取得するAPIをOpenAPIを用いて実装し、取得したデータを表示する方法を紹介いたします。

最終的な構成図は以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3565355/d5ae2d75-5776-bdb5-04ae-2d27ef678734.png)

#### 使用した環境：

- リモート環境（Ubuntu 20.04）
  - Docker version 24.0.5
    - [openapitools/openapi-generator-cli](https://github.com/OpenAPITools/openapi-generator)
  - Python 3.8.10：
    - streamlit
    - numpy
    - matplotlib

また、データ取得をするスマート家電として、自宅においてある **Switchbot Hub 2** を用いました。

https://www.amazon.co.jp/sspa/click?ie=UTF8&spc=MTo1MjAzODA3MjIxMTcwNjQyOjE2OTY0OTc5MzI6c3BfYXRmOjEyODg0ODA1NTk5ODo6MDo6&url=%2FSwitchBot-%25E3%2582%25B9%25E3%2583%259E%25E3%2583%25BC%25E3%2583%2588%25E3%2583%25AA%25E3%2583%25A2%25E3%2582%25B3%25E3%2583%25B3-%25E8%25B5%25A4%25E5%25A4%2596%25E7%25B7%259A%25E5%25AE%25B6%25E9%259B%25BB%25E3%2582%2592%25E7%25AE%25A1%25E7%2590%2586-%25E3%2582%25B9%25E3%2583%259E%25E3%2583%25BC%25E3%2583%2588%25E3%2583%259B%25E3%2583%25BC%25E3%2583%25A0-Alexa%2Fdp%2FB0BM8VS13P%2Fref%3Dsr_1_1_sspa%3F__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D3O768GL9PA3ON%26keywords%3Dswitchbot%2Bhub%26qid%3D1696497932%26sprefix%3Dswitchbot%2Bhub%252Caps%252C156%26sr%3D8-1-spons%26sp_csd%3Dd2lkZ2V0TmFtZT1zcF9hdGY%26psc%3D1

#### 0. 事前準備

SwitchbotのAPIを利用するためには、アプリの開発者向けオプションをオンにし、トークンを取得する必要があります。手順は以下のページを御覧ください。

https://blog.switchbot.jp/announcement/api-v1-1/#switchbotapi%e3%81%ab%e3%81%a4%e3%81%84%e3%81%a6

:::note warn
**本記事では、実装の簡易化のためにAPIのv1.0を利用します。API v1.1で導入された新しい認証方法には対応していません。**
:::

以下の本文で `YOUR_TOKEN` と記載してある部分は、上記の手順で入手した実際のトークンに置き換えてください。

#### 1. APIの実装

[Switchbot APIの仕様](https://github.com/OpenWonderLabs/SwitchBotAPI/blob/main/README-v1.0.md)を参考に、以下の２つのAPIを定義します。

  - `GET /devices`：デバイスの一覧を取得する
  - `GET /devices/{deviceId}/status`：指定した`deviceId`のデバイスの情報を取得する

また、リクエストヘッダーで、`Authorization`パラメータを定義する必要があります。
以下が実装例です。

```yaml:api.yaml
openapi: '3.0.3'
info:
  title: SwitchBot
  version: "0.0.1"
servers:
  - url: https://api.switch-bot.com/v1.0
security:
  - Authorization: []
paths:
  /devices:
    get:
      operationId: devicesget
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Devices"
  /devices/{deviceId}/status:
    get:
      operationId: devicesidstatusget
      parameters:
        - name: deviceId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusOfDevice"
components:
  schemas:
    Devices:
      type: object
      properties:
        statusCode: 
          type: integer
        message:
          type: string
        body:
          type: object
          properties:
            deviceList:
              type: array
              items: 
                properties:
                  deviceId:
                    type: string
                  deviceName:
                    type: string
                  deviceType:
                    type: string
                  enableCloudService:
                    type: boolean
                  hubDeviceId:
                    type: string
            infraredRemoteList:
              type: array
              items:
                type: object
                properties:
                  deviceId:
                    type: string
                  deviceName:
                    type: string
                  remoteType:
                    type: string
                  hubDeviceId:
                    type: string
    StatusOfDevice:
      type: object
      properties:
        statusCode: 
          type: integer
        message:
          type: string
        body:
          type: object
          properties: 
            deviceId: 
              type: string
            deviceType:
              type: string
            hubDeviceId:
              type: string
            humidity: 
              type: integer
            temperature:
              type: number
  securitySchemes:
    Authorization:
      type: apiKey
      in: header
      name: Authorization
```

上記のコードの確認方法として、[Switchbot Editor](https://editor.swagger.io/)にコードを貼り付けると、レンダリングされた状態で確認できます。

https://editor.swagger.io/

以下のように、画面右側の部分で入力したAPIを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3565355/d803ecc4-413c-3f1e-5aff-766fe7640955.png)

#### 2. コード生成

`openapi-generator-cli`を使うことで、定義したOpenAPIのyamlファイルから、クライアントのコードを生成します。`openapi-generator-cli`を使う方法はいくつかありますが、ここでは`docker`を利用します。作業ディレクトリに`api.yaml`を作成してから、以下を実行します。

```bash
$  docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -g python -o /local/client -i /local/api.yaml 
```

実行後に、`client`ディレクトリに`python`のコードが生成されます。この生成されたアプリケーションを以下の手順でインストールします。
```bash
$ cd client
$ python3 setup.py install --user
```

#### 3. Webアプリケーションの実装

上記で生成したAPIを通じてSwitchbot Hub 2の温度を取得してグラフとして表示するWebアプリケーションを、[`streamlit`](https://streamlit.io/)というフレームワークを用いて実装してみます。

まずSwitchbot Hub 2のデバイスID（以下、`YOUR_DEVICE_ID`と表記します）を取得するために、以下のリクエストを実行します。

```bash
$ curl -X 'GET' \
  'https://api.switch-bot.com/v1.0/devices' \
  -H 'accept: application/json' \
  -H 'Authorization: ${YOUR_TOKEN}'
```

正常なレスポンスが取得できた場合、`JSON`形式でデバイス一覧情報が表示されます。このとき、`body->deviceList`のリストの中で、`deviceName`（ハブの名前）が一致する項目の`deviceId`がデバイスID（`YOUR_DEVICE_ID`）です。

```json:実行結果例
{
    "statusCode": 100,
    "body": {
        "deviceList": [
            {
                "deviceId": "YOUR_DEVICE_ID",
                "deviceName": "hub",
                "enableCloudService": true,
                "hubDeviceId": ""
            }
        ],
        "infraredRemoteList": [
        ]
    },
    "message": "success"
}
```

以下のコードでは、取得したデバイスIDを用いてハブの温度情報を取得し、グラフ表示します。温度情報の取得間隔は`0.1秒`とし、最新1000回分をグラフで表示します。

```python:app.py
import streamlit as st

import openapi_client
import numpy as np
import collections
import time
import datetime
from matplotlib import pyplot as plt

configuration = openapi_client.Configuration()
configuration.api_key['Authorization'] = "YOUR_TOKEN"
placeholder = st.empty()
fig = plt.figure()
ax = fig.add_subplot()

queue1 = collections.deque(maxlen=1000)
queue2 = collections.deque(maxlen=1000)
with openapi_client.ApiClient(configuration) as api_client:
    api_instance = openapi_client.DefaultApi(api_client)
    try:
        with placeholder:
            while True:
                ax.clear()
                response = api_instance.devicesidstatusget("YOUR_DEVICE_ID")
                queue1.append(response.body.temperature)
                queue2.append(datetime.datetime.now())
                
                ax.plot(np.array(queue2, dtype=np.datetime64), np.array(queue1, dtype=np.float32))
                ax.set_title(datetime.datetime.now())
                placeholder.pyplot(fig)
                time.sleep(0.1)

    except Exception as e:
        print("Exception when calling DefaultApi->devicesget: %s\n" % e)
```

作業ディレクトリ上に`app.py`を作成してから、アプリケーションを実行します。
```bash
$ streamlit run app.py
```

正常に実行できた場合、アプリケーションのURLが表示されます(例：`http://localhost:8555`)。このURLにローカル環境のブラウザからアクセスすると、画面中にグラフが表示されます。
以下はその実行例です。横軸は日時、縦軸は温度/℃を表しています。

![b9e7860a225f4e9a1b3d8e01b51062cdad6ba532c228f19d7df775bc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3565355/b6ed4cb8-2604-ddba-c21b-6c21acf54ed5.png)

#### 4. まとめ

本記事では、以下の機能について紹介しました。

- OpenAPIを用いたAPI定義
- openapi-generator-cliを用いたコード生成
- streamlit/matplotlibを用いたリアルタイムな情報の可視化

記事の前半部分では、OpenAPIを用いることで、実装するプログラミング言語に依存しない形でAPI定義を作成することができることが確認しました。また後半部分では、生成したクライアント側のコードを通じて、簡単にAPIを呼び出す事ができることを確認しました。