BLE GATT設計:UUID、データ形式、通知、バージョン互換性
BLE接続が成功しても、製品の統合が成功したとは限りません。アプリが別のキャラクタリスティックを検出したり、符号付き測定値を誤ってデコードしたり、ファームウェア更新後に通知を受信できなくなったりする場合があります。実用的なGATTインターフェースには、識別方法、バイト形式、配送の意味、安全性、バージョン変更を定めた文書が必要です。本稿では、独自のセンサー・制御インターフェースを例に、その取り決めを整理します。
アプリを実装する前にインターフェースを定義する
各サービスとキャラクタリスティックについて、UUID、プロパティ、アクセス権、最大長、エラー応答を記録します。Bluetooth SIGのサービスを再利用するのは、定義された意味と形式が製品に合う場合に限ります。独自の意味を持つデータには、未割り当ての短いUUIDを勝手に使わず、安定した128ビットUUIDを割り当てます。SIGのデータ型仕様では、短いサービスUUIDはSIGが割り当てた値に限定されています。
UUIDは、ファームウェア、アプリ、試験ツールで共有する、バージョン管理された台帳にまとめます。デバイス名だけで識別しないでください。名前は変更でき、複数台が同じ名前をアドバタイズすることもあります。同様に、属性ハンドルをファームウェアのバージョンをまたいで固定値として埋め込まないでください。サービスを探索して対象インスタンスを選び、そのサービス内でキャラクタリスティックを特定します。
| キャラクタリスティック | 推奨操作 | 文書化する取り決め |
|---|---|---|
| プロトコル情報 | 読み取り | 形式のメジャー/マイナーバージョン、対応機能、最大アプリケーションフレーム |
| 測定ストリーム | Notification | 単位、シーケンスカウンター、時刻基準、オーバーフロー方針 |
| 設定 | 読み取りと応答付き書き込み | 範囲、認可、検証、永続化のタイミング |
| 指令結果 | Indication、またはアプリケーション確認付きNotification | トランザクションID、結果コード、完了の意味 |
テスト可能なバイト形式を定める
以下は12バイトの測定フレーム例であり、Bluetoothの標準プロファイルではありません。複数バイトのフィールドはすべてリトルエンディアンです。温度を固定小数点で表すと、プログラミング言語ごとの浮動小数点シリアライズに依存せずに済みます。プロトコル情報を別途読み出し、マイナーバージョンと対応機能を取得します。
| オフセット | フィールド | 定義 |
|---|---|---|
| 0 | major | 符号なし8ビットの形式メジャーバージョン。初期値1 |
| 1 | flags | ビット0は温度有効。ほかは予約ビットで0 |
| 2–3 | sequence | 符号なし16ビットカウンター。65536で周回 |
| 4–7 | uptime_ms | 起動からのサンプリング時刻を符号なし32ビットで表す。2³²で周回 |
| 8–9 | temperature | 符号付き16ビット、1カウント0.01 °C。無効時は無視 |
| 10–11 | battery_mV | 符号なし16ビットのミリボルト値。65535は取得不可 |
この例では、シーケンス番号と稼働時間は再起動時にリセットされます。セッション開始時に読み出す起動識別子によって、再起動とカウンターの周回を区別します。負の温度、不正なフラグ、途中で切れたフレーム、カウンターの周回を、共通の基準テストベクトルに含めます。次のPythonデコーダーは、明示的な検証の例です。
import struct
def decode_v1(payload):
if len(payload) != 12:
raise ValueError("expected 12 bytes")
major, flags, seq, ms, temp, mv = struct.unpack("<BBHIhH", payload)
if major != 1 or flags & 0xFE:
raise ValueError("unsupported schema or flags")
return {
"sequence": seq, "uptime_ms": ms,
"temperature_C": temp / 100 if flags & 1 else None,
"battery_mV": None if mv == 65535 else mv,
}
基準ベクトルは01 01 2A 00 E8 03 00 00 2E FB E4 0Cです。メジャーバージョン1、温度有効、シーケンス番号42、稼働時間1000 ms、−12.34 °C、3300 mVを表します。実機を接続する前に、両側で正しくデコードできることを確認します。
予約ビットを厳密に検査するため、このデコーダーは新しいフラグの意味を暗黙に受け入れられません。v1フレームを維持するか、対応する拡張を協議するか、新しいメジャー形式を導入します。末尾へのバイト追加が後方互換になるのは、既存のパーサーが追加バイトを受け入れるよう明示的に設計されている場合だけです。

