Bluetooth GATT クライアント

この記事では、ユニバーサル Windows プラットフォーム (UWP) アプリに Bluetooth 汎用属性 (GATT) クライアント API を使用する方法について説明します。

重要

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

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

重要な API

概要

開発者は、Windows.Devices.Bluetooth.GenericAttributeProfile 名前空間の API を使って Bluetooth LE デバイスにアクセスすることができます。 Bluetooth LE デバイスは、その機能をコレクションを通じて公開します。コレクションには次の情報が含まれています。

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

サービスは、LE デバイスの機能的なコントラクトを定義するもので、サービスを定義する特性のコレクションを含みます。 これらの特性はさらに、その特性を表す記述子を含みます。 これら 3 つの用語は、一般的に、デバイスの属性と呼ばれます。

Bluetooth LE GATT API は、生のトランスポートにアクセスするのではなく、オブジェクトと関数を公開します。 また、GATT API で Bluetooth LE デバイスと連携することによって、次のことが可能となります。

  • 属性の検出の実行
  • 属性値の読み取りと書き込み
  • 特性の ValueChanged イベントで呼び出されるコールバックの登録

実用的なアプリケーションを作成するためには、利用する GATT のサービスと特性についての予備知識が開発者に求められます。実際に必要な特性値を処理し、API から提供されるバイナリ データを実用的なデータに変換したうえで、ユーザーに提示しなければなりません。 Bluetooth GATT API が公開するのは、Bluetooth LE デバイスとの通信に必要な基本的なプリミティブだけです。 データを解釈するためには、Bluetooth SIG の標準のプロファイルか、デバイスのベンダーが実装したカスタム プロファイルによって、アプリケーション プロファイルを定義する必要があります。 プロファイルは、交換されるデータが表す内容や、その解釈の方法に関して、アプリケーションとデバイスとの間で交わされるバインド コントラクトを形成します。

Bluetooth SIG は、利便性向上のため、一連のプロファイルを一般公開しています。

完全なサンプルについては、Bluetooth 低エネルギーのサンプルに関するページを参照してください。

近くのデバイスの照会

近くのデバイスを照会するための主なメソッドは 2 つあります。

2 つ目のメソッドについては、アドバタイズに関するドキュメントで詳しく説明されているため、ここでは簡単に説明します。基本的な考え方は、特定のアドバタイズ フィルターの条件を満たす、近くにあるデバイスの Bluetooth アドレスを検出するということです。 アドレスを検出したら、BluetoothLEDevice.FromBluetoothAddressAsync を呼び出して、デバイスへの参照を取得します。

DeviceWatcher メソッドの説明に戻ります。 Bluetooth LE デバイスは、Windows の他のデバイスと同じように列挙 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 について詳しくは、Github にある完全なサンプルをご覧ください。

デバイスへの接続

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

async void 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 エラーと共に制御が戻ります。

注意

このメソッドを単独で呼び出し、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
}

ヒント

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

通知の受信登録

特性が Indicate または Notify をサポートしているかどうかを確認します (確認するには特性のプロパティを調べます)。

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

通知を取得する前に処理することが 2 つあります。

  • Client Characteristic Configuration Descriptor (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.
}