Bluetooth GATTクライアント

この記事では、Windows アプリに Bluetooth 汎用属性 (GATT) クライアント API を使用する方法について説明します。

Important

Package.appxmanifest で "bluetooth" 機能を宣言する必要があります。

<Capabilities> <DeviceCapability Name="bluetooth" /> </Capabilities>

概要

Bluetooth LE デバイスにアクセスするには、Windows.Devices.Bluetooth.GenericAttributeProfile 名前空間の API を使用できます。 Bluetooth LE デバイスは、次のコレクションを通じてその機能を公開します。

  • サービス
  • 特性
  • 記述子

サービスは、LE デバイスの機能コントラクトを定義し、サービスを定義する特性のコレクションを含みます。 これらの特性には、特性を記述する記述子が含まれています。 これらの 3 つの用語は、デバイスの属性として一般的に知られています。

Bluetooth LE GATT API は、生トランスポートにアクセスするのではなく、オブジェクトと関数を公開します。 GATT API を使用すると、次のタスクを実行Bluetooth LE デバイスを操作することもできます。

  • 属性の検出を実行する
  • 属性値の読み取りと書き込み
  • Characteristic ValueChanged イベントのコールバックを登録する

有用な実装を作成するには、アプリケーションが使用する GATT サービスと特性に関する事前知識を持ち、API によって提供されるバイナリ データがユーザーに提示される前に有用なデータに変換されるように、特定の特性値を処理する必要があります。 Bluetooth GATT API は、Bluetooth LE デバイスとの通信に必要な基本プリミティブのみを公開します。 データを解釈するには、アプリケーション プロファイルを、Bluetooth SIG 標準プロファイルまたはデバイス ベンダーによって実装されたカスタム プロファイルによって定義する必要があります。 プロファイルは、交換されたデータが何を表し、それを解釈する方法に関して、アプリケーションとデバイスの間にバインド コントラクトを作成します。

便宜上、Bluetooth SIG は使用可能な パブリック プロファイルの一覧 を保持します。

近くのデバイスのクエリ

近くのデバイスに対してクエリを実行するには、主に次の 2 つの方法があります。

2 番目の方法は 、広告 ドキュメントで長く説明されているため、ここではあまり説明しませんが、基本的な考え方は、特定の 広告フィルターを満たす近くのデバイスのBluetoothアドレスを見つけることです。 アドレスを取得したら、 BluetoothLEDevice.FromBluetoothAddressAsync を呼び出してデバイスへの参照を取得できます。

次に、DeviceWatcher メソッドに戻ります。 Bluetooth LE デバイスは、Windows内の他のデバイスと同様であり、Enumeration API を使用してクエリを実行できます。 DeviceWatcher クラスを使用し、検索するデバイスを指定するクエリ文字列を渡します。

// Query for extra properties you want returned
string[] requestedProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected" };

DeviceWatcher deviceWatcher =
            DeviceInformation.CreateWatcher(
                    BluetoothLEDevice.GetDeviceSelectorFromPairingState(false),
                    requestedProperties,
                    DeviceInformationKind.AssociationEndpoint);

// Register event handlers before starting the watcher.
// Added, Updated and Removed are required to get all nearby devices
deviceWatcher.Added += DeviceWatcher_Added;
deviceWatcher.Updated += DeviceWatcher_Updated;
deviceWatcher.Removed += DeviceWatcher_Removed;

// EnumerationCompleted and Stopped are optional to implement.
deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted;
deviceWatcher.Stopped += DeviceWatcher_Stopped;

// Start the watcher.
deviceWatcher.Start();

DeviceWatcher を起動すると、対象のデバイスの Added> イベントのハンドラーでクエリを満たす各デバイスに対して、DeviceInformation を受け取ります。 DeviceWatcher の詳細については、完全なサンプル on Github を参照してください。

デバイスに接続する

目的のデバイスが検出されたら、 DeviceInformation.Id を使用して、該当するデバイスのBluetooth LE Device オブジェクトを取得します。

private async Task ConnectDevice(DeviceInformation deviceInfo)
{
    // Note: BluetoothLEDevice.FromIdAsync must be called from a UI thread because it may prompt for consent.
    BluetoothLEDevice bluetoothLeDevice = await BluetoothLEDevice.FromIdAsync(deviceInfo.Id);
    // ...
}

一方、デバイスの BluetoothLEDevice オブジェクトへのすべての参照を破棄すると (およびシステム上の他のアプリがデバイスへの参照を持っていない場合)、タイムアウト期間が短い場合に自動切断がトリガーされます。

bluetoothLeDevice.Dispose();

アプリがデバイスにもう一度アクセスする必要がある場合は、デバイス オブジェクトを再作成し、特性 (次のセクションで説明) にアクセスするだけで、必要に応じて OS が再接続されます。 デバイスが近くにある場合は、デバイスにアクセスできます。そうしないと、DeviceUnreachable エラーが返されます。

Note

