SwitchBot温湿度計の値を、WindowsでC#でBLEで直接受信する

この記事は、

- **SwitchBot温湿度計**（SwitchBotMeter）の値を、
- **Windows10以降**で、
- **C#**（.Net5.0以降）で、
- **BluetoothLEのAdvertisementを受信**して直接読み込む

方法に関するものです。
Windows10以外では動かないし、SwitchBot以外の温湿度計では使えません。

:::note warn
**Hubのシステムが変わったり、温度計のファームウェアが更新したりして、記事の内容が古いです。**
パケットをフィルタする部分を書き直さないと、動かなくなりました。
一旦、新しいファームウェアでも動くように修正しましたが、後で新しい記事にするかも。
記事を書いたときの温湿度計のファームウェア→v2.6
記事修正時点のファームウェア→**v2.7**
:::

# はじめに

SwitchBotの温湿度計の値を、Hubを介して、WEBから取得する方法はある。
~~しかし、Hubを介すと、30分くらい間隔でしか取得できない。~~（現在はもっと短いスパンでできるかも。）
BLEで直接取得したい。

# 受信する手順
## C#でBLEのクラスを使えるようにする
WindowsでBLEを使うには、Windowsが提供するAPIを使うのがよいでしょう。C#でも、そのAPIを使うためのクラスが提供されています。
しかし、.Net5.0はクロスプラットフォームなので、そのままではWindowsAPIを使ったBLEのクラスにアクセスできません。実行環境をWindowsに限定することで、そのクラスにアクセス可能になります。
実行環境を限定するには、TFM（ターゲットフレームワークモニカー）を変更します。

### TFMを変更する
プロジェクトファイルを直接編集して、TFMを書き換えます。

（VisualStudio2022では、TFMの変更が、GUIでできるようになっています。プロジェクトのプロパティを開いて、ターゲットOSとかターゲットOSバージョンとかを変えると便利。）

プロジェクトファイル内の、

```xml
<TargetFramework>net5.0</TargetFramework>
```

とか書いてあるのが、TFMです。Windows10に限定するにはこう↓

```xml
<TargetFramework>net5.0-windows10.0.17763.0</TargetFramework>
```

これで、BLEのクラスにアクセス可能になります。

## Advertisementパケットを受信する

あとはプログラムを書くだけ。
温湿度計は、温湿度情報をアドバタイズでブロードキャストしてるので、それを受信します。
BLEのアドバタイズパケットを受信するには、`BluetoothLEAdvertisementWatcher`というクラスを使います。

```cs
_watcher = new BluetoothLEAdvertisementWatcher();
_watcher.Received += OnReceived;


_watcher.ScanningMode = BluetoothLEScanningMode.Active;
//_watcher.AdvertisementFilter.Advertisement.ServiceUuids.Add(new Guid("cba20d00-224d-11e6-9fb8-0002a5d5c51b"));
// ↑ServiceUuidが無くなったため、これでフィルタ出来なくなった

using var dataWriter = new DataWriter();
//dataWriter.WriteBytes(new byte[] { 0x00, 0x0d, 0x54 });
// ↑おまじないが変わった↓
dataWriter.WriteBytes(new byte[] { 0x3d, 0xfd, 0x54 });
// ↓温湿度計プラスを使う場合はこっち
//dataWriter.WriteBytes(new byte[] { 0x3d, 0xfd, 0x69 });
_watcher.AdvertisementFilter.Advertisement.DataSections.Add(new BluetoothLEAdvertisementDataSection(0x16, dataWriter.DetachBuffer()));

_watcher.Start();
```

### ポイント
- アクティブスキャンモードにする
    - ベンダーデータとかは常にブロードキャストしているんですが、温湿度情報まで常にしているわけではありません。BLEは、アクティブスキャンすることでブロードキャストの追加データをゲットできます。その中に温湿度情報があります。
- ~~ServiceUuidでフィルタリングする~~
    - ~~SwitchBotの、データのサービスIDは`cba20d00-224d-11e6-9fb8-0002a5d5c51b`です。これでフィルタリングしておかないと、関係ないデータがどしどし来ます。~~
    - **ServiceUuidが送信されなくなったので、これでフィルタ出来なくなりました。**
- データの先頭の値でフィルタリングする
    - 温湿度計のデータは、`0x16`のセクションで提供され、~~`0x000d54`で始まります。`0x000d`はおまじない。~~**（おまじないが変わりました。`0x3dfd54`で始まります。）**`0x54`は温湿度計を表します。これで温湿度計のデータだけゲットできます。
    - 温湿度計プラスの場合は、`0x3dfd69`で始まります。普通の温湿度計と違うので注意。

## 受信したパケットから、データを取り出す

```cs
private void OnReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
    var section = args.Advertisement.GetSectionsByType(0x16)[0];

    var buffer = ArrayPool<byte>.Shared.Rent(8);

    section.Data.CopyTo(buffer);

    var temperature = (buffer[6] & 0x7f) + (buffer[5] & 0x0f) * 0.1f;
    if ((buffer[6] & 0x80) == 0) temperature *= -1;
    var humidity = buffer[7] & 0x7f;

    ArrayPool<byte>.Shared.Return(buffer);

    StatusChanged?.Invoke(args.BluetoothAddress, args.Timestamp, temperature, humidity);
}
```

### ポイント

- ~~受信したデータの先頭に2バイト、0x000dがくっついているので無視しましょう。~~
- 受信したデータの先頭に2バイト、0x3dfdがくっついているので無視しましょう。
- 受信したデータの中に、温湿度情報がバラバラに格納されています。公式のドキュメントを参考に、集めましょう。
    - バッテリー残量も入っています。
- 氷点下のところで計測したことないので、氷点下の計算方法が合ってるのかどうか自信ない。

# まとめ
SwitchBot温湿度計のデータを、Hubを介さず直接BLEで受信してみた。
2秒間隔でアドバタイズパケット送ってるみたいだけど、温湿度計とPCをかなり近づけないと、普通に1分間隔くらいになる。
でも30分間隔よりははるかに良い。

# 参考

SwitchBot公式のドキュメント

https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API