Zenn
📶

【C++/WinRT】BLE開発についてまとめる

2025/02/13に公開

はじめに

この記事では、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や名前などの情報を含むオブジェクト。
DeviceInformationCollectionDeviceInformationのコレクション。

・引数の文字列はAQSというWindows特有の構文で、検出するデバイスをフィルタリングする。
・引数を追加すれば検出するデバイスの種類やDeviceCollectionに含める情報もカスタムできる。
DeviceWatcherBluetoothLEAdvertisementWatcherといったオブジェクトを使うと、近づいたデバイスを追加したり離れたデバイスを削除したりと、より柔軟な検出結果が得られる。

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:取得操作の結果。
BluetoothCacheModeUnchachedにするとデバイスから取得する。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

特性の取得

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

特性値の読み取り/書き込み

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()などもある。

参考

DataReader
DataWriter

通知の受け取り

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

ログインするとコメントできます