iOSでBLE(Bluetooth Low Energy)アプリを開発する際、避けて通れないのがGATT(Generic Attribute Profile)プロファイルの概念です。
この記事では、CoreBluetoothの実装で最も重要となる**「サービス」と「キャラクタリスティック」**の関係性を、初心者向けに図解を交えて解説します。
1. サービスとキャラクタリスティックの基本概念
BLE通信におけるデータ管理は、よく**「フォルダとファイル」**に例えられます。
[Peripheral (周辺機器 / デバイス)]
└── 📂 [Service (サービス / フォルダ)]
└── 📄 [Characteristic (キャラクタリスティック / ファイル)]
└── 💾 [Value (値 / 中身)]
📂 サービス(Service)とは?
サービスは、デバイスが持つ**「機能ごとのグループ(フォルダ)」**です。
1つのBLEデバイスの中に、用途に応じたサービスが複数存在します。
- 役割: 関連するデータをひとまとめにする
-
例: スマートウォッチの場合
- 「心拍計サービス」
- 「バッテリー残量サービス」
- 「デバイス情報サービス」
📄 キャラクタリスティック(Characteristic)とは?
キャラクタリスティックは、サービスの中にある**「具体的なデータ項目(ファイル)」**です。
実際の数値の読み書きや、通知の受け取りはこの単位で行います。
キャラクタリスティックは、さらに以下の3要素で構成されています。
- Declaration(宣言): この項目の性質(読み込み専用、書き込み可能などの「属性」)
-
Value(値): 実際のデータ(例:心拍数
72、バッテリー85%など) - Descriptor(記述子): 単位や設定情報などのメタデータ(省略可能)
2. 具体的な構成イメージ(心拍計の例)
iPhone(セントラル)が、心拍計(ペリフェラル)からデータを取得するときの階層構造を視覚化すると以下のようになります。
[ペリフェラル:スマートウォッチ]
│
└── 📂 サービス:心拍計 (UUID: 0x180D)
│
├── 📄 キャラクタリスティック:心拍数測定値 (UUID: 0x2A37)
│ └── [属性: Notify] ──> iPhoneに現在の心拍数を自動で連続通知
│
└── 📄 キャラクタリスティック:センサーの装着位置 (UUID: 0x2A38)
└── [属性: Read] ──> 「胸」「手首」などの位置を1回読み込み
3. UUID(識別ID)による管理
サービスとキャラクタリスティックは、すべて**UUID(一意の識別子)**というID番号で区別されます。UUIDには大きく分けて2種類あります。
| 種類 | 桁数 | 用途 |
|---|---|---|
| GATT標準UUID | 16ビット (4桁) | Bluetooth SIG(規格団体)が定めた世界共通のID。 (例)バッテリーサービス: 0x180F
|
| カスタムUUID | 128ビット (32桁) | 独自開発のデバイス(ESP32や自社製品など)で通信するために、開発者が自由に生成するID。 (例) 4A5Cxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
4. iOS(Swift / CoreBluetooth)での実装コード例
iOSアプリで特定のデータにアクセスする際は、以下のようにUUIDを指定して「狙い撃ち」で探索・通信を行います。
① UUIDの定義
まずは操作したいサービスとキャラクタリスティックのUUIDを CBUUID 型で定義します。
import CoreBluetooth
// サービスのUUID(例: 心拍計サービス)
let heartRateServiceUUID = CBUUID(string: "180D")
// キャラクタリスティックのUUID(例: 心拍数測定値)
let heartRateMeasurementCharacteristicUUID = CBUUID(string: "2A37")
② 指定したサービスだけを探索する
ペリフェラル(デバイス)に接続した直後、すべてのサービスを探索すると時間がかかるため、UUIDを指定して絞り込みます。
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
// 対象のサービスのみを指定して探索開始
peripheral.discoverServices([heartRateServiceUUID])
}
③ サービス内から特定のキャラクタリスティックを探索する
サービスが見つかったら、そのサービスに紐づくキャラクタリスティックを探索します。
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
if service.uuid == heartRateServiceUUID {
// 対象のキャラクタリスティックのみを指定して探索開始
peripheral.discoverCharacteristics([heartRateMeasurementCharacteristicUUID], for: service)
}
}
}
④ 発見したキャラクタリスティックを操作する
目的のキャラクタリスティックが見つかったら、そのプロパティ(属性)に応じてデータ処理を要求します。
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.uuid == heartRateMeasurementCharacteristicUUID {
// 例:Notify(値の自動通知)を有効にする場合
if characteristic.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: characteristic)
}
// 例:Read(値の読み込み)を行う場合
// if characteristic.properties.contains(.read) {
// peripheral.readValue(for: characteristic)
// }
}
}
}
まとめ
- サービスは機能ごとの「フォルダ」
- キャラクタリスティックはデータごとの「ファイル」
- すべてはUUIDによって管理され、iOSアプリからはピンポイントで指定して呼び出す