Bluetooth GATT サーバー

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

重要

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

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

重要な API

概要

Windows は、通常、クライアントの役割で動作します。 ただし、Windows を Bluetooth LE GATT サーバーとして動作させることが必要なシナリオが数多く発生します。 多くのクロスプラットフォーム BLE 通信と共に、IoT デバイス向けのほとんどすべてのシナリオでは、Windows が GATT サーバーとして機能する必要があります。 さらに、近くのウェアラブル デバイスに通知を送信することも、このテクノロジを必要とする一般的なシナリオになっています。

サーバーの処理は、サービス プロバイダーと GattLocalCharacteristic を中心に実行されます。 これら 2 つのクラスは、データの階層を宣言、実装し、リモート デバイスに公開するために必要な機能を提供します。

サポートされるサービスの定義

アプリでは、Windows によって公開される 1 つまたは複数のサービスを宣言できます。 各サービスは、UUID によって一意に識別されます。

属性と UUID

各サービス、特性、記述子は、それぞれ一意の 128 ビット UUID によって定義されます。

すべての Windows API では GUID という用語を使用しますが、Bluetooth の標準では、これらを UUID と定義しています。 このドキュメントの説明では、これら 2 つの用語は入れ替え可能であるため、ここでは UUID という用語を使用します。

属性が標準であり、Bluetooth SIG によって定義されている属性の場合は、対応する 16 ビットの短い ID もあります (たとえば、バッテリ レベル UUID は 00002A19-0000-1000-8000-00805F9B34FB で、短い ID は 0x2A19 です)。 これらの標準的な UUID については、GattServiceUuidsGattCharacteristicUuids に関するページをご覧ください。

アプリで独自のカスタム サービスを実装している場合は、カスタム UUID を生成する必要があります。 これは、Visual Studio で [ツール] -> [GUID の作成] を選択して簡単に作成できます ("xxxxxxxx-xxxx-...xxxx" 形式で取得するにはオプション 5 を使用します)。 この UUID を使用して、新しいローカル サービス、特性、記述子を宣言できます。

制限されているサービス

次のサービスは、システムによって予約されており、現時点で公開することはできません。

  1. デバイス情報サービス (DIS)
  2. 汎用属性プロファイル サービス (GATT)
  3. 汎用アクセス プロファイル サービス (GAP)
  4. ヒューマン インターフェイス デバイス サービス (HOGP)
  5. スキャン パラメーター サービス (SCP)

ブロックされているサービスを作成しようとすると、CreateAsync への呼び出しから BluetoothError.DisabledByPolicy が返されます。

生成される属性

次の記述子は、特性の作成時に指定した GattLocalCharacteristicParameters に基づいて、システムによって自動的に生成されます。

  1. Client Characteristic Configuration (特性が指示可能または通知可能とマークされている場合)。
  2. Characteristic User Description (UserDescription プロパティが設定されている場合)。 詳しくは、GattLocalCharacteristicParameters.UserDescription プロパティをご覧ください。
  3. Characteristic Format (指定されたプレゼンテーション形式ごとに 1 つの記述子)。 詳しくは、GattLocalCharacteristicParameters.PresentationFormats プロパティをご覧ください。
  4. Characteristic Aggregate Format (複数の形式を指定した場合)。 詳しくは、GattLocalCharacteristicParameters.PresentationFormats プロパティをご覧ください。
  5. Characteristic Extended Properties (特性が拡張プロパティ ビットでマークされている場合)。

Extended Properties 記述子の値は、ReliableWrites および WritableAuxiliaries 特性プロパティによって決まります。

予約済みの記述子を作成しようとすると、例外が発生します。

現時点では、ブロードキャストはサポートされていないことに注意してください。 GattCharacteristicProperty のブロードキャストを指定すると、例外が発生します。

サービスと特性の階層の構築

ルート プライマリ サービスの定義を作成してアドバタイズするには、GattServiceProvider を使用します。 各サービスでは、GUID を受け取る独自の ServiceProvider オブジェクトが必要です。

GattServiceProviderResult result = await GattServiceProvider.CreateAsync(uuid);

if (result.Error == BluetoothError.Success)
{
    serviceProvider = result.ServiceProvider;
    // 
}

プライマリ サービスは、GATT ツリーの最上位レベルです。 プライマリ サービスには、特性とその他のサービス (含まれるサービスまたはセカンダリ サービスと呼ばれます) が含まれます。

次に、サービスに、必要な特性と記述子を設定します。

GattLocalCharacteristicResult characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid1, ReadParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
    // An error occurred.
    return;
}
_readCharacteristic = characteristicResult.Characteristic;
_readCharacteristic.ReadRequested += ReadCharacteristic_ReadRequested;

characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid2, WriteParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
    // An error occurred.
    return;
}
_writeCharacteristic = characteristicResult.Characteristic;
_writeCharacteristic.WriteRequested += WriteCharacteristic_WriteRequested;

characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid3, NotifyParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
    // An error occurred.
    return;
}
_notifyCharacteristic = characteristicResult.Characteristic;
_notifyCharacteristic.SubscribedClientsChanged += SubscribedClientsChanged;