購読とアプリケーションへの確実な配送を区別する
NotificationにはATTの確認応答がなく、Indicationにはあります。ただし、どちらも測定値がクラウドデータベースに到達したことや、モーターが指令を完了したことを証明しません。BLEの接続リンクには独自の再送動作がありますが、切断、ソフトウェアキューの満杯、アプリ再起動には、エンドツーエンドの方針が必要です。欠落サンプルの検出にはシーケンス番号、指令結果の照合にはトランザクションIDを使います。基礎となる手順はATT仕様で定義されています。
プラットフォームAPIで購読し、完了を確認してからストリームを準備完了とします。GATT層のClient Characteristic Configuration Descriptor(UUID 0x2902)は、0x0001でNotification、0x0002でIndicationを有効にします。購読状態はクライアントごとに管理されます。ボンディング済みと未ボンディングの機器では永続化の規則が異なるため、再接続処理ではローカルのコールバックを復元し、購読が有効であることを確認する必要があります。GATT仕様を参照してください。
キューに上限を設定し、過負荷時の動作を文書化します。最も古いサンプルを破棄する、最新値だけ残す、製品上許されるなら収集を一時停止する、といった方針です。破棄サンプル数を公開します。指令は「受け付け済み」「実行中」「完了」を区別します。切断後に同じトランザクションを再試行しても、アクチュエーターが誤って2回動作しないようにします。
MTUとプラットフォームの違いを明示する
通常の単一ハンドルNotificationでは、値をATT_MTUから3バイト引いた長さに収める必要があり、属性値には別途512バイトの上限があります。したがって、デフォルトのLE ATT MTUが23の場合、このNotificationに使えるのは20バイトです。より大きな値には、合意したアプリケーション層の分割方式か、別の適切な転送手順が必要です。MTUを増やすだけでは、より高速なPHYは選択されず、リンク層データ長も増えません。ATTパケット形式を参照してください。
Android 14以降では、最初のGATTクライアントによるMTU要求時にスタックが517を要求し、その後の要求は無視されます。フレーム長は要求した数値ではなく、コールバックで得た実際のネゴシエーション結果に合わせます。AppleプラットフォームではAndroidの前提を流用せず、選択した書き込みタイプに対してmaximumWriteValueLength(for:)を問い合わせます。AndroidのBluetoothGattリファレンスとAppleの書き込み長リファレンスを確認してください。
採用したスタックが必要な並列実行を明示的にサポートしない限り、非同期の初期設定処理は順に実行します。呼び出し成功は、要求がキューに入っただけであることも多く、結果はコールバックで返されます。交渉済みMTU、選択したキャラクタリスティックUUID、セキュリティ状態、購読結果、パーサーバージョンをまとめて記録します。
ファームウェアとアプリのバージョン互換性を計画する
プロトコルのデータ形式バージョン、ファームウェアバージョン、GATTデータベースの構造は別の問題です。対応するデータ形式の範囲では、既存フィールドの意味を維持します。ファームウェアの文字列から機能を推測させず、対応機能を明示します。未対応のメジャーバージョンは、制御指令を送信する前に有用な診断情報とともに拒否します。
更新でサービスデータベースが変わる場合は、適用されるService Changedとキャッシュ処理を実装します。Database Hashが利用できる場合は、変更検出に使用できます。これらの仕組みは、変更されたアプリケーションデータ形式を変換するものではありません。更新とロールバックは、既存のボンディング済みクライアントと新規インストールの双方で試験します。データベース層の要件はGATTキャッシュ規則で説明されています。
不具合を顕在化させるリリース試験マトリクスを使う
| 試験 | 意図的に発生させる不具合 | 残す証拠 |
|---|---|---|
| 旧アプリと新ファームウェア、新アプリと旧ファームウェア | 未知のメジャーバージョン、任意フィールドの欠落、構造変更 | 対応機能の読み取りと明示的な受け入れ/拒否 |
| MTU 23およびそれより大きい交渉済みMTU | 過大なペイロード、不正な分割長、切り詰め | 受信長と基準ベクトルのデコード結果 |
| 指令および購読の途中で切断 | 二重動作またはストリーム停止 | トランザクションID、購読完了、最初の有効サンプル |
| ボンディング情報を保持した更新とロールバック | 古いハンドルキャッシュ、誤ったキャラクタリスティックへのアクセス | 探索/キャッシュの遷移とUUID対応 |
| 低速な処理側と満杯のキュー | メモリー増加、説明できない欠損 | キューの最大使用量と破棄数 |
| 未認証または未認可のクライアント | 不適切なセキュリティレベルで設定が受理される | 想定どおりのエラーと設定が変わっていない証拠 |
ストリームが無言のままなら、Notifyプロパティ、購読完了、権限、データ生成側の動作を順に確認します。値が不自然なら、無線パラメーターを調整する前に、生バイトを符号、倍率、エンディアンと照合します。更新済み機器だけで失敗する場合は、データベースキャッシュと形式の協議を最初に調べます。
レビュー可能な統合資料を用意する
ファームウェアレビューには、UUID台帳、パケット仕様、基準ベクトル、対応するスマートフォンとOSの一覧、互換性マトリクスを持ち込みます。Obeitaのファームウェア・BSP診断サービスが相談の入口になります。納入済みWS63無線モジュール統合事例はハードウェアと無線モードの範囲を定める背景を示すものであり、本稿のGATT仕様例がそのモジュールに実装・検証済みであることを示すものではありません。