このメソッドだけを呼び出して BluetoothLEDevice オブジェクトを作成しても、必ずしも接続は開始されません。 接続を開始するには、 GattSession.MaintainConnectiontrue に設定するか、 BluetoothLEDevice でキャッシュされていないサービス検出メソッドを呼び出すか、デバイスに対して読み取り/書き込み操作を実行します。

  • GattSession.MaintainConnection が true に設定されている場合、システムは接続を無期限に待機し、デバイスが使用可能になると接続します。 GattSession.MaintainConnection はプロパティであるため、アプリケーションが待機するものはありません。
  • GATT でのサービス検出および読み取り/書き込み操作の場合、システムは有限の可変時間を待機します。 瞬時の場合もあれば、数分かかる場合もあります。 要因には、スタック内のトラフィックや、リクエストがどの程度キューにたまっているかが含まれます。 他の保留中の要求がなく、リモート デバイスに到達できない場合、システムはタイムアウトになるまで 7 秒間待機します。他の保留中の要求がある場合、キュー内の各要求の処理に 7 秒かかる可能性があるため、キューの背面に向かって進むほど、待つ時間が長くなります。

現時点では、接続プロセスを取り消すことはできません。

サポートされているサービスと特性を列挙する

BluetoothLEDevice オブジェクトが作成されたので、次の手順は、デバイスが公開するデータを検出することです。 これを行う最初の手順は、サービスのクエリを実行することです。

GattDeviceServicesResult result = await bluetoothLeDevice.GetGattServicesAsync();

if (result.Status == GattCommunicationStatus.Success)
{
    var services = result.Services;
    // ...
}

目的のサービスが特定されたら、次の手順では特性を照会します。

GattCharacteristicsResult result = await service.GetCharacteristicsAsync();

if (result.Status == GattCommunicationStatus.Success)
{
    var characteristics = result.Characteristics;
    // ...
}

OS は、操作を実行できる GattCharacteristic オブジェクトの ReadOnly リストを返します。

特性に対して読み取り/書き込み操作を実行する

特徴は、GATT ベースの通信の基本単位です。 デバイス上の個別のデータを表す値が含まれています。 たとえば、バッテリ レベル特性には、デバイスのバッテリ レベルを表す値があります。

サポートされている操作を確認するには、特性プロパティを読み取ります。

GattCharacteristicProperties properties = characteristic.CharacteristicProperties

if(properties.HasFlag(GattCharacteristicProperties.Read))
{
    // This characteristic supports reading from it.
}
if(properties.HasFlag(GattCharacteristicProperties.Write))
{
    // This characteristic supports writing to it.
}
if(properties.HasFlag(GattCharacteristicProperties.Notify))
{
    // This characteristic supports subscribing to notifications.
}

読み取りがサポートされている場合は、値を読み取ることができます。

GattReadResult result = await selectedCharacteristic.ReadValueAsync();
if (result.Status == GattCommunicationStatus.Success)
{
    var reader = DataReader.FromBuffer(result.Value);
    byte[] input = new byte[reader.UnconsumedBufferLength];
    reader.ReadBytes(input);
    // Utilize the data as needed
}

特性への書き込みは、同様のパターンに従います。

var writer = new DataWriter();
// WriteByte used for simplicity. Other common functions - WriteInt16 and WriteSingle
writer.WriteByte(0x01);

GattCommunicationStatus result = await selectedCharacteristic.WriteValueAsync(writer.DetachBuffer());
if (result == GattCommunicationStatus.Success)
{
    // Successfully wrote to device
}

Tip

DataReaderDataWriter は、多くのBluetooth API から取得する生バッファーを操作する際に不可欠です。

通知を購読する

特性が Indicate または Notify をサポートしていることを確認します (特性プロパティを確認してください)。

Indicate は、各値変更イベントがクライアント デバイスからの受信確認と結合されるため、信頼性が高いと見なされます。 Notify は、ほとんどの GATT トランザクションが非常に信頼性の高いものではなく、電力を節約する必要があるため、より一般的です。 いずれの場合も、アプリが関与しないように、そのすべてがコントローラー レイヤーで処理されます。 これらを単なる "通知" と総称します。

通知を受け取る前に、次の 2 つの点に注意する必要があります。

  • クライアント特性構成記述子 (CCCD) への書き込み
  • Characteristic.ValueChanged イベントを処理する

CCCD への書き込みは、このクライアントが特定の特性値が変更されるたびに知りたいことをサーバー デバイスに通知します。 これを行うには、次の手順を実行します。

GattCommunicationStatus status = await selectedCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
                        GattClientCharacteristicConfigurationDescriptorValue.Notify);
if(status == GattCommunicationStatus.Success)
{
    // Server has been informed of clients interest.
}

ここで、GattCharacteristic の ValueChanged イベントは、リモート デバイスで値が変更されるたびに呼び出されます。 残っているのは、ハンドラーを実装することです。

characteristic.ValueChanged += Characteristic_ValueChanged;

...

void Characteristic_ValueChanged(GattCharacteristic sender,
                                    GattValueChangedEventArgs args)
{
    // An Indicate or Notify reported that the value has changed.
    var reader = DataReader.FromBuffer(args.CharacteristicValue)
    // Parse the data however required.
}

Examples

完全なサンプルについては、Bluetooth Low Energy サンプルを参照してください。