近距離無線

GATT

BLEのデータ構造・通信ルール。

概要

GATT(Generic Attribute Profile)は、BLE(Bluetooth Low Energy)デバイス間でデータをどのように構造化し、読み書き・通知を行うかを定めたプロファイルです。BLEアプリケーション層のデータモデルの根幹を担い、Peripheral(センサーなど)が持つデータをCentral(スマートフォンなど)が取得・操作する際の共通言語として機能します。

GATTはATT(Attribute Protocol)の上に構築されており、ATTが定義する「属性(Attribute)」をサービス・キャラクタリスティック・デスクリプタという階層構造で整理します。この階層構造はBluetooth SIGが標準化しており、標準サービスのUUIDが割り当てられているため、異なるメーカーのデバイス間でもデータの意味を共通理解できます。

歴史・背景

GATTはBluetooth 4.0(2010年)でBLEとともに策定されました。それ以前のBluetooth ClassicにもSDP(Service Discovery Protocol)がありましたが、GATTはより簡潔でリソース制約の大きいセンサーデバイス向けに再設計されました。

Bluetooth SIGはSIG標準プロファイルを継続的に追加しており、2024年時点で心拍数・血圧・血糖値・温度・電池残量・位置情報など数十種類の標準サービスが定義されています。独自のカスタムサービスはランダム生成した128bit UUIDで定義できます。

技術仕様

GATTの階層構造

GATT Server(Peripheral)
└── Service(サービス)  ← UUID で識別
    ├── Characteristic(キャラクタリスティック)  ← UUID + Properties + Value
    │   ├── Value(データ本体)
    │   └── Descriptor(デスクリプタ)  ← メタデータ
    └── Characteristic(別のキャラクタリスティック)
        ├── Value
        └── Descriptor

Service(サービス)

サービスは関連するキャラクタリスティックのグループです。16bitの標準UUIDまたは128bitのカスタムUUIDで識別されます。

主な標準サービスUUID(16bit)の例:

サービス名UUID
Generic Access0x1800
Generic Attribute0x1801
Battery Service0x180F
Heart Rate0x180D
Health Thermometer0x1809
Blood Pressure0x1810
Device Information0x180A
Environmental Sensing0x181A

Characteristic(キャラクタリスティック)

キャラクタリスティックは実際のデータを保持する単位です。以下の属性を持ちます:

  • UUID: データの種類を示す識別子
  • Value: 実際のデータ(最大512バイト、ATT MTU に依存)
  • Properties: Read / Write / Notify / Indicate などの操作権限

主なProperties:

Property説明
ReadCentralがデータを読み取れる
WriteCentralがデータを書き込める(応答あり)
Write Without ResponseCentralが応答なしで書き込める(高速)
NotifyPeripheralが変化時にCentralへ通知(応答不要)
IndicatePeripheralが変化時にCentralへ通知(応答あり)
Broadcastアドバタイズパケットに含めてブロードキャスト

Descriptor(デスクリプタ)

デスクリプタはキャラクタリスティックに付属するメタデータです。最も重要なのはCCCD(Client Characteristic Configuration Descriptor、UUID: 0x2902)で、CentralがNotify/Indicateを有効化する際に書き込みます。

ATT MTU(Maximum Transmission Unit)

ATT MTUはATT層の最大パケットサイズです:

  • デフォルト: 23バイト(実効データは20バイト)
  • Bluetooth 4.2以降: 最大247バイト(実効データ244バイト)まで交渉可能(MTU Exchange)
  • Bluetooth 5.x: LE 2M PHYと組み合わせると高スループット

大きなデータを送る場合はMTUを拡大するか、アプリケーション層でパケット分割(Chunking)が必要です。

ATT プロトコル操作

Central(Client)              Peripheral(Server)
      |                                |
      |--- ATT_READ_REQ(UUID)-------->|  ← 読み取り要求
      |<-- ATT_READ_RSP(Value)--------|  ← 応答
      |                                |
      |--- ATT_WRITE_REQ(Value)------>|  ← 書き込み要求
      |<-- ATT_WRITE_RSP(確認)--------|  ← 応答
      |                                |
      |<-- ATT_HANDLE_VALUE_NTF--------|  ← Notify(応答なし)
      |                                |
      |<-- ATT_HANDLE_VALUE_IND--------|  ← Indicate(応答あり)
      |--- ATT_HANDLE_VALUE_CNF------->|  ← Indicate確認

動作原理

サービス探索(Service Discovery)

Central接続後、GATTサービス・キャラクタリスティックの構造を探索します:

  1. Primary Service Discovery: サービス一覧を取得(ATT_READ_BY_GROUP_TYPE_REQ)
  2. Characteristic Discovery: 各サービスのキャラクタリスティック一覧を取得
  3. Descriptor Discovery: 各キャラクタリスティックのデスクリプタを取得

Notify の有効化手順

1. Centralがサービス・キャラクタリスティックを探索
2. Notify対象キャラクタリスティックのCCCD(0x2902)に0x0001を書き込む
3. PeripheralはCCCDに0x0001が書かれたことを確認しNotifyを有効化
4. データ変化時にPeripheralが自動的にNotify送信
5. 切断するとCCCDは0x0000にリセット

Handle(ハンドル)

