SwitchBot温湿度計からBluetoothで気温と湿度を取得する


[SwitchBot温湿度計](https://www.amazon.co.jp/Alexa%E8%AA%8D%E5%AE%9A%E3%80%91SwitchBot-%E3%82%B9%E3%82%A4%E3%82%B9%E8%A3%BD%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC-%E3%82%B9%E3%83%9E%E3%83%9B%E3%81%A7%E6%B8%A9%E5%BA%A6%E6%B9%BF%E5%BA%A6%E7%AE%A1%E7%90%86-Alexa%E3%80%81Google-home%E3%80%81HomePod%E3%80%81IFTTT/dp/B07L4QNZVF?pd_rd_w=3TMRe&pf_rd_p=229e8785-46af-400e-a825-e0c5d2ea7c1c&pf_rd_r=820F0ZW77YD59RKAKMS2&pd_rd_r=5029d754-d781-4ea6-a473-b46ab5da9d4a&pd_rd_wg=oJe5C&psc=1&ref_=pd_bap_d_csi_bia_sims_prsubs_0_t) から Bluetooth 通信で現在値を取得するメモ。

# 動作確認環境
- macOS 12.1
- node v17.3.0
- npm v8.3.0
- SwitchBot温湿度計 

# node-switchbot で値を取る

[OpenWonderLabs](https://github.com/OpenWonderLabs)が公開している[node-switchbot](https://github.com/OpenWonderLabs/node-switchbot)を使うと、非常に楽。

必要なモジュールをインストールして、

```bash
npm install @abandonware/noble
npm install node-switchbot
```

Quick Start のコードをapp.js等に保存して実行するだけで、値が取得できる。

```javascript
// Load the node-switchbot and get a `Switchbot` constructor object
const Switchbot = require('node-switchbot');
// Create an `Switchbot` object
const switchbot = new Switchbot();

(async () => {
  // Start to monitor advertisement packets
  await switchbot.startScan();
  // Set an event hander
  switchbot.onadvertisement = (ad) => {
    console.log(JSON.stringify(ad, null, '  '));
  };
  // Wait 10 seconds
  await switchbot.wait(10000); // 10秒だと受信しないことがあるので、受信確認用に60秒くらいにしておくと良い
  // Stop to monitor
  switchbot.stopScan();
  process.exit();
})();
```

こんな感じのデータが受信できる。

```json
{
  "id": "11d6e84dd3e18b4e3aab47da4aecb60a",
  "address": "XX:XX:XX:XX:XX:XX",
  "rssi": -54,
  "serviceData": {
    "model": "T",
    "modelName": "WoSensorTH",
    "temperature": {
      "c": 21.7,
      "f": 71.1
    },
    "fahrenheit": false,
    "humidity": 53,
    "battery": 100
  }
}
```
serviceData.temperature.c が気温(℃)
serviceData.humidity が湿度(%)
serviceData.address が対象端末のMACアドレスなので、これで個体を識別する

・データタイプ(modelName)には、
　Bot, Meter, Curtain があるようで、
　温湿度計はMeterに属する。
　https://github.com/OpenWonderLabs/node-switchbot#advertisement-data

・データの受信はプログラム実行直後にくるとは限らず、
　実行から３０秒くらいかかることもある。

・アパートの一部屋で実行すると、ご近所さんのSwitchBotからも ガンガン データを受信する

# Bluetooth API で値を取る

SwitchBot温湿度計は、Bluetooth通信規格に BLE(Bluetooth Low Energy)を採用しているので、とってもエコ。

[通信データの内容](https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API)が公開されているので、任意のBLE通信APIからアクセスしてデータを活用できる。

手元では、[abandonware/noble](https://github.com/abandonware/noble)を使ってデータを取得することもできた。

## abandonware/noble

nodeのBLE通信モジュールとしてnobleが有名みたいだが、[本家](https://github.com/noble/noble)は更新が止まっているので、
[abandonwareさん?](https://github.com/abandonware)がそういった有益なOSSを引き継いで開発保守をしてくれているらしい。

BLEでは、[親機(central module)] **1** 対 [子機(peripheral)] **多** で通信が行われ、
nobleは親機の実装用モジュールとなる。

雑にデータを取りに行くだけならこんな感じ。

```javascript
const UUIDs = ['cba20d00224d11e69fb80002a5d5c51b'];
const noble = require('@abandonware/noble');
noble.on('stateChange', state => {
  // nobleの初期stateはUnknown. poweredOn になってからスキャンを開始しなければいけない
  if (state == 'poweredOn'){
    // UUIDsで指定された対象だけが、スキャンの対象となる。
    // ここで設定している値は、SwitchBot機器用に公開されているUUID
    // 第２引数は duplicate. 同端末からは１回のみデータを受け取るならば false にする
    noble.startScanning(UUIDs, true);
  }
});
noble.on('discover', peripheral => {
  console.log('' + peripheral);
});
```

こんな感じにデータが取れる

```json
{
  "id": "11d6e84dd3e18b4e3aab47da4aecb60a",
  "address": "",
  "addressType": "unknown",
  "connectable": true,
  "advertisement": {
    "manufacturerData": {
      "type": "Buffer",
      "data": [ 89, 0, 220, 48, 188, 73, 157, 176 ]
    },
    "serviceData": [
      {
        "uuid": "0d00",
        "data": {
          "type": "Buffer",
          "data": [ 84, 16, 100, 8, 148, 54 ]
        }
      }
    ],
    "serviceUuids": [
      "cba20d00224d11e69fb80002a5d5c51b"
    ]
  },
  "rssi": -55,
  "mtu": null,
  "state": "disconnected"
}
```

## Meter BLE open API

[abandonware/noble](https://github.com/abandonware/noble)でデータ取得ができるようになったら、
[Meter BLE open API](https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API)で公開されているデータ仕様を参考にして必要な値を取得する。

※ここにUUIDについての記載もある
`UUID: cba20d00-224d-11e6-9fb8-0002a5d5c51b and fee7`

温湿度計用には[(New) Broadcast Message](https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API#new-broadcast-message)のあたりが直接参考になるので、コードにおこすとこんな感じになる。

```javascript
noble.on('discover', peripheral => {
    const sd = peripheral.advertisement.serviceData[0].data;
    const md = peripheral.advertisement.manufacturerData;

    const temperatureAboveZero = (sd[4] & 0b10000000) != 0;
    const data = {
        'type': sd[0] & 0b01111111,
        'battery': sd[2] & 0b01111111,
        'temperature': ((sd[3] & 0b00001111) / 10 + (sd[4] & 0b01111111)) * (temperatureAboveZero?1:-1),
        'humidity': sd[5] & 0b01111111,
        'rssi': peripheral.rssi,
        'address': `${md[2].toString(16)}:${md[3].toString(16)}:${md[4].toString(16)}:${md[5].toString(16)}:${md[6].toString(16)}:${md[7].toString(16)}`
    };
    console.log(JSON.stringify(data));
});
```