SwitchBot API v1.1を使用して、Zabbixから各種データを取得する

# はじめに
我が家で使用している各サーバやネットワーク機器、ストレージやIoT関連のものは、すべてZabbixにてモニタリングしています。
SwitchBot社の製品も大好きで、特に温湿度計は家の至る所にばらまいています。
これまでSwitchBotの温湿度計はAPI v1.0を使用してZabbixからデータ取得、管理していましたが、APIのv1.1が[公開された](https://blog.switchbot.jp/announcement/api-v1-1/)のは知っていたのですが、

・v1.0で特に困っていなかったこと
・v1.1は認証方法が少し複雑になっていて、v1.0の場合のように単発curlで値を取得するということができなくなってしまったこと

これらのことから、「今後のことも考えて新しいVersionに移行したほうがいいんだろうな」とは思いながら1年以上も放置してしまっていました。
今回ようやくv1.1に移行したので、自分用備忘録の意味も含めて記事にします。

## API v1.1に移行するモチベーション
・v1.0では取得できなかった、バッテリー残容量が取得できる。（今回のメインの目的）※
・勉強になる。
・今後新商品が発売された際にも対応できる。つまり発売されたら心置きなく即ポチすることができる。

※実はBluetooth APIではバッテリー残量も取得できていたのですが、そのために「SwitchBot温湿度計からBluetoothの届く場所にデータ取得用のRaspberryPiZeroを設置する」という訳分からんことをしてしまっていました。


## 環境
### データ取得側
・ZabbixServer 6.0.23
・Debian 11.8
・Python 3.9.2

### データ取得される側
・SwitchBot 温湿度計（V2.6）
・SwitchBot 温湿度計プラス（V0.6）
・SwitchBot 防水温湿度計（V0.4）
・SwitchBot HUB MINI（V5.4-3.8）　※APIでデータを取得するには、HUBが必須です。

# Zabbixでの設定
## データ取得の流れ
ZabbixからAPIを叩いてデータ取得をし、ZabbixDBまでに格納するまでの流れは以下の通りです。
１．アイテムのタイプ：外部チェック を使用して、目的のデバイスのデータすべてをJSON形式で取得する。
２．アイテムのタイプ：依存アイテム　を使用して、１．で取得したアイテムから目的の項目（温度や湿度やバッテリー残量等）を取得する
３．取得したい項目の数だけ２．と同様のアイテムを作成する。

こうすることで、1回のAPI実行で複数の値を取得することができるため、API実行回数を節約することができます。
SwitchBot APIの実行回数は、1日あたり10,000回までに制限されています。

例えば温度、湿度、バッテリー残量の3つのデータをそれぞれ個別に1分毎にAPIを実行して取得する場合、
$10000 ÷ (86400 ÷ 60 × 3) ≒ 2.315$
となり、2台までしかデータを取得することができません。
しかし、1度のAPI実行ですべての値を取得する場合は
$10000 ÷ (86400 ÷ 60 × 1) ≒ 6.944$
となり、6台までデータを取得することができるようになります。


## 事前準備
### ・SwitchBot APIを使用するためのトークン、クライアントシークレットの確認
公式ページにある[手順](https://support.switch-bot.com/hc/ja/articles/12822710195351-%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E5%8F%96%E5%BE%97%E6%96%B9%E6%B3%95)に従い、トークンとクライアントシークレットを取得しておきます。
長いのでタイポしないようにコピーしてGmailで送る等でPCに持ってくることをおすすめします。

### ・各デバイスのデバイスIDの確認
API v1.1を使用しても良いのですが、デバイスIDを確認するだけの場合はv1.0のほうが簡単なので、v1.0を使用します。

任意のLinuxホストから以下コマンドを実行すると
```
curl -H "Authorization:トークン" -X GET "https://api.switch-bot.com/v1.0/devices" | jq
```


以下のような結果が得られます。
```
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2473  100  2473    0     0   2810      0 --:--:-- --:--:-- --:--:--  2807
{
  "statusCode": 100,
  "body": {
    "deviceList": [
      {
        "deviceId": "xxxxxxxxxx",　←この値を確認
        "deviceName": "防湿庫",
        "deviceType": "Meter",
        "enableCloudService": true,
        "hubDeviceId": "xxxxxxxxxx"
      },
      {
        "deviceId": "xxxxxxxxxx",　←この値を確認
        "deviceName": "ベランダ",
        "deviceType": "WoIOSensor",
        "enableCloudService": true,
        "hubDeviceId": "xxxxxxxxxx"
      },
      {
        "deviceId": "xxxxxxxxxx",　←この値を確認
        "deviceName": "居間",
        "deviceType": "MeterPlus",
        "enableCloudService": true,
        "hubDeviceId": "xxxxxxxxxx"
      },

 ・
　・
　・
　中略
　・
　・
　・

    ]
  },
  "message": "success"
}

```




### ・ZabbixServerにPythonスクリプトを設置
#### -外部スクリプトの保存場所確認

zabbix_server.conf内のExternalScriptsという記載箇所を確認します。
下の例では **/usr/lib/zabbix/externalscripts** が外部スクリプトの置き場所になります。
```
# grep ExternalScripts /etc/zabbix/zabbix_server.conf

### Option: ExternalScripts
# ExternalScripts=/usr/lib/zabbix/externalscripts
ExternalScripts=/usr/lib/zabbix/externalscripts
```
もしこの記載が無いorコメントアウトされている場合は、任意のディレクトリを指定する設定を追加しておきます。（zabbix_server.conf編集時はzabbix-serverサービスを再起動する必要があります。

#### -外部スクリプトの設置
先程確認したディレクトリに以下のPythonスクリプトを起きます。
ファイル名は何でも良いですが、ここではわかりやすいようにをswitchbot_api.pyとしておきます。

[@rat-engineer755](https://qiita.com/rat-engineer755)さんの[記事](https://qiita.com/rat-engineer755/items/f6341c9dc0b6afcb1130)を参考にさせていただきました。
トークン、クライアントシークレット、デバイスIDを引数に実行すると、JSON形式で値一覧が取得できるように改変しています。
```switchbot_api.py
#!/usr/bin/env python3

import time
import hashlib
import hmac
import base64
import uuid
import sys
import requests
import json

#クライアントシークレットをbytes方に変更
def make_secret(secret_key):
    secret_key = bytes(secret_key, 'utf-8')
    return secret_key

#signを作成
def make_sign(secret_key, t, nonce):
    string_to_sign = '{}{}{}'.format(token, t, nonce)
    string_to_sign = bytes(string_to_sign, 'utf-8')
    sign = base64.b64encode(hmac.new(secret_key, msg=string_to_sign, digestmod=hashlib.sha256).digest())
    return sign

#tを作成
def make_t():
    t = int(round(time.time() * 1000))
    return str(t)

#nonceを作成
def make_nonce():
    nonce = str(uuid.uuid4())
    return nonce

#引数を格納
token = sys.argv[1]
secret_key = sys.argv[2]
device_id = sys.argv[3]

#必要なパラメータを作成する
secret_key = make_secret(secret_key)
t = make_t()
nonce = make_nonce()
sign = make_sign(secret_key, t, nonce)

#URL指定
url = "https://api.switch-bot.com/v1.1/devices/{}/status".format(device_id)

#APIheader作成
headers = {
    "Authorization": token,
    "sign": sign,
    "t": t,
    "nonce": nonce,
    "Content-Type": "application/json; charset=utf-8"
}

#requests処理
response = requests.get(url,headers=headers)

# レスポンス処理とデータ型変更
data = response.json()

# JSONデータを文字列に変換してprintで出力
print(json.dumps(data, indent=2))
```

パーミッションを変更するのを忘れないように。
```
# pwd
/usr/lib/zabbix/externalscripts
# chown zabbix:zabbix switchbot_api.py
# chmod 0755 switchbot_api.py
# ls -l switchbot_api.py
-rwxr-xr-x 1 zabbix zabbix 1439  1月  9 22:33 switchbot_api.py
```
#### -動作確認
一度手動でコマンドを実行し、正常に値を取得できることを確認しておきます。
以下のように、ステータスコード100で各種値が取得できれば成功です。
```
# sudo -u zabbix ./switchbot_api.py トークン クライアントシークレット デバイスID
{
  "statusCode": 100,
  "body": {
    "deviceId": "xxxxxxxxxx",　←デバイスID
    "deviceType": "WoIOSensor",
    "humidity": 61,　←湿度
    "temperature": 3.3,　←温度
    "version": "V0.4",　←ファームウェアバージョン
    "battery": 100　←バッテリー残量
  },
  "message": "success"
}
```

## Zabbixの設定
### ・Zabbixテンプレートの作成
個別でアイテムを作成しても良いのですが、複数の温湿度計を設定したいので、Zabbixテンプレートを作成します。
#### テンプレート
テンプレート名：Swichbot_meter
表示名　　　　：Switchbot温度計
グループ　　　：任意
![00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696364/2fa2debf-216c-97e0-c7b4-091aec101e77.jpeg)

#### テンプレート-マクロ
マクロ：{$AUTHORIZATION}
値　　：アプリで確認したトークン

マクロ：{$CLIENT_SECRET}
値　　：アプリで確認したクライアントシークレット

![001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696364/cdf03efb-9e78-0649-02ad-08f776daad66.jpeg)

#### テンプレート-アイテム-アイテム
名前　　：switchbot.api　※任意で構いません
タイプ　：外部チェック
キー　　：switchbot_api.py[{$AUTHORIZATION},{$CLIENT_SECRET},{$DEVICEID}]
データ型：テキスト
監視間隔：2m　※通常は1m等で問題ありませんが、我が家は温湿度計が7台以上あるので、API実行回数制限にひっかかってしまうので、2分間隔としています。
![01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696364/a664935e-61d7-1824-6f67-2aca05b2a29c.jpeg)
このアイテムはあくまでも事前のデータ取得用なので、ヒストリの保存期間はデバッグが終わったら「ヒストリを保存しない」としても良いかもしれません。

#### テンプレート-アイテム-保存前処理
名前　　　：JSONPath
パラメータ：$.body
![02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696364/f1acbdaa-7556-478a-3208-67536cf4f7c0.jpeg)

#### テンプレート-アイテムその2-アイテム
名前　　：温度
タイプ　：依存アイテム
キー　　：temperature　※任意で構いません
データ型：数値(不動小数)　※取得する値の種類にあわせる
マスターアイテム：Switchbot: switchbot.api
単位　　：℃　※取得する値の種類にあわせる
![03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696364/6a17fe2a-5f38-136d-7a94-a23ee1cd399d.jpeg)

#### テンプレート-アイテムその2-保存前処理
名前　　　：JSONPath
パラメータ：$.temperature　※取得したいJSONPathに合わせます
![04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696364/648cdd75-bab6-844f-ed6b-6ae82dd27cc1.jpeg)

このように、一旦メインのアイテムですべての値を一括取得し、そこから依存アイテムで必要な値を抜き出す形を取ります。
上記の例では温度を取得しましたが、例えば湿度の場合は保存前処理のパラメータを
**$.humidity**
とします。
取得したいデータの種類数だけ同じようにテンプレート-アイテムを作成します。

### ・ホストの作成
#### ホスト
ホスト名：Switchbot WoIOSensor　※任意で構いません
テンプレート：Switchbot温湿度計　※先に作成したテンプレートを適用します。
インタフェース：**SNMPでもエージェントでも構いませんが、何らかのインタフェースを追加する必要があります。（インタフェースが存在しない状態だと、エラーとなってしまいます。）**　アドレスは127.0.0.1で構いません。


![05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696364/fe2ed4da-38a7-c300-e73a-5017b7edddc4.jpeg)

#### ホスト-マクロ
マクロ：{$DEVICEID}
値　　：事前に確認しておいたデバイスID
![06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696364/ad6bab72-e202-37bd-f340-9065a37f8bca.jpeg)

これを、データを取得したいアイテム毎に作成します。
つまり、名前とマクロの{$DEVICEID}の値のみ違うホストが台数分できることになります。

# 最後に
基本的に値を取得するだけであれば、SwitchBotプラグminiであろうと、カーテンであろうとSwitchBot社の製品であれば同じ仕組みが使えるはずです。
私は同じ仕組みでSwitchBotプラグminiの消費電力をモニタリングしています。

また、Pythonスクリプトにトークン、クライアントシークレットは書かれておらず、Zabbixのマクロにて指定しているため、別のアカウントに登録された機器も同じスクリプトを使うことができます。


今回は値の取得だけですが、今後はカーテンやスマートロック等の操作もZabbixから行えるようにしてみたいと考えています。