GATT(Generic Attribute Profile)はBluetooth Low Energy(BLE)のプロファイルであり、BLEデバイス間のデータ構造と情報交換のルールを定義します。GATTはAttribute Protocol(ATT)の上に構築され、データを階層的に整理します:サービス、特性、ディスクリプタ。Bluetooth SIG(2025)によると、GATTプロファイルはフィットネストラッカーからスマートロック、医療用センサーに至るまで、すべてのBLEアプリケーションの98%で使用されています。
重要ポイント
GATT(Generic Attribute Profile)は、2つのBLEデバイスがAttribute Protocol(ATT)を介してデータを交換する方法を定義するBluetooth Low Energy(BLE)プロファイルです。GATTはデータ構造を標準化します:すべてのデータは“サービス → 特性 → ディスクリプタ”の階層に整理されます。このプロファイルはBLEとともにBluetooth 4.0仕様(2010年)で導入され、それ以来、省エネBluetoothデバイスの主要なデータ転送メカニズムであり続けています。データがシリアルポート(SPP)を介して送信されるクラシックBluetoothとは異なり、GATTは読み取り、書き込み、通知操作を通じてデータへの構造化されたアクセスを提供します。
BLEスタックは複数のレイヤーで構成されます:物理層(無線)、リンク層(接続管理)、L2CAP(論理チャネル制御)、ATT(Attribute Protocol — 属性へのアクセス)、GATT(ATTベースのプロファイル)。GATTはアプリケーション開発者が扱う最上位のレイヤーです。下位のATTは基本的な操作を提供します:属性の読み取り、書き込み、通知、指示。GATTはセマンティクスを追加します:サービスと特性とは何か、それらがどのようにグループ化されるか、読み書き時にどのようなルールが適用されるかを定義します。Bluetooth Core Specification 5.4(2023年)によると、GATTは単一のデバイス上で最大65,535の属性(サービス+特性+ディスクリプタ)をサポートします。
GATT階層は3つのレベルで構成されます。サービスは1つのタスクを解決する特性の論理グループです(例:“バッテリーサービス”や“心拍数サービス”)。特性は既知の型を持つデータ単位です:現在のバッテリーレベル、センサー読み取り値、スイッチの状態。各特性には値と、メタデータを記述する1つ以上のディスクリプタがあります:測定単位、通知設定、値の範囲。UUID(Universally Unique Identifier)は各サービスと各特性を一意に識別します。
GATTは特性と対話するための4種類の操作をサポートします。Read — クライアントがサーバーから特性の現在の値を要求します。Write — クライアントがサーバーに新しい値を送信します。Notify — サーバーが確認なしでクライアントに値を送信します(より高速だが信頼性は低い)。Indicate — サーバーが受信確認付きで値を送信します(より信頼性が高いが低速)。開発者はシナリオに基づいて種類を選択します:心拍数センサーの読み取りにはNotifyで十分です;スマートロックの設定を書き込むには確認付きのWriteが必要です。
// AndroidコードにおけるGATTデータ構造
data class BleService(
val uuid: UUID,
val characteristics: List<BleCharacteristic>
)
data class BleCharacteristic(
val uuid: UUID,
val properties: Int, // READ, WRITE, NOTIFY, INDICATE
val descriptors: List<BleDescriptor>,
var value: ByteArray?
)
BLE仕様は2つの主要なプロファイルを定義します:GAP(Generic Access Profile)とGATT(Generic Attribute Profile)。GAPはデバイスの発見、接続確立、可視性管理を担当します — BLEの“ネットワーク層”です。GATTは接続確立後のデータ交換を担当します — “アプリケーション層”です。開発者はデバイスのスキャンと接続にGAPを使用し、接続されたデバイスからのデータの読み取り、書き込み、通知の受信にGATTを使用します。
| 特性 | GAP | GATT |
|---|---|---|
| 目的 | 発見と接続 | データ交換 |
| ロール | Central / Peripheral | Client / Server |
| プロトコル | HCI、リンク層 | ATT(Attribute Protocol) |
| フェーズ | 接続前 | 接続後 |
| メインクラス | BluetoothAdapter | BluetoothGatt |
GATTは2つのロールを定義します:GATTサーバーとGATTクライアント。サーバーはデータを提供するデバイスです(例:心拍数データを送信するフィットネストラッカー)。クライアントはデータを要求するデバイスです(例:データを読み取るスマートフォン)。ほとんどのシナリオでは、AndroidアプリがGATTクライアントとして機能し、BLEペリフェラルがGATTサーバーとして機能します。ただし、AndroidはGATTサーバーにもなれます — 例えば、アプリが他のデバイスに対してBLEデバイスをエミュレートする場合などです。ロールはGATT接続セットアップ段階で決定され、セッション中は変更されません。
GATT通信プロセスは、デバイス間のBLE接続が確立された後に始まります。最初に、GATTクライアントはService Discoveryを通じてGATTサーバー上のサービスを発見します — AndroidはBluetoothGatt.discoverServices()を介して接続時に自動的にこれを行います。発見後、クライアントは利用可能なサービス、特性、ディスクリプタのリストを取得します。その後、クライアントは特性値の読み取り(Read)、新しい値の書き込み(Write)、または通知への登録(Set Notify/Indicate)ができます。GATTサーバーは接続確立後いつでもクライアントにNotify/Indicateを送信できます。
デフォルトでは、BLEにおける単一のGATTパケット(MTU)のサイズは23バイトで、そのうち3バイトがATTヘッダー、20バイトがペイロードです。アプリケーションがより多くのデータを転送する必要がある場合(例:OTAファームウェアアップデート)、AndroidのrequestMtu()を介してMTUを517バイトまで増やすことができます。MTUの増加により、1つのデータブロックを転送するために必要なパケット数が減少します — 50個の小さなパケットの代わりに2個の大きなパケットを送信でき、消費電力が低下し転送速度が向上します。最大MTUはBLEのバージョンとチップの能力に依存します — Bluetooth 5.0は最大517バイト、古いバージョンは最大247バイトをサポートします。
// 特性通知を購読する
fun enableNotifications(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
gatt.setCharacteristicNotification(characteristic, true)
// CCCD(クライアント特性設定ディスクリプタ)を有効にする
val cccdUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
val descriptor = characteristic.getDescriptor(cccdUuid)
descriptor?.let {
it.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
gatt.writeDescriptor(it)
}
}
AndroidでGATTサーバーから通知を受信するには、BluetoothGattCallback.onCharacteristicChanged()コールバックを実装する必要があります。このメソッドで、サーバーがNotifyまたはIndicateを送信するたびに、アプリは更新された特性値を受け取ります。リアルタイムデータ処理には、バッファとKotlinコルーチンを使用します — これにより、大量の通知(例:毎秒100以上の心拍数データ)が発生している間もUIスレッドのブロックを防ぎます。Indicateの場合は、サーバー側でgatt.sendResponse()を呼び出す必要があります — Androidクライアントでは自動的に処理されます。
Androidでは、GATTの操作はandroid.bluetoothパッケージのクラスを通じて実装されます:BluetoothGatt(接続)、BluetoothGattService(サービス)、BluetoothGattCharacteristic(特性)、BluetoothGattDescriptor(ディスクリプタ)。BLEデバイスへの接続はBluetoothDevice.connectGatt()を呼び出すことから始まります — このメソッドはBluetoothGattを返し、これを介して後続のすべての操作が実行されます。すべてのGATTコールバックはBluetoothGattCallbackに届きます — これはBluetoothGattが作成されたのと同じスレッドで呼び出される非同期インターフェースです。重要:すべてのGATT操作は順次実行する必要があります — 同じBluetoothGattで複数の操作を同時に呼び出すとエラーが発生します。
GATT接続のライフサイクルを適切に管理することは、BLEアプリの安定性にとって非常に重要です。connectGatt()を呼び出した後、アプリはSTATE_CONNECTED状態のonConnectionStateChange()コールバックを待ちます。その後、システムは自動的にService Discoveryを開始し、完了後にonServicesDiscovered()が呼び出されます。この後でのみ、読み取り、書き込み、登録を実行できます。終了時は、必ずonDestroy()またはonPause()でgatt.close()を呼び出してください — 閉じられていないGATT接続はバッテリーを消費し、Androidで同じデバイスへの再接続をブロックする可能性があります。
// GATT経由でBLEデバイスに接続する
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt, status: Int, newState: Int
) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
val service = gatt.getService(UUID.fromString("180D"))
val characteristic = service?.getCharacteristic(
UUID.fromString("2A37")
)
characteristic?.let { enableNotifications(gatt, it) }
}
}
Bluetooth SIGは、固定の16ビットUUIDを持つ数十の標準GATTプロファイルとサービスを定義しています。最も一般的なもの:Battery Service(UUID 180F、Battery Level特性2A19)、Heart Rateサービス(UUID 180D、Heart Rate Measurement特性2A37)、Device Informationサービス(UUID 180A、Manufacturer NameとSerial Number特性)。標準サービスの使用は、異なるメーカーのデバイス間の互換性を保証します — Heart Rateサービスを持つ任意のフィットネスブレスレットは、それをサポートする任意のAndroidアプリで動作するはずです。
標準サービスがタスクをカバーしない場合、開発者は128ビットUUIDを持つカスタムGATTサービスを作成できます。カスタムサービスを設計する際には、次のことが必要です:特性の論理グループを定義し(例:“ロック制御サービス”)、各特性に正しいプロパティ(Read、Write、Notify)を割り当て、Characteristic Presentation Formatディスクリプタを通じて許可される値と測定単位を定義します。複雑なプロトコルには、Writeプロパティを持つCommand特性とNotifyを持つStatus特性を含めることを推奨します — これは産業用BLEアプリケーションで採用されているCommand/Statusパターンに従います。
// カスタムGATTサービスを作成する(GATTサーバーとしてのAndroid)
private fun createCustomService(): BluetoothGattService {
val serviceUuid = UUID.fromString("12345678-1234-5678-1234-56789abcdef0")
val service = BluetoothGattService(
serviceUuid, BluetoothGattService.SERVICE_TYPE_PRIMARY
)
val charUuid = UUID.fromString("12345678-1234-5678-1234-56789abcdef1")
val characteristic = BluetoothGattCharacteristic(
charUuid,
BluetoothGattCharacteristic.PROPERTY_READ or
BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ
)
service.addCharacteristic(characteristic)
return service
}
よくある質問
GATT(Generic Attribute Profile)は、デバイス間のデータ交換構造を定義するBluetooth Low Energyプロファイルです。情報へのアクセスを標準化するために必要です:すべてのBLEデバイスはデータをサービスと特性に整理し、任意のクライアントがセンサー値の読み取り、デバイスの制御、通知の受信を可能にします。
GAPはBLEデバイスの発見と接続(スキャン、アドバタイズ、接続セットアップ)を担当します。GATTは接続後のデータ交換(読み取り、書き込み、通知)を担当します。GAPは接続前に機能し、GATTは接続後に機能します。両方のプロファイルはBLEに必須ですが、異なる機能を果たします:GAPは“ネットワーク層”、GATTは“アプリケーション層”です。
標準サービスにはBattery Service(180F、バッテリーレベル)、Heart Rate(180D、心拍数)、Device Information(180A、デバイスデータ)が含まれます。各サービスには16ビットUUIDを持つ複数の特性が含まれています。開発者は特定のタスク(スマートロック制御やフィットネストラッカーデータ転送など)のために128ビットUUIDを持つカスタムサービスを作成できます。
Androidでは、GATTはBluetoothGatt(接続)、BluetoothGattService(サービス)、BluetoothGattCharacteristic(特性)、BluetoothGattDescriptor(ディスクリプタ)を通じて実装されます。接続はconnectGatt()を介して行われ、その後BluetoothGattCallbackを通じてイベントが届きます:onConnectionStateChange、onServicesDiscovered、onCharacteristicChanged(通知用)。すべてのGATT操作は順次実行する必要があります。
UUID(Universally Unique Identifier)は、GATT内のサービスまたは特性を一意に識別する16ビットまたは128ビットの識別子です。標準のBluetooth SIGサービスは16ビットUUIDを使用します(例:Heart Rateの場合は180D)。開発者のカスタムサービスは128ビットUUIDを使用します(例:12345678-1234-5678-1234-56789abcdef0)。UUIDにより、クライアントはGATTサーバー上で必要なデータを見つけることができます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。