Bluetooth GATTサーバー

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

Important

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

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

概要

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

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

サポートされるサービスを定義する

アプリは、Windowsによって発行される 1 つ以上のサービスを宣言できます。 各サービスは UUID によって一意に識別されます。

属性と UUID

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

Note

Windows API はすべて GUID という用語を使用しますが、Bluetooth標準では、これらを UUID として定義しています。 ここでは、これら 2 つの用語は交換可能であるため、UUID という用語を引き続き使用します。

属性が標準で、Bluetooth SIG で定義されている場合は、対応する 16 ビットの短い ID も持ちます (たとえば、バッテリ レベル UUID は 0000 2A19-0000-1000-8000-00805F9B34FB で、短い ID は0x2A19)。 これらの標準 UUID は 、GattServiceUuidsGattCharacteristicUuids で確認できます。

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

制限付きサービス

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

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

Caution

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

生成された属性

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

  1. クライアント特性構成 (特性が示可能または認識可能としてマークされている場合)。
  2. 特性ユーザーの説明 (UserDescription プロパティが設定されている場合)。 詳細については、GattLocalCharacteristicParameters.UserDescription プロパティを参照してください。
  3. 特性形式 (指定されたプレゼンテーション形式ごとに 1 つの記述子)。 詳細については、GattLocalCharacteristicParameters.PresentationFormats プロパティを参照してください。
  4. 特性集計形式 (複数のプレゼンテーション形式が指定されている場合)。 GattLocalCharacteristicParameters.See PresentationFormats プロパティで詳細を確認してください。
  5. 特性の拡張プロパティ(特性に拡張プロパティ ビットが設定されている場合)。

Note

拡張プロパティ記述子の値は、 ReliableWrites および WritableAuxiliaries 特性プロパティを使用して決定されます。

Caution

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

Caution

Broadcast 現時点ではサポートされていません。 GattCharacteristicProperty Broadcast を指定すると、例外が発生します。

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

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 に通知します。 IsDiscoverableIsConnectableの 2 つのプロパティを設定する必要があります。

GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
{
    IsDiscoverable = true,
    IsConnectable = true
};
serviceProvider.StartAdvertising(advParameters);
  • IsDiscoverable: 通知内のリモート デバイスにフレンドリ名をアドバタイズし、デバイスを検出できるようにします。
  • IsConnectable: 周辺機器の役割で使用するために、接続可能なアドバタイズをアドバタイズします。

サービスが Discoverable と Connectable の両方である場合、システムはサービス Uuid をアドバタイズ パケットに追加します。 アドバタイズ パケットには 31 バイトしか存在し、128 ビット UUID はそのうちの 16 バイトを占めます。

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

読み取りと書き込みの要求に応答する

必要な特性を宣言するときに前述したように、GattLocalCharacteristics には、 ReadRequestedWriteRequestedSubscribedClientsChangedの 3 種類のイベントがあります。

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();
}

書き込む

リモート デバイスが特性に値を書き込もうとすると、 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.  
}

Note

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

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