ESP-IDFでBLE(Bluetooth Low Energy)のキーボードを開発するにあたり、BLEやHOGP (HID Over GATT Profile)について調べました。
ちなみに、ESP-IDFでBLEを使うために、ここではnimBLEライブラリを使います。
個人で使う想定です。商用で販売される場合は、Bluetoothの技術を使った時点で、ロゴの掲載の有無にかかわらず、Bluetooth認証が必要ですので、専門家にご相談下さい。(1製品毎に、Declaration IDの料金が100万円ぐらいするので、個人では販売は難しいですね。)
認証は、技適とは別の話です。秋月で売っているESP32であれば、技適は問題にはなりませんが、最終製品としての登録でBluetooth認証が必要になってきます。
- BLEとは?
- アドバタイズとは?
- GATTとは?
- UUID(Universally Unique Identifier)について
- HOGP(HID Over GATT Profile)とは?
- HIDサービスのキャラクタリスティックについて
- Batteryサービスのキャラクタリスティックについて
- Device Informationサービスのキャラクタリスティックについて
- SYSTEM ID キャラクタリスティック(UUID:0x2A23)
- MODEL NUMBER キャラクタリスティック(UUID:0x2A24)
- SERIAL NUMBER キャラクタリスティック(UUID:0x2A25)
- FIRMWARE REVISION キャラクタリスティック(UUID:0x2A26)
- HARDWARE REVISION キャラクタリスティック(UUID:0x2A27)
- SOFTWARE REVISION キャラクタリスティック(UUID:0x2A28)
- MANUFACTURER NAME キャラクタリスティック(UUID:0x2A29)
- PnP INFO キャラクタリスティック(UUID:0x2A50)
- GATTにおけるPrimary ServiceとSecondary Serviceの違い
- 最後に
BLEとは?
従来からあるBluetoothよりも省エネで通信することができ、スマホやパソコンと親和性が高い規格です。
キーボードやマウスなどのBLEデバイスは、「ペリフェラル」と呼ばれ
スマホやパソコンなどは、「セントラル」と呼ばれます。
通信の流れは、BLEデバイスが周囲に自分の存在を一定間隔で発信して接続待ちであることを知らせ(アドバタイズ)、セントラルがデバイスを見つけて接続要求を出すことで、接続が行われ、接続が確立されると1対1のGATT通信を行う流れになります。
アドバタイズとは?
アドバタイズ(Advertising)は、BLEデバイスが自身の存在や提供するサービスを周囲に通知するための不特定多数に向けた通信(ブロードキャスト)です。
つまり、僕はここにいるよ!と叫んでいる状態です。
GATTとは?
1対1でデータをやり取りするためのプロトコルがGATT(Generic Attribute Profile)となります。
どのBLEデバイスもこのGATTでデータをやり取りします。
通信の仕方は、クライアントサーバーモデルに基づいて行います。
スマホやコンピュータなどの(セントラル)がGATTクライアントとして、BLEデバイス(マウスやキーボードなどのペリフェラル)がGATTサーバーとして動作しています。
スマホなどのクライアントは、キーボードのサーバーにアクセスしてデータを読み取ったり、書き込んだりして通信します。
GATTは、サービス、キャラクタリスティック、ディスクリプタで構成されます。
サービス(Services)
サービスは、BLEデバイスが提供する機能やサービスを表します。
キーボードであれば、HID(Human Interface Device)サービスを使用して、HIDデバイスであることが分かります。キーボードなどのよくある汎用的なサービスについては、BLEの規格で予め用意されており、OSがサポートしていれば、スマホなどで専用のソフト無しで使うことができます。しかし、温度計やスマートロックなどの規格にないサービスを作る場合は、独自でサービスを設計して専用のアプリを作る必要があります。
キャラクタリスティック(Characteristics)
キャラクタリスティックは、サービスの中にある要素で、この要素を使いデータのやり取りを行います。サービスの中に複数できます。キャラクタリスティックには、読み取り、書き込み、通知の属性があります。
キーボードで使うHIDサービスでは、読み取りのキャラクタリスティックに対して、読みだすと、現在押されているキーの情報を知ることができるといった具合です。書き込みであれば、NumLockのLEDを光らせるなどです。
ディスクリプタ(Descriptors)
ディスクリプタは、キャラクタリスティックの中にある要素で、キャラクタリスティックに関連する追加情報を提供します。
UUID(Universally Unique Identifier)について
BLEで通信するというのは、サービスの中にあるキャラクタリスティックに対して読み書きを行うことになります。この時、サービス、キャラクタリスティック、ディスクリプタを識別する際に用いられる数字(識別子)がUUIDになります。
UUIDは16ビットまたは128ビットの2種類があり、16ビットのUUIDは、Bluetooth SIG(Bluetooth Special Interest Group)によって管理されており、よく使われるサービスが色々登録されています。
サービス名 | UUID |
---|---|
Generic Access Profile (GAP) Service | 0x1800 |
Generic Attribute Profile (GATT) Service | 0x1801 |
Device Information Service (DIS) | 0x180A |
Battery Service | 0x180F |
Heart Rate Service | 0x180D |
Health Thermometer Service | 0x1809 |
Environmental Sensing Service | 0x181A |
Human Interface Device (HID) Service | 0x1812 |
Cycling Speed and Cadence Service | 0x1816 |
Running Speed and Cadence Service | 0x1814 |
16ビットのUUIDは、正確には、128ビットのUUIDを短縮していると言う表現が正しく、上記の16ビットのUUIDは、0000XXXX-0000-1000-8000-00805f9b34fbの形で表せる128ビットのUUIDのXXXXの部分になっています。つまり、短縮表現が認められていると言うことです。
自分でオリジナルのサービスを作りたい場合は、短縮ができない128ビットのUUIDを使うことになります。UUIDは、Ubuntuであれば、ターミナルで
uuidgen
と入力すると得ることができます。
Webサービスでも得ることができます。
HOGP(HID Over GATT Profile)とは?
HOGPは、HID (Human Interface Device)をGATT (Generic Attribute Profile)を使用してBLEデバイスに実装するためのプロファイルです。つまり、キーボードやマウス、ジョイスティックなどHID用のプロファイルです。
HOGPにはメインとして3つのサービスがあります。
HID (Human Interface Device) サービス (UUID: 0x1812)
キーボードやマウスなどのHIDデバイスとの通信で使われます。
Battery サービス (UUID: 0x180F)
デバイスの電池残量が分かります。
Device Information (デバイス情報) サービス (UUID: 0x180A)
BLEデバイスの製造元、モデル番号、ファームウェアバージョンなどのデバイス情報を教えてくれます。
HIDサービスのキャラクタリスティックについて
HIDサービスに含まれるキャラクタリスティックは以下のものがあります。
HID Infomation (HID情報) キャラクタリスティック (UUID: 0x2A4A)
HID情報キャラクタリスティックは、HIDデバイスの仕様に関する情報を提供します。基本的に以下の4バイトになります。
// HIDの最新バージョン 1.11
#define HID_USB_VERSION 0x0111
uint8_t hid_info[] = {
(HID_USB_VERSION & 0xFF), // bcdHID:BCD表現のHIDバージョン
((HID_USB_VERSION >> 8) & 0xFF),
0x00, // bCountryCode:地域固有デバイスのための国識別番号。必要がなければ0
HID_FLAGS_REMOTE_WAKE // Flags
};
Report Map (レポートマップ) キャラクタリスティック (UUID: 0x2A4B)
レポートマップキャラクタリスティックは、HIDデバイスのレポートに関する情報を提供します。つまり、レポートキャラクタリスティックのビットの並びは何を示しているのかを説明しているわけです。ちなみに、InputレポートとOutputレポートの2種類があり、レポートマップではこの2つが混在して書かれています。
例えば、Inputレポートのキャラクタリスティックは、修飾キー1バイトと、予約1バイト、キーコード6バイト(キー6個分)で構成されているよ。と書いてあるイメージです。修飾キーとは、シフトキーやコントロールキーなどのことです。つまり、「シフトキーを押しながらaキーが押されたよ」と伝えるイメージになります。
Outputレポートのキャラクタリスティックは、5ビット分のLED情報で構成されているよ。と書かれているイメージです。CapsLockやNumLock、ScreenLockなどのLED表示のための情報がホスト側から伝えられます。
レポートマップはざっくり以下の情報が書かれています。
- レポートサイズとフォーマット:各レポートのサイズ(バイト数)とそのフォーマットが記述されます。
- レポートの種類と用途:レポートが入力レポートや出力レポートであるか、さらに具体的な用途(例:キーボードの押されたキー、マウスのカーソル移動など)が記述されます。
- レポートのデータ:レポートに含まれるデータの構造が記述されます。例えば、キーボードの入力レポートには各キーの状態(押されているかどうか)が含まれます。
今回作ったキーボードのレポートマップの例になります。
const uint8_t hid_report_map[] = {
// KEYBOARD REPORT
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection: (Application)
0x85, 0x01, // Report Id (1)
// Input Byte 0: 修飾キー Modifier
0x75, 0x01, // Report Size (1) 1bit を 8 回
0x95, 0x08, // Report Count (8)
0x05, 0x07, // Usage Pg (Key Codes)
0x15, 0x00, // Log Min (0)
0x25, 0x01, // Log Max (1)
0x19, 0xE0, // Usage Min (224)
0x29, 0xE7, // Usage Max (231)
0x81, 0x02, // Input: (Data, Variable, Absolute)
// Key arrays (7 bytes)
0x75, 0x08, // Report Size (8)
0x95, 0x07, // Report Count (7)
0x15, 0x00, // Log Min (0)
0x25, 0x65, // Log Max (101)
0x05, 0x07, // Usage Pg (Key Codes)
0x19, 0x00, // Usage Min (0)
0x29, 0x65, // Usage Max (101)
0x81, 0x00, // Input: (Data, Array)
// LED report
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Pg (LEDs)
0x19, 0x01, // Usage Min (1)
0x29, 0x05, // Usage Max (5)
0x91, 0x02, // Output: (Data, Variable, Absolute)
// LED report padding
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x03, // Output: (Constant)
0xC0 // End Collection
};
面白いのは、InputとOutputが両方混在して書かれている点です。Inputの場合は、Input場所だけ抜き出して使い、Outputの場合はOutputの場所だけ抜き出して使います。
ちなみに、バイト列を入力すると解析してくれるサイトもあります。
HID Control Point (HID制御ポイント) キャラクタリスティック (UUID: 0x2A4C)
HID制御ポイントキャラクタリスティックは、HIDデバイスに対して特定の制御コマンドを送信するために使用されます。1バイトのキャラクタリスティックになります。
以下のようなものがあります:
- No Operation(NOP)値:0
何も指示はないことを示します。通常の状態ですね。 - Hardware Reset(ハードリセット)値:1
ハード上のリセットを実行します。初期状態などでしょうか。 - Software Reset(ソフトリセット)値:2
ソフト上のリセットを実行します。 - Suspend(サスペンド)値:3
HIDデバイスの動作を一時停止します。例えば、ユーザーがキーボードを一時的に無効にすることができます。 - Exit Suspend(サスペンド解除)値:4
HIDデバイスの一時停止を解除し、通常の動作に戻します。 - Virtual Cable Unplug(仮想ケーブルの抜き差し)値:5
HIDデバイスの仮想的なケーブルを抜き差しすることを示します。
Report (レポート) キャラクタリスティック (UUID: 0x2A4D)
レポートキャラクタリスティックは、HID キーボードならば、どのキーが押されているか(Input)、NumLockなどのLEDを点灯させるか(Output)の情報が収められています。Input用とOutput用でキャラクタリスティックは2つ存在しています。すると同じUUIDのキャラクタリスティックが2つ存在すると訳なのですが、どのように見分けるかと言うと、それぞれのキャラクタリスティックに付随するレポート参照ディスクリプタに書いてあります。
上で示したレポートマップであれば、Inputの場合は、1バイト目が修飾キーで、そのあとがキーコードが7個分(7バイト)で、合計8バイトのデータになります。Outputで終わっている個所は無視されます。
Outputの場合は、5ビットがLED情報を示していて、3ビットがパディングされています。
レポート参照ディスクリプタ(UUID:0x2907)
レポートのどこを参照するのかを示す2バイトからなるディスクリプタです。
1バイト目にレポートマップで記載したレポートIDを記載します。
2バイト目にレポートタイプを記載します。Input=1, Output=2, Feature=3になります。
上で示したレポートマップであれば、1バイト目は0x01となります。2バイト目が、1か、2で違うと言うことになります。
Protocol Mode (プロトコルモード) キャラクタリスティック (UUID: 0x2A4E)
HIDデバイスは、2種類の動作モードをサポートしています。通常はレポートプロトコルモードです。このモードを選択する1バイトのキャラクタリスティックです。
- Boot Protocol Mode(ブートプロトコルモード)値:0
ブートプロトコルで使用するレポートマップがあらかじめ決まっており、レポートマップを示すキャラクタリスティックはありません。
1バイト目:Report ID
2バイト目:修飾キー
3バイト目:予約。常に0
4~9バイト目:キーコード6個
の合計9バイトです。
このモードでは、最低限の機能しか提供せず、基本的な入力のみをサポートとなります。
BluetoothアダプタがHID Proxy モードに対応している必要があります。すると、BIOSでも使用できます。 - Report Protocol Mode(レポートプロトコルモード)値:1
レポートマップに記載した通りに動作するモードです。レポートマップに記載した色々な動作を行うことができます。ゲームパッドやジョイスティックなど、複雑なHIDデバイスも対応することができます。通常はこのモードが使用されます。
解析する時の注意
LightBlueや、bluetoothctlなどで、HIDサービスに登録されているキャラクタリスティックを読み込もうとしても読み込むことはできません。
おそらくセキュリティ上の理由で、キーボード入力を盗み取ることができないようになっているものと思われます。
そのため、知りたい場合は、ESP32をホストにして、BLEキーボードなどを接続するとよいでしょう。
手元にBLEキーボードがない場合は、Windowsの場合は、MicrosoftからBluetooth LE Explorerとなるソフトが配布されています。
https://apps.microsoft.com/store/detail/bluetooth-le-explorer/9N0ZTKF1QD98?hl=ja-jp&gl=jp
使い方も簡単で、Virtual Keyboardを選択し、Discoverable and ConnectableをOnにして、ESP32やスマホで新しいデバイスに接続するだけで使えます。
接続が完了した後に、上記の画面上でキーボード入力すると接続した相手にキー入力が伝わります。
これで、ESP32でHIDのキャラクタリスティックを見てみることもできますし、ターゲットとするデバイスがBLEキーボードに対応しているかどうか調べることもできます。
Batteryサービスのキャラクタリスティックについて
こちらは単純で、バッテリーの残量を示すキャラクタリスティックと、それに付随する残量の示し方示すディスクリプタになります。
バッテリー残量 キャラクタリスティック(UUID:0x2A19)
バッテリー表現ディスクリプタで示す表し方でバッテリーの残量を示すキャラクタリスティックです。
通常は、以下のディスクリプタの例の様に1バイトで、0~100の数値として、単位が%だと思います。
バッテリー表現ディスクリプタ(UUID:0x2904)
characteristic presentation formatに沿って、残量表示を説明します。
以下にプログラムの例で示します。
#define GATT_NAME_SPACE_BLUETOOTH_SIG 0x01
#define GATT_CPF_FORMAT_UINT8 0x04
// キャラクタリスティックの値を説明するための情報
typedef struct characteristic_presentation_format{
uint16_t unit; // UUIDで単位を表します。 3.5 Units を参照
uint16_t description; // name_space=1の時は 2.4.2.1 Bluetooth SIG GATT Characteristic Presentation Format Description を参照
uint8_t format; // 数値のフォーマットを表す
uint8_t exponent; // 指数。キャラクタリスティックの値に10の何乗をかけるのか。
uint8_t name_space; // descriptionの名前空間。 0:なし。 1:Bluetooth SIGによる定義
} gatt_cpf_struct;
// バッテリ残量の単位情報
gatt_cpf_struct battery_level_units = {
.unit = 0x27AD, // 単位:パーセント
.description = 0, // 不明を指定
.format = GATT_CPF_FORMAT_UINT8,
.exponent = 0, // 10^0 = 1倍
.name_space = GATT_NAME_SPACE_BLUETOOTH_SIG
};
要するに、単位と、10の何乗倍するかの指数と、uint8などのバイト数を指定します。
unitやdescriptionで指定する値は以下を参照。
https://www.bluetooth.com/specifications/assigned-numbers/
以下抜粋
2.4.2.1 Bluetooth SIG GATT Characteristic Presentation Format Description
0x0000:不明
0x0001〜0x00FF:1〜255番目 という意味
0x0100:フロント
0x010F:内側
のようになっています。
BLE対応の温度計を例にすると、
設置場所を1番目や内側などの説明するために使います。
UUIDを使い単位を指定できます。
3.5 Units 抜粋
0x272F:温度℃
0x2704:電流A
0x2728:電圧V
0x27B0:電池容量Ah
0x2727:電荷量C
0x27AD:パーセント%
0x27AB:電力量kWh
Device Informationサービスのキャラクタリスティックについて
デバイスに関する情報を示すサービスです。単に、モデル名やシリアル番号、バージョンなど製品に関する情報が書かれているだけです。
SYSTEM ID キャラクタリスティック(UUID:0x2A23)
BLEデバイスを一意に識別するための値です。64ビット(8バイト)の値で表され、以下の情報を含んでいます。
OUI(Organizationally Unique Identifier:ベンダーコード)(24ビット):メーカー、またはその他の組織を一意に識別する値。
メーカー固有ID(16ビット): メーカー自身で付与する一意のID。
バージョン(8ビット): デバイスのバージョン情報を示す値
MODEL NUMBER キャラクタリスティック(UUID:0x2A24)
製品の型名を示す文字列。
SERIAL NUMBER キャラクタリスティック(UUID:0x2A25)
製品のシリアル番号を示す文字列。
FIRMWARE REVISION キャラクタリスティック(UUID:0x2A26)
製品のファームウェアのリビジョンを示す文字列。
HARDWARE REVISION キャラクタリスティック(UUID:0x2A27)
製品のハードウェアのリビジョンを示す文字列。
SOFTWARE REVISION キャラクタリスティック(UUID:0x2A28)
製品のソフトウェアのリビジョンを示す文字列。
MANUFACTURER NAME キャラクタリスティック(UUID:0x2A29)
製品の製造者名を示す文字列。
PnP INFO キャラクタリスティック(UUID:0x2A50)
この中で、ちょっとわかりずらいのがこのキャラクタリスティックでしょうか。PnPは、Plug and Playの略で、特定のデバイスを一意に識別するための識別子です。ちなみに、andは発音すると、n似ているで、省略されるときは、nとなります。以下の7バイトで構成されます。このサービスの中で唯一文字列じゃない属性です。
項目 | 型 | 説明 |
---|---|---|
Vendor ID Source | uint8 (1バイト) | どこで取得したベンダーIDかを示します。 0x01:Bluetooth SIG 0x02:USB IF それ以外は将来のために予約されています。 |
Vendor ID | uint16 (2バイト) | ベンダーID |
Product ID | uint16 (2バイト) | プロダクトID |
Product Version | uint16 (2バイト) | プロダクトのバージョン |
趣味で使う分には、適当に入れておきましょう。
GATTにおけるPrimary ServiceとSecondary Serviceの違い
サービスには、Primary ServiceとSecondary Serviceという分類があります。
Primary Service (プライマリサービス)
プライマリサービスはBLEデバイスの動作に必須のサービスが含まれます。通常、BLEデバイスは1つ以上のプライマリサービスを持ち、それぞれのプライマリサービスには複数のキャラクタリスティックで構成されます。
一般的なプライマリサービスは、Bluetooth SIGによって定義されており、例としてHIDサービスが該当します。
Secondary Service (セカンダリサービス)
セカンダリサービスは、プライマリサービスの一部として存在するサービスを表します。プライマリサービス内の1つのキャラクタリスティックとして定義されます。補助的な情報や機能を提供が目的です。
セカンダリサービスは、プライマリサービスでインクルードされることで、色々なプライマリサービスで使いまわすことができます。
プライマリサービスとセカンダリサービスの違いは、GATTデータベース内での階層的な関係にあります。プライマリサービスはBLEデバイスの動作に必須のサービスが含まれます。一方、セカンダリサービスはプライマリサービスの一部であり、バッテリの残量やデバイスの製造者やシリアル番号などの追加の情報や機能を提供するために使用されます。
最後に
長くなりましたが、以上が現時点で調べた結果です。もともと、ESP32 S3用にBLEキーボードのライブラリがあるなって思っていたのですが、上手く使えなかったので、一から調べて、順番に実装した次第です。次回以降もESP32でBLEキーボードを作るまでをお届けしようと思います。
コメント