ATT属性はすべて16bitのHandle番号で識別されます。サービス発見の際にHandleが割り当てられ、読み書きはUUIDではなくHandleで行われます(UUIDでの検索後にHandleを使い回す形が一般的)。

用途・ユースケース

標準プロファイルの利用

標準GATTプロファイルを使うことで、専用アプリなしでもOS標準機能で認識されます:

  • Battery Service(0x180F): スマートフォンが自動的に電池残量を表示
  • Heart Rate Profile: Androidヘルスアプリが心拍データを自動取得
  • HID over GATT(HOGP): キーボード・マウスとしてOS標準ドライバで動作

カスタムサービスの設計

独自センサーデータを送る場合は128bit UUIDのカスタムサービスを定義します:

# Python(bleak)でBLEデバイスからデータ読み取り
import asyncio
from bleak import BleakClient

TARGET_MAC = "XX:XX:XX:XX:XX:XX"
SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
CHAR_UUID    = "beb5483e-36e1-4688-b7f5-ea07361b26a8"

async def main():
    async with BleakClient(TARGET_MAC) as client:
        # サービス一覧の表示
        for service in client.services:
            print(f"Service: {service.uuid}")
            for char in service.characteristics:
                print(f"  Char: {char.uuid}, Props: {char.properties}")

        # データ読み取り
        value = await client.read_gatt_char(CHAR_UUID)
        print(f"Value: {value}")

        # Notify購読
        def notification_handler(sender, data):
            print(f"Notify from {sender}: {data}")

        await client.start_notify(CHAR_UUID, notification_handler)
        await asyncio.sleep(10.0)
        await client.stop_notify(CHAR_UUID)

asyncio.run(main())

実装・開発のポイント

ESP32でGATTサーバーを実装する例

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

// カスタムサービス・キャラクタリスティックUUID
#define SERVICE_UUID           "12345678-1234-1234-1234-123456789012"
#define CHAR_TEMPERATURE_UUID  "12345678-1234-1234-1234-123456789013"
#define CHAR_HUMIDITY_UUID     "12345678-1234-1234-1234-123456789014"
#define CHAR_CONTROL_UUID      "12345678-1234-1234-1234-123456789015"

BLECharacteristic* pTempChar;
BLECharacteristic* pHumidChar;
BLECharacteristic* pCtrlChar;

// 書き込みコールバック
class ControlCallback : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic* pCharacteristic) override {
        String value = pCharacteristic->getValue().c_str();
        if (value == "ON") {
            // 制御処理
        }
    }
};

void setup() {
    BLEDevice::init("SensorNode");
    BLEServer* pServer = BLEDevice::createServer();
    BLEService* pService = pServer->createService(SERVICE_UUID);

    // 温度キャラクタリスティック(Read + Notify)
    pTempChar = pService->createCharacteristic(
        CHAR_TEMPERATURE_UUID,
        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
    );
    pTempChar->addDescriptor(new BLE2902());  // CCCDを追加

    // 湿度キャラクタリスティック(Read + Notify)
    pHumidChar = pService->createCharacteristic(
        CHAR_HUMIDITY_UUID,
        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
    );
    pHumidChar->addDescriptor(new BLE2902());

    // 制御キャラクタリスティック(Write)
    pCtrlChar = pService->createCharacteristic(
        CHAR_CONTROL_UUID,
        BLECharacteristic::PROPERTY_WRITE
    );
    pCtrlChar->setCallbacks(new ControlCallback());

    pService->start();
    BLEDevice::getAdvertising()->start();
}

void loop() {
    float temp = 25.4f;
    float humid = 60.2f;

    // float を バイト列に変換してセット
    pTempChar->setValue(reinterpret_cast<uint8_t*>(&temp), sizeof(temp));
    pTempChar->notify();

    pHumidChar->setValue(reinterpret_cast<uint8_t*>(&humid), sizeof(humid));
    pHumidChar->notify();

    delay(5000);
}

MTU拡張でスループット向上

// ESP32: MTUを517バイトに要求(接続後)
BLEDevice::setMTU(517);

Bluetooth SIGの標準UUIDを使う利点

  • スマートフォンのOSが自動的にサービスを認識する場合がある
  • nRF Connect等の汎用BLEツールで即座に値の意味が分かる
  • Apple Health / Google Fit 等のプラットフォームと連携しやすい

他技術との比較

GATT vs カスタムプロトコル(Notify Raw Data)

GATTを使わずATT上で独自プロトコルを構築することも可能ですが、標準ツールでのデバッグ・スマートフォンアプリ開発のコスト面でGATT利用が推奨されます。

GATT vs CoAP / MQTT

大規模IoTではゲートウェイを経由してMQTTCoAPへブリッジすることが一般的です。BLE GATTは端末-スマートフォン間、MQTTはクラウドとの通信と役割を分担します。

ATT MTUサイズとスループットの関係

ATT MTU = 23バイト(デフォルト)の場合、実効データは20バイト/パケット。1秒間に7.5パケット交換とすると実効スループットは約150 bytes/secに留まります。MTU = 247バイトに拡大すると実効データ244バイト/パケット、2M PHYと合わせると数十kbps〜数百kbpsが実現できます。

関連用語

参考リンク