上記のように、これは、それぞれの特性がサポートしている操作のイベント ハンドラーを宣言する場所としても適しています。 要求に正しく応答するには、属性がサポートしている各種の要求について、アプリがイベント ハンドラーを設定している必要があります。 ハンドラーを登録できない場合、システムによって UnlikelyError が生成され、要求はすぐに完了します。

定数の特性

場合によって、アプリの有効期間中に変更されない特性値があります。 その場合、不要なアプリのアクティブ化を防ぐために、定数の特性を宣言することをお勧めします。

byte[] value = new byte[] {0x21};
var constantParameters = new GattLocalCharacteristicParameters
{
    CharacteristicProperties = (GattCharacteristicProperties.Read),
    StaticValue = value.AsBuffer(),
    ReadProtectionLevel = GattProtectionLevel.Plain,
};

var characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid4, constantParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
    // An error occurred.
    return;
}

サービスの公開

サービスが完全に定義されたら、次の手順は、サービスのサポートを公開することです。 これにより、リモート デバイスがサービスの検出を実行するときに、サービスが返されることを OS に通知します。 IsDiscoverable と IsConnectable の 2 つのプロパティを設定する必要があります。

GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
{
    IsDiscoverable = true,
    IsConnectable = true
};
serviceProvider.StartAdvertising(advParameters);
  • IsDiscoverable: 公開通知によってリモート デバイスにフレンドリ名をアドバタイズし、デバイスを検出可能にします。
  • IsConnectable: ペリフェラルの役割で使うための接続可能な公開通知をアドバタイズします。

サービスが検出可能で、接続可能である場合、システムはサービス UUID をアドバタイズ パケットに追加します。 アドバタイズ パケットは 31 バイトだけであり、128 ビットの UUID はそのうちの 16 バイトを使用します。

あるサービスがフォアグラウンドで公開されている場合、アプリケーションが中断するときに、アプリケーションは StopAdvertising を呼び出す必要があります。

読み取り要求や書き込み要求への応答

上で見たように、必要な特性を宣言するときに、GattLocalCharacteristics では 3 種類のイベント ReadRequested、WriteRequested、SubscribedClientsChanged が発生します。

Read

リモート デバイスが特性から値を読み取る場合 (また、その値が定数値ではない場合)、ReadRequested イベントが呼び出されます。 読み取りが呼び出された特性が、引数 (リモート デバイスに関する情報を含む) と共に、デリゲートに渡されます。

characteristic.ReadRequested += Characteristic_ReadRequested;
// ... 

async void ReadCharacteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
{
    var deferral = args.GetDeferral();
    
    // Our familiar friend - DataWriter.
    var writer = new DataWriter();
    // populate writer w/ some data. 
    // ... 

    var request = await args.GetRequestAsync();
    request.RespondWithValue(writer.DetachBuffer());
    
    deferral.Complete();
}

Write

リモート デバイスが特性に値を書き込む場合、リモート デバイスに関する詳細、書き込み先の特性、書き込む値自体を使用して WriteRequested イベントが呼び出されます。

characteristic.ReadRequested += Characteristic_ReadRequested;
// ...

async void WriteCharacteristic_WriteRequested(GattLocalCharacteristic sender, GattWriteRequestedEventArgs args)
{
    var deferral = args.GetDeferral();
    
    var request = await args.GetRequestAsync();
    var reader = DataReader.FromBuffer(request.Value);
    // Parse data as necessary. 

    if (request.Option == GattWriteOption.WriteWithResponse)
    {
        request.Respond();
    }
    
    deferral.Complete();
}

書き込みには、応答がある書き込みと応答がない書き込みの 2 種類があります。 リモート デバイスが実行している書き込みの種類を特定するには、GattWriteOption (GattWriteRequest オブジェクトのプロパティ) を使用します。

受信登録しているクライアントへの通知の送信

最も一般的な GATT サーバーの操作である通知によって、データをリモート デバイスにプッシュする重要な機能を実行します。 受信登録しているすべてのクライアントに通知する場合もあれば、新しい値の送信先のデバイスを選択することが必要な場合もあります。

async void NotifyValue()
{
    var writer = new DataWriter();
    // Populate writer with data
    // ...
    
    await notifyCharacteristic.NotifyValueAsync(writer.DetachBuffer());
}

新しいデバイスが通知を受信登録する場合、SubscribedClientsChanged イベントが呼び出されます。

characteristic.SubscribedClientsChanged += SubscribedClientsChanged;
// ...

void _notifyCharacteristic_SubscribedClientsChanged(GattLocalCharacteristic sender, object args)
{
    List<GattSubscribedClient> clients = sender.SubscribedClients;
    // Diff the new list of clients from a previously saved one 
    // to get which device has subscribed for notifications. 

    // You can also just validate that the list of clients is expected for this app.  
}

注意

アプリケーションは、 MaxNotificationSize プロパティを使用して、特定のクライアントの最大通知サイズを取得できます。 最大サイズを超えるすべてのデータは、システムによって切り詰められます。

GattLocalCharacteristic.SubscribedClientsChanged イベントを処理する場合は、以下で説明するプロセスを使用して、現在サブスクライブされているクライアント デバイスに関する完全な情報を確認できます。