一、HID over GATT Profile:蓝牙HID的现代之路

说到蓝牙HID设备,很多人第一反应是经典的蓝牙键盘、鼠标。但传统的蓝牙HID走的是BR/EDR(经典蓝牙)通道,功耗高、配对慢。后来BLE(低功耗蓝牙)普及了,Google在Android 4.0之后开始支持HID over GATT(HOGP),说白了就是用BLE来传HID数据。

我个人习惯把HOGP看作「BLE版的HID协议栈」。它定义了一套GATT服务和特征值,让HID设备(比如蓝牙键盘)和Host(比如手机)通过BLE通信。你想想看,传统蓝牙键盘可能几天就没电了,而BLE键盘用一颗纽扣电池能撑半年——这就是HOGP的价值。

1.1 HOGP的核心架构

HOGP协议栈分三层:

  • HID Service:暴露HID报告、协议版本、设备信息等
  • Report Protocol:定义输入/输出/特性报告的格式
  • Boot Protocol:兼容传统USB HID的启动模式(键盘/鼠标基本功能)

我在项目中遇到过一个问题:某款BLE键盘在Android手机上能连上,但按键没反应。后来发现是HID Service的Report Map特征值没配置对——Android Host要求必须包含键盘的Usage Page和Usage ID。嗯,这里要注意,HOGP的GATT Service UUID是固定的0x1812,Report Map特征值是0x2A4B。

关键GATT特征值一览:

特征值UUID作用
Report Map0x2A4B描述HID报告的结构(即报告描述符)
Report0x2A4D传输实际的输入/输出数据
HID Information0x2A4A包含HID版本、国家代码等
Protocol Mode0x2A4E切换Boot模式或Report模式

二、HID报告描述符:设备与Host的「翻译官」

HID报告描述符(Report Descriptor)是HID设备最核心的部分。它用一套二进制语言告诉Host:我这个设备有几个按键、几个轴、数据怎么组织。说白了,它就是一份「数据说明书」。

我曾经调试过一个游戏手柄,Host始终识别不到摇杆数据。最后发现报告描述符里把摇杆的Logical Minimum写成了0,但实际数据范围是-127到127——Host按照描述符解析,自然全乱了。

2.1 报告描述符的结构

每个报告描述符由一系列Item组成,每个Item包含:

  • Tag:比如Input、Output、Feature、Collection
  • Usage Page:比如Generic Desktop Controls(0x01)、Keyboard/Keypad(0x07)
  • Usage ID:具体功能,比如键盘的A键(0x04)、鼠标的X轴(0x30)
  • Logical Minimum/Maximum:数据的取值范围
  • Report Size/Count:每个字段的位数和数量

小技巧:写报告描述符时,先用USB HID Descriptor Tool(HUT)生成模板,再根据BLE的MTU大小调整Report Size。BLE的MTU通常只有23字节(默认),所以报告要尽量精简。

2.2 键盘报告描述符示例

下面是一个标准键盘的报告描述符(8字节输入报告):

0x05, 0x01,        // Usage Page (Generic Desktop)
0x09, 0x06,        // Usage (Keyboard)
0xA1, 0x01,        // Collection (Application)
0x05, 0x07,        //   Usage Page (Keyboard/Keypad)
0x19, 0xE0,        //   Usage Minimum (Keyboard LeftControl)
0x29, 0xE7,        //   Usage Maximum (Keyboard Right GUI)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x08,        //   Report Count (8)
0x81, 0x02,        //   Input (Data,Var,Abs)
0x95, 0x01,        //   Report Count (1)
0x75, 0x08,        //   Report Size (8)
0x81, 0x01,        //   Input (Const)  // 填充字节
0x95, 0x06,        //   Report Count (6)
0x75, 0x08,        //   Report Size (8)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x65,        //   Logical Maximum (101)
0x05, 0x07,        //   Usage Page (Keyboard/Keypad)
0x19, 0x00,        //   Usage Minimum (Reserved)
0x29, 0x65,        //   Usage Maximum (Keyboard Application)
0x81, 0x00,        //   Input (Data,Array)
0xC0               // End Collection

这个描述符定义了:8个修饰键位(Ctrl/Shift等)+ 1个填充字节 + 6个普通按键。Host收到8字节报告后,就能解析出用户按了哪些键。

