GATT サーバーのBluetooth

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

重要

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

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

重要な API

概要

Windows は通常、クライアント ロールで動作します。 ただし、多くのシナリオでは、Windows を Bluetooth LE GATT Server として機能させる必要があります。 ほとんどのクロスプラットフォーム 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 は、 GattServiceUuids および GattCharacteristicUuids で確認できます。

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

制限付きサービス

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

  1. Device Information Service (DIS)
  2. 汎用属性プロファイル サービス (GATT)
  3. 汎用アクセス プロファイル サービス (GAP)
  4. Scan Parameters Service (SCP)

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

生成された属性

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

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

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

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

現時点では、ブロードキャストはサポートされていないことに注意してください。 Broadcast 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: ペリフェラルの役割で使うための接続可能な公開通知をアドバタイズします。

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

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

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

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

読み込み

リモート デバイスが特性から値を読み取ろうとすると (定数値ではありません)、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 イベントを処理する場合は、以下に示すプロセスを使用して、現在サブスクライブされているクライアント デバイスに関する完全な情報を確認できます。