Descriptor はBLEキャラクタリスティックの補助属性であり、その値に関するメタデータや設定を提供します。Bluetooth Core Specification 5.4はデスクリプタをGATTプロファイルの不可欠な部分として定義しています:測定単位、値の範囲、テキスト名を記述し、通知の購読を管理します。最もよく知られているデスクリプタはClient Characteristic Configuration Descriptor(CCCD、0x2902)で、Centralがキャラクタリスティックからの通知を購読できるようにします。Bluetooth SIGによると、8つの標準デスクリプタが規定されており、追加のメタデータのために開発者がカスタムデスクリプタを作成することもできます。
重要ポイント
Descriptor はGATTプロトコルの属性で、Characteristicに付随し、その値や使用方法に関する追加情報を提供します。GATTの用語では、キャラクタリスティックがデータであり、デスクリプタはそのデータのメタデータです。
各Characteristicは0個以上のデスクリプタを持つことができます。デスクリプタはGATTテーブル内で個別の属性として定義され、Bluetooth SIG空間に独自の16ビットUUIDを持ちます。デスクリプタは独立して存在できず、常に特定のキャラクタリスティックに紐付けられています。
Bluetooth Core Specification 5.4(2023)によると、デスクリプタは標準(Bluetooth SIGが定義)とカスタム(開発者が定義)の2つに分類されます。標準デスクリプタは0x2900~0x2908の範囲のUUIDを持ち、厳密に定義された機能を実行します。カスタムデスクリプタはカスタムサービスやキャラクタリスティックと同様に128ビットUUIDを使用します。
デスクリプタはBLE接続の動作を設定するための重要なメカニズムです。CCCD(0x2902)なしでは通知を購読できず、Presentation Format(0x2904)なしではCentralがキャラクタリスティックの生のバイトを解釈できません。
CCCD(UUID 0x2902)はBLEで最も重要かつ広く使用されているデスクリプタです。キャラクタリスティックからのNotifyおよびIndicateに対するCentralの購読を管理します。CCCDに書き込まなければ、Notifyプロパティがあってもキャラクタリスティックは通知を送信しません。
CCCDは16ビットの値で、最下位ビット(0x0001)がNotifyを有効にし、2番目のビット(0x0002)がIndicateを有効にします。CentralはCCCDに目的の値を書き込むことで通知を購読します。Centralが0x0000を書き込むと購読は解除されます。
CCCDの仕組みは次のとおりです:CentralがNotifyをサポートするキャラクタリスティックを検出すると、そのCCCDデスクリプタを見つけて0x0001を書き込みます。その後、Peripheralがキャラクタリスティックの値を更新するたびに、Handle Value Notificationを介して購読中のCentralに自動的に送信されます(Centralからの要求なしで)。
Apple Core Bluetooth Programming Guideによると、キャラクタリスティックに対してsetNotifyValueを呼び出すと、CCCDはシステムによって自動的に管理されます。開発者が手動でCCCDを探して書き込む必要はありません。Core Bluetoothが通知の有効化時に自動的に実行します。
iOSでは、CBPeripheralオブジェクトのsetNotifyValueメソッドで購読します。
import CoreBluetooth
// キャラクタリスティック通知を購読
peripheral.setNotifyValue(true, for: characteristic)
// デリゲートが更新値を受信
func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?
) {
// characteristic.valueに新しいデータがあります
guard let data = characteristic.value else { return }
}
Androidでは、BluetoothGatt.setCharacteristicNotificationを使用して通知を購読します。開発者は手動でCCCDに値0x0001を書き込む必要もあります。
import android.bluetooth.*;
// 通知を有効にする
bluetoothGatt.setCharacteristicNotification(characteristic, true);
// CCCDに0x0001を書き込む
BluetoothGattDescriptor descriptor = characteristic
.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
Bluetooth SIGは一般的なキャラクタリスティック設定タスクのために8つの標準デスクリプタを定義しています。各デスクリプタには厳密に定義されたUUIDと値の形式があります。
| UUID | デスクリプタ名 | 目的 | 値の型 |
|---|---|---|---|
| 0x2900 | Characteristic Extended Properties | キャラクタリスティックの追加プロパティ | uint16(ビットマスク) |
| 0x2901 | Characteristic User Description | キャラクタリスティックのテキスト名 | UTF-8文字列 |
| 0x2902 | Client Characteristic Configuration | 通知管理 | uint16 |
| 0x2903 | Server Characteristic Configuration | ブロードキャスト用サーバー設定 | uint16 |
| 0x2904 | Characteristic Presentation Format | データ表示形式 | 構造体(7バイト) |
| 0x2905 | Characteristic Aggregate Format | 複数キャラクタリスティックの結合 | UUIDリスト |
| 0x2906 | Valid Range | 有効値の範囲 | uint16 × 2(最小、最大) |
| 0x2907 | External Report Reference | 外部レポート参照 | uint16 |
| 0x2908 | Report Reference | 内部レポート参照 | uint8 + uint16 |
Characteristic User Description(0x2901)はUIに特に便利です:アプリケーションのインターフェースに表示できるキャラクタリスティックの人間が読める名前を含みます。例えば、Battery Levelキャラクタリスティックには「バッテリー充電レベル」という値のUser Descriptionデスクリプタがあります。
Characteristic Presentation Format(0x2904)はキャラクタリスティック値の解釈方法に関する情報を含みます:形式(uint8、uint16、float)、指数、測定単位(Bluetooth SIGレジストリのUUID経由)、名前空間、説明。例えば、温度は指数-2(分度値0.01°C)、測定単位「摂氏」のint16になります。
デスクリプタへのアクセスはiOSとAndroidで異なります。Core Bluetoothは通知が有効になると自動的にCCCDを管理しますが、Androidではデスクリプタに手動で値を書き込む必要があります。両方のアプローチを見てみましょう。
iOSでは、開発者は通常デスクリプタと直接やりとりしません。setNotifyValueを呼び出すと、Core Bluetoothが自動的にCCCDを見つけて必要な値を書き込みます。デスクリプタの読み取りにはdiscoverDescriptorsメソッドを使用します。
// デスクリプタを検出
peripheral.discoverDescriptors(for: characteristic)
// User Description値を読み取る
func peripheral(
_ peripheral: CBPeripheral,
didDiscoverDescriptorsFor characteristic: CBCharacteristic,
error: Error?
) {
for descriptor in characteristic.descriptors ?? [] {
if descriptor.uuid == CBUUID("2901") {
peripheral.readValue(for: descriptor)
}
}
}
Androidでは、開発者はキャラクタリスティックのCCCDデスクリプタを見つけて、通知を有効にするために値を書き込む必要があります。これはiOSと比較して追加の手順です。
// User Descriptionデスクリプタ値を読み取る
BluetoothGattDescriptor userDesc = characteristic
.getDescriptor(
UUID.fromString("00002901-0000-1000-8000-00805F9B34FB")
);
if (userDesc != null) {
bluetoothGatt.readDescriptor(userDesc);
}
カスタムデスクリプタは、Bluetooth SIGの標準デスクリプタで十分でない場合に使用します。例えば、医療用センサーの製造元がセンサーのキャリブレーション情報を含むデスクリプタやエラーログのデスクリプタを追加する場合があります。
カスタムデスクリプタはカスタムキャラクタリスティックと同様に128ビットUUIDで作成します。Peripheral側では、開発者が適切なAPIを介してキャラクタリスティックに追加します。Central側では、discoverDescriptorsを呼び出すと自動的に検出されます。
カスタムデスクリプタを作成する際は、値の長さが期待されるデータ型と一致し、アクセス権(読み取り/書き込み)が明示的に指定されている必要があります。カスタム目的でBluetooth SIG標準デスクリプタの範囲(0x2900~0x2908)のUUIDを使用しないでください。
キャラクタリスティックとデスクリプタはGATT属性の異なるレベルです。キャラクタリスティックはデータポイントであり、デスクリプタはそのメタデータまたは設定です。その違いは、目的、UUID、アクセスルールのいくつかの側面に現れます。
キャラクタリスティックにはGATTテーブルに常に存在する必須の宣言があります。デスクリプタはオプションであり、キャラクタリスティックにデスクリプタがない場合もあります。キャラクタリスティックは独立して読み取り、書き込み、通知の送信ができます。デスクリプタは常にキャラクタリスティックに紐付けられ、単独では存在しません。
Bluetooth Core Specification 5.4によると、GATTテーブルの属性の順序は次のとおりです:サービス宣言、キャラクタリスティック宣言、キャラクタリスティック値、キャラクタリスティックデスクリプタ(ある場合)、次のサービス。この順序はGATTクライアントの正しい動作のために必須です。
よくある質問
CCCDは購読管理に必要です:Notifyプロパティはキャラクタリスティックが通知を送信できることを示すだけで、有効にはしません。CentralはCCCDに0x0001を書き込んで明示的に購読する必要があります。これにより、準備ができていないCentralへの不要なデータ送信を防ぎます。
仕様はデスクリプタの数を制限していません。実際には、各キャラクタリスティックは通常1~3個のデスクリプタを持ちます:CCCD(Notify/Indicateを持つキャラクタリスティック用)、オプションでUser DescriptionとPresentation Formatです。デスクリプタが多すぎるとGATTテーブルのサイズが増加します。
ほとんどの場合、必要ありません。iOSとAndroidのCore Bluetoothは、NotifyまたはIndicateプロパティを持つキャラクタリスティックに自動的にCCCDを追加します。カスタムデスクリプタは特定のメタデータのためにのみ手動で作成します。
いいえ、CCCDには厳密に定義された形式(uint16)と目的(通知管理)があります。他の値を書き込むとBluetooth仕様に違反し、購読が機能しなくなる可能性があります。カスタムデータには128ビットUUIDのカスタムデスクリプタを使用してください。
User Descriptionにはキャラクタリスティックの人間が読める名前が含まれます。例えば、UUID 0x2A6Eの代わりに「デバイスの温度」と表示できます。アプリケーションはこの名前をインターフェースに表示することで、ハードコードされた文字列なしでBLEデバイスとのやりとりをユーザーにとって理解しやすくします。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。