三、键盘/鼠标模拟:从设备端到Host端

在Android上做HID设备开发,通常有两种场景:

  1. Android设备作为HID Device:比如把手机模拟成蓝牙键盘,控制电脑
  2. Android设备作为HID Host:比如手机连接蓝牙键盘,接收输入

我们重点讲第二种——Android作为HID Host。这是Android系统原生支持的,从Android 4.0开始,系统内置了HID Host服务。

3.1 Android HID Host架构

Android的HID Host实现位于packages/apps/Bluetooth模块中,核心类是BluetoothHidHost。它通过BluetoothProfile接口暴露给应用层。

我记得第一次调HID Host时,发现连接成功后收不到按键事件。查了半天,原来是应用没有注册BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED广播。嗯,这个广播是必须的,否则系统不会把HID事件分发给你的应用。

避坑指南:我曾经在Android 9上遇到一个坑——系统默认只支持Boot Protocol模式的键盘。如果你的设备只实现了Report Protocol,需要手动调用setProtocolMode()切换。否则Host会一直等待Boot模式的报告,设备却发Report模式的报告,两边对不上。

3.2 键盘模拟的代码骨架

下面是一个简单的HID键盘模拟代码(设备端):

// 1. 构建HID报告
byte[] report = new byte[8];
report[0] = 0x00;  // 修饰键:无
report[1] = 0x00;  // 保留
report[2] = 0x04;  // 按键:A键(Usage ID 0x04)
// report[3..7] 为其他按键,这里省略

// 2. 通过GATT发送报告
BluetoothGattCharacteristic reportChar = 
    hidService.getCharacteristic(HID_REPORT_UUID);
reportChar.setValue(report);
bluetoothGatt.writeCharacteristic(reportChar);

Host端接收时,系统会通过InputManager注入按键事件。你不需要自己解析报告——Android的HID Host服务已经帮你做好了。

四、Android设备作为HID Host:实战要点

如果你想让Android手机连接一个自定义的BLE HID设备(比如自制的键盘),需要做以下几步:

4.1 扫描与配对

  • 使用BluetoothLeScanner扫描BLE设备,过滤包含HID Service UUID(0x1812)的设备
  • 配对时调用BluetoothDevice.createBond()
  • 配对成功后,通过BluetoothProfile.ServiceListener获取BluetoothHidHost实例

4.2 连接与数据接收

  • 调用BluetoothHidHost.connect(device)建立HID连接
  • 注册BluetoothHidHost.ACTION_HID_DEVICE_REPORT广播接收报告数据
  • 或者通过InputManager直接接收系统注入的按键事件

性能优化建议:BLE HID的延迟通常在10-20ms,比经典蓝牙的5-10ms稍高。如果做游戏外设,建议:

  • 使用BLE 5.0的2M PHY提升吞吐量
  • 将连接间隔(Connection Interval)设为7.5ms(最小值)
  • 报告长度控制在20字节以内,避免分片

五、知识体系总览

下面这张图总结了HID over GATT开发的核心脉络:

HID over GATT 开发知识体系 HOGP协议栈 GATT Service 0x1812 报告描述符 Usage Page / Usage ID 协议模式 Boot / Report 模式 键盘设备 8字节报告 / 6键同时 鼠标设备 XY轴 / 滚轮 / 按键 游戏手柄 摇杆 / 扳机 / 按键矩阵 HID Host API BluetoothHidHost 输入事件注入 InputManager 广播与回调 ACTION_HID_DEVICE_REPORT 从协议到设备再到系统实现,HOGP是一条完整链路

这张图从左到右展示了HOGP开发的三个层次:最上层是协议规范,中间是具体设备类型,最下层是Android系统的实现接口。我个人建议初学者先从键盘设备入手,因为它的报告结构最简单,调试起来也最直观。

我的经验:如果你手头没有BLE HID设备,可以用nRF Connect App模拟一个。它支持自定义GATT Service,你可以手动配置Report Map和Report特征值,然后观察Android Host的响应。这是最便宜的调试方式。

好了,关于HID over GATT的基础知识就聊到这里。记住三个核心点:报告描述符是灵魂、协议模式要匹配、Android Host API要注册广播。把这些搞明白了,剩下的就是调参和填坑了。


公众号:蓝海资料掘金营,微信deep3321