【C++/WinRT】BLE開発についてまとめる
はじめに
この記事では、WindowsのC++/WinRTでBLE(Bluetooth Low Energy)の開発を行う方法について簡単にまとめています。BLE開発って時間が空くとすぐに忘れてしまうし、WinRT自体あまり使う機会がないので、備忘録的な扱いで書いています。詳細な解説は省いているのでご了承ください。
動作環境
執筆時点で動作を確認したWindowsのバージョンです。
エディション | バージョン | ビルド |
---|---|---|
Windows 11 Home | 23H2 | 22631.4751 |
デバイスの検出
DeviceInformation.FindAllAsync()
次のようなコードでBLEデバイスを検出できる。
hstring aqsFilter = L"System.Devices.Aep.ProtocolId:={bb7bb05e-5972-42b5-94fc-76eaa7084d49}";
DeviceInformationCollection devices = co_await DeviceInformation::FindAllAsync(aqsFilter);
DeviceInformation
:デバイスのIDや名前などの情報を含むオブジェクト。
DeviceInformationCollection
:DeviceInformation
のコレクション。
・引数の文字列はAQSというWindows特有の構文で、検出するデバイスをフィルタリングする。
・引数を追加すれば検出するデバイスの種類やDeviceCollection
に含める情報もカスタムできる。
・DeviceWatcher
やBluetoothLEAdvertisementWatcher
といったオブジェクトを使うと、近づいたデバイスを追加したり離れたデバイスを削除したりと、より柔軟な検出結果が得られる。
hstring aqsFilter = GattDeviceService::GetDeviceSelectorFromUuid(uuid);
// uuid: guidオブジェクト。
とすれば、特定のサービスを含むデバイスのみを対象にできる。
参考
DeviceInformation
高度なクエリ構文(AQS)
デバイスの列挙
デバイスの接続
BluetoothLEDevice.FromIdAsync()
次のようなコードでBLEデバイスオブジェクトを作成できる。
BluetoothLEDevice device = co_await BluetoothLEDevice::FromIdAsync(deviceInformation.Id());
// deviceInformation: DeviceInformation.
BluetoothLEDevice
:BLEデバイスを表すオブジェクト。デバイスの状態やサービスにアクセスできる。
・アドレスを使って作成するBluetoothLEDevice::FromBluetoothAddressAsync()
もある。
・実際にはデバイスを作成しただけでは接続できず、サービスを検出したり読み書きの操作を行ったりすると接続が始まる。
参考
BluetoothLEDevice
Bluetooth GATT クライアント
サービスの取得
BluetoothLEDevice.GetGattServicesAsync()
次のようなコードでBLEデバイスから全てのサービスを取得できる。
GattDeviceServicesResult result = co_await device.GetGattServicesAsync(BluetoothCacheMode::Unchached);
if (result.Status() == GattCommunicationStatus::Success)
{
IVectorView<GattDeviceService> services = result.Services();
}
// device: BluetoothLEDevice.
GattDeviceServicesResult
:取得操作の結果。
BluetoothCacheMode
:Unchached
にするとデバイスから取得する。Cached
にするとまずキャッシュから取得を試みる。
GattCommunicationStatus
:接続状態。操作が正常終了したときはSuccess
になる。
GattDeviceService
:サービスを表すオブジェクト。特性の情報を含んでいる。
BluetoothLEDevice.GetGattServicesForUuidAsync()
次のようなコードで特定のuuidに一致するサービスを取得できる。
GattDeviceServicesResult result = co_await device.GetGattServicesForUuidAsync(uuid);
if (result.Status() == GattCommunicationStatus::Success)
{
IVectorView<GattDeviceService> services = result.Services();
}
// device: BluetoothLEDevice.
// uuid: guid.
参考
特性の取得
GattDeviceService.GetCharacteristicsAsync()
次のようなコードでサービスから全ての特性を取得できる。
GattCharacteristicsResult result = co_await service.GetCharacteristicsAsync(BluetoothCacheMode::Unchached);
if (result.Status() == GattCommunicationStatus::Success)
{
IVectorView<GattCharacteristic> characteristics = result.Characteristics();
}
// service: GattDeviceService.
GattCharacteristicsResult
:取得操作の結果。
GattCharacteristic
:特性を表すオブジェクト。特性値の読み書きに使う。
GattDeviceService.GetCharacteristicsForUuidAsync()
次のようなコードで特定のuuidに一致する特性を取得できる。
GattCharacteristicsResult result = co_await service.GetCharacteristicsForUuidAsync(uuid);
if (result.Status() == GattCommunicationStatus::Success)
{
IVectorView<GattCharacteristic> characteristics = result.Characteristics();
}
// service: GattDeviceService.
// uuid: guid.
参考
特性値の読み取り/書き込み
GattCharacteristic.ReadValueAsync()
次のようなコードで特性値を読み取ることができる。※下記はバイト値を読み取る場合。
GattReadResult result = co_await characteristic.ReadValueAsync(BluetoothCacheMode::Unchached);
if (result.Status() == GattCommunicationStatus::Success)
{
DataReader reader = DataReader::FromBuffer(result.Value());
byte value = reader.ReadByte();
}
// characteristic: GattCharacteristic.
GattReadResult
:読み取り操作の結果。
DataReader
:ストリームからデータを読み取るオブジェクト。
・GattReadResult.Value()
で特性値を含むバッファを取得し、このバッファを使ってDataReader
を作成する。
・バイト値以外にReadUInt16()
、ReadUInt32()
など他のフォーマットの読み取りに対応したメソッドがある。
GattCharacteristic.WriteValueAsync()
次のようなコードで特性値を書き込むことができる。※下記はバイト値を書き込む場合。
DataWriter writer;
writer.WriteByte(value);
GattCommunicationStatus status = co_await characteristic.WriteValueAsync(writer.DetachBuffer());
if (status == GattCommunicationStatus::Success)
{
// 書き込み成功.
}
// characteristic: GattCharacteristic.
DataWriter
:ストリームにデータを書き込むオブジェクト。
・DataWriter.DetachBuffer()
でデータを書き込んだバッファを返し、解放する。
・DataReader
同様にWriteUInt16()
やWriteUInt32()
などもある。
参考
通知の受け取り
GattCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync()
次のようなコードで特性値が変更されたときに通知を受信できる。
GattClientCharacteristicConfigurationDescriptorValue cccdValue = GattClientCharacteristicConfigurationDescriptorValue::Notify;
GattCommunicationStatus status = characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(cccdValue)
if (status == GattCommunicactionStatus::Success)
{
token = characteristic.ValueChanged(handler);
}
// token: event_token.
// handler: TypedEnventHandler<GattCharacteristic, GattValueChangedEventArgs>.
GattClientCharacteristicConfigurationDescriptorValue
:cccdに書き込む値。通知を受け取る場合はNotify
にする。
・通知を許可するにはcccdの値を書き換えておく必要がある。この値は通信切断後も保持されるため通知が不要になったらNone
を書き込む必要がある。
・特性値が更新されるとGattCharacteristic.ValueChanged()
に渡したイベントハンドラが呼び出される。
参考
GattClientCharacteristicConfigurationDescriptorValue
GattValueChangedEventArgs
デバイスの切断
BluetoothLEDevice.Close()
次のようなコードでBLEデバイスとの通信を切断できる。
device.Close();
device = nullptr;
// deivce: BluetoothLEDevice.
・Close()
でBLEデバイスとの接続を閉じる。
最後に
以上、C++/WinRTによるBLE開発のざっくりとしたまとめでした。そのうち追記していくかもしれません。
C++で実装している例が少なそうなので、何かの参考になれば幸いです。
Discussion