Files
HeavenlyHeroStar/docs/spec/ble-hid-keyboard.md
T
rsgltzyd 52546e35fe feat: 添加 BLE HID 键盘实现规格文档
- 新增 ble-hid-keyboard.md 文件,详细描述 BLE HID 键盘的背景、目标、系统架构、HID 报告格式及对外 API 规格
- 包含实现步骤和配置要求,指导开发者如何将 ESP32-S3 开发板实现为标准 BLE HID 键盘
2026-07-01 23:05:49 +08:00

474 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BLE HID 键盘实现规格
> 项目:HeavenlyHeroStar
> 目标芯片:ESP32-S3
> ESP-IDF6.0.1
> BLE 栈:NimBLE(外设模式)
> 状态:待实现
## 1. 背景与目标
### 1.1 现状
当前 `main/services/ble_manager.c` 实现为自定义 GATT 外设:
- 服务 UUID`0xFFF0`
- 特征 UUID`0xFFF1`Read / Write
- 无 Notify / HID 能力
- 电脑端无法以系统键盘方式接收按键
### 1.2 目标
将开发板实现为标准 **BLE HID 键盘(HOGP**,使 Windows / macOS / Linux 在蓝牙配对后,将设备识别为键盘,**无需安装 PC 端接收程序**。
扩展目标(后续阶段):
- 快捷键映射保存在设备 NVS
- 通过网页或 Web BLE 配置快捷键
- 物理按键(BOOT / KEY)及触摸屏 UI 触发 HID 报告
### 1.3 方案选型结论
| 方案 | 说明 | 结论 |
|------|------|------|
| A | 自定义 GATT Notify + PC Python 脚本 | 适合快速验证,需 PC 常驻程序 |
| **C** | **BLE HID 键盘** | **采用**OS 原生输入,适合 macropad 产品形态 |
| C + 配置通道 | HID 执行 + GATT/WiFi 写 NVS | 网页配置快捷键时采用 |
配置与执行分离:
```text
配置阶段:网页 / Web BLE / 屏上 UI → 写入 NVS 快捷键表
使用阶段:按键 / 触摸 → 读 NVS → 发送 BLE HID Report → OS 输入
```
---
## 2. 系统架构
### 2.1 模块划分
```text
main/services/
├── ble_manager.c # GAP:广播、连接、LED、bonding
├── ble_manager.h
├── ble_hid.c # HID GATT 服务、Report Map、发送报告
├── ble_hid.h
└── hid_keymap.c # 扫描码表、shortcut → modifier + keycode(可选拆分)
```
### 2.2 数据流
```mermaid
sequenceDiagram
participant User as 用户
participant Btn as BOOT/KEY/UI
participant FW as 固件
participant NVS as NVS
participant HID as BLE HID
participant OS as 操作系统
User->>FW: 网页/GATT 配置快捷键
FW->>NVS: 保存映射表
User->>Btn: 按下物理键或 UI 按钮
Btn->>FW: 触发事件
FW->>NVS: 查 shortcut
FW->>HID: keyboard report (press)
FW->>HID: keyboard report (release)
HID->>OS: Notify Input Report
OS->>User: 当前焦点窗口收到快捷键
```
### 2.3 GATT 服务设计
#### 阶段一(M2):仅 HID
| 服务 / 特征 | UUID | 属性 | 用途 |
|-------------|------|------|------|
| Human Interface Device | `0x1812` | Primary | 标准 HID 服务 |
| HID Information | `0x2A4A` | Read | 版本信息 |
| Report Map | `0x2A4B` | Read | USB HID 描述符 |
| HID Control Point | `0x2A4C` | Write w/o resp | Suspend / Exit |
| Report | `0x2A4D` | Read, Notify | **键盘 Input Report** |
| Protocol Mode | `0x2A4E` | Read, Write | Boot / Report 协议 |
Report 特征需附带 **Report Reference** 描述符(Report ID = 1, Input)。
#### 阶段二(M5,可选):配置服务
| 服务 / 特征 | UUID | 属性 | 用途 |
|-------------|------|------|------|
| HHS Config | `0xFFF0` | Primary | 厂商配置 |
| Config Write | `0xFFF1` | Write | 接收 shortcut blob / JSON |
| Config Read | `0xFFF2` | Read | 读回当前配置(可选) |
HID 与配置服务可 **并存** 于同一 GATT 表。
### 2.4 广播数据
除设备名 `HeavenlyHeroStar``CONFIG_ESP_BLE_DEVICE_NAME`)外,广播包需包含 HID 服务 UUID,否则 Windows 可能无法识别为键盘:
```c
static ble_uuid16_t adv_uuids16[] = { BLE_UUID16_INIT(0x1812) };
fields.uuids16 = adv_uuids16;
fields.num_uuids16 = 1;
fields.uuids16_is_complete = 1;
```
---
## 3. HID 键盘报告格式
### 3.1 Input Report8 字节)
与 USB HID 标准键盘 boot/report 格式一致:
| 偏移 | 字段 | 说明 |
|------|------|------|
| 0 | modifier | 修饰键位图 |
| 1 | reserved | 保留,填 0 |
| 27 | keycodes[6] | 最多 6 个同时按下的键,通常仅用 keycodes[0] |
```c
#define HID_MOD_LCTRL 0x01
#define HID_MOD_LSHIFT 0x02
#define HID_MOD_LALT 0x04
#define HID_MOD_LGUI 0x08 /* Win / Command */
typedef struct __attribute__((packed)) {
uint8_t modifier;
uint8_t reserved;
uint8_t keycodes[6];
} hid_keyboard_report_t;
```
### 3.2 发送组合键流程
组合键必须发送 **按下****释放** 两帧,否则 OS 认为键仍按住:
```text
1. report = { modifier, 0, keycode, 0, 0, 0, 0, 0 }
2. delay 2050 ms
3. report = { 0, 0, 0, 0, 0, 0, 0, 0 } /* 全零 = 释放 */
```
示例:`Ctrl+C`
```text
按下:modifier=0x01, keycode=0x06
释放:全零
```
### 3.3 常用扫描码(Keyboard/Keypad Page 0x07
| 键 | keycode |
|----|---------|
| C | `0x06` |
| S | `0x16` |
| D | `0x07` |
| F5 | `0x3E` |
| Enter | `0x28` |
完整表见 [USB HID Usage Tables](https://www.usb.org/sites/default/files/documents/hut1_3_0.pdf)。
### 3.4 Report Map
**不要手写**。从 ESP-IDF 官方 HID 示例复制 `report_map[]` 字节数组:
```powershell
cd C:\esp\v6.0.1\esp-idf\examples\bluetooth\nimble
dir /s /b *hid*
```
---
## 4. 对外 API 规格
### 4.1 `ble_hid.h`
```c
#pragma once
#include <stdint.h>
#include <stdbool.h>
int ble_hid_init(void);
bool ble_hid_ready(void);
int ble_hid_key_press(uint8_t modifier, uint8_t keycode);
int ble_hid_key_release(void);
int ble_hid_send_combo(uint8_t modifier, uint8_t keycode);
```
| 函数 | 说明 |
|------|------|
| `ble_hid_init` | 注册 HID GATT 服务,在 `ble_manager_init` 中调用 |
| `ble_hid_ready` | 已连接 **且** Host 已订阅 Report Notify |
| `ble_hid_send_combo` | 发送按下 → 延迟 → 释放,供按键/UI 调用 |
### 4.2 `ble_manager.h`(保留)
```c
void ble_manager_init(void);
void ble_manager_set_status_cb(ble_status_cb_t cb);
bool ble_manager_is_connected(void);
```
连接状态与 LED`BOARD_PIN_LED`GPIO16)逻辑保持不变。
### 4.3 就绪条件
`gap_event_handler` 中处理 `BLE_GAP_EVENT_SUBSCRIBE`
```c
case BLE_GAP_EVENT_SUBSCRIBE:
if (event->subscribe.attr_handle == s_hid_report_handle) {
s_notify_enabled = event->subscribe.cur_notify;
}
break;
```
```c
bool ble_hid_ready(void)
{
return s_connected && s_notify_enabled;
}
```
---
## 5. sdkconfig 配置
`sdkconfig.defaults``idf.py menuconfig` 中确认:
```text
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y
# Windows 重连稳定性
CONFIG_BT_NIMBLE_SM_LEGACY=y
CONFIG_BT_NIMBLE_SM_SC=y
CONFIG_BT_NIMBLE_NVS_PERSIST=y
```
`ble_manager_init` 中 Security Manager
```c
ble_hs_cfg.sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT; /* Just Works */
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_sc = 1;
```
保留 `ble_store_config_init()` 以持久化 bonding 信息。
可选(配置通道 / 较大 payload):
```text
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=256
```
若后续启用 WiFi + BLE 共存:
```text
CONFIG_ESP_COEX_ENABLED=y
```
修改后执行:
```powershell
idf.py fullclean
idf.py reconfigure
idf.py build
```
---
## 6. 实现步骤
### 阶段 0:准备与基线
| 步骤 | 内容 |
|------|------|
| 0.1 | 在 `$IDF_PATH/examples/bluetooth/nimble/` 找到 HID demo,单独编译烧录 |
| 0.2 | Windows 配对 demo,在记事本验证能输入字符 |
| 0.3 | 确认本工程 ESP-IDF 6.0.1、`IDF_TARGET=esp32s3`、NimBLE 已启用 |
**策略选择:**
- **阶段一**:删除现有 `0xFFF0/0xFFF1` 自定义服务,仅保留 HID(最快跑通)
- **阶段二**HID + 恢复 `0xFFF0` 配置服务(网页 / Web BLE)
### 阶段 1:新增 `ble_hid.c`
1. 从 IDF demo 迁移 Report Map、HID GATT 服务定义
2. 实现 `ble_hid_init()`:注册 GATT 服务,保存 `s_hid_report_handle`
3. 实现 `ble_hid_send_report()``ble_gatts_notify_custom()`
4. 实现 `ble_hid_send_combo()`press → `vTaskDelay(20ms)` → release
5.`CMakeLists.txt` 添加 `"services/ble_hid.c"`
### 阶段 2:改造 `ble_manager.c`
1. 移除现有 `0xFFF0/0xFFF1` 自定义 GATT(阶段一)
2. `gatt_svc_init()` 替换为 `ble_hid_init()`
3. 广播增加 `0x1812` 服务 UUID
4. `gap_event_handler` 增加 `BLE_GAP_EVENT_SUBSCRIBE` 处理
5. 保留连接 / 断开 / 重广播 / LED 逻辑
初始化顺序:
```c
nimble_port_init();
ble_svc_gap_init();
ble_svc_gap_device_name_set(BLE_DEVICE_NAME);
ble_hid_init();
ble_hs_cfg.sync_cb = on_stack_sync;
ble_hs_cfg.reset_cb = on_stack_reset;
ble_hs_cfg.sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT;
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_sc = 1;
ble_store_config_init();
nimble_port_freertos_init(nimble_host_task);
```
### 阶段 3:按键触发(最小验证)
`app_main.c` 中:
```c
#include "boot_button.h"
#include "ble_hid.h"
static void on_boot_press(void)
{
if (ble_hid_ready()) {
ble_hid_send_combo(HID_MOD_LCTRL, HID_KEY_C);
} else {
ESP_LOGW("app", "HID not ready");
}
}
/* ble_manager_init() 之后 */
boot_button_init(on_boot_press);
```
**验证顺序:**
1. 烧录,串口确认 `advertising as "HeavenlyHeroStar"`
2. Windows:设置 → 蓝牙 → 添加设备 → 配对键盘
3. 打开记事本并聚焦
4. 按 BOOT:验证快捷键(建议先用 F5 等可见效果)
### 阶段 4NVS 快捷键表
#### 4.1 数据结构
```c
typedef struct {
uint8_t source; /* 0=BOOT, 1=KEY(GPIO4), 2+=UI slot */
uint8_t modifier;
uint8_t keycode;
uint8_t reserved;
} shortcut_entry_t;
#define SHORTCUT_MAX 16
```
#### 4.2 存储
```text
NVS namespace: "shortcuts"
Key: "map"
Format: shortcut_entry_t[SHORTCUT_MAX] blob
```
启动时加载;无有效数据时使用默认映射(例如 BOOT → F5)。
#### 4.3 触发
```c
shortcut_entry_t *e = shortcut_find(SOURCE_BOOT);
if (e && ble_hid_ready()) {
ble_hid_send_combo(e->modifier, e->keycode);
}
```
### 阶段 5:网页 / 配置通道(后续)
| 方式 | 通道 | 说明 |
|------|------|------|
| WiFi SoftAP + HTTP | `esp_http_server` | PC/手机浏览器 POST 配置,需 `BOARD_WIFI_ENABLED=1` |
| Web Bluetooth | 自定义 GATT `0xFFF0` Write | 无需 WiFi,需 Chrome/Edge |
| 屏上 LVGL UI | 本地 | 已有触摸屏,可不依赖网页 |
网页仅写入 NVS;运行时仍通过 HID 发送,PC 无需安装程序。
---
## 7. 与现有代码对应关系
| 现有 | HID 实施后 |
|------|------------|
| `0xFFF0/0xFFF1` Read/Write | 阶段一删除;阶段二改为配置专用 |
| `chr_access()` | 移至配置服务或删除 |
| `ble_manager_is_connected()` | 保留 |
| `notify_status()` + LED | 保留 |
| Notify 方案 `ble_manager_send()` | 替换为 `ble_hid_send_combo()` |
| `boot_button.c` | 已具备框架,`app_main` 中注册回调 |
| `BOARD_WIFI_ENABLED=0` | 网页配置阶段再启用 |
---
## 8. Windows 联调清单
| 现象 | 可能原因 | 处理 |
|------|----------|------|
| 搜不到设备 | 广播未含 `0x1812` | 检查 adv fields |
| 能连上但无按键 | 未订阅 Notify | 查 `BLE_GAP_EVENT_SUBSCRIBE``ble_hid_ready()` |
| 键一直按住 | 未发 release 报告 | 发送全零 8 字节 report |
| 配对后难重连 | bonding 未持久化 | 确认 `ble_store_config_init()`、NVS |
| 部分键无效 | 扫描码错误 | 对照 HID Usage Table |
| 连接后很快断开 reason=531 | 远端主动断开 | 531 = Remote User Terminated;完成配对后再测 |
| 安全软件拦截 | 蓝牙键盘权限 | 检查杀毒 / 企业策略 |
---
## 9. 里程碑
| 编号 | 交付物 | 验收标准 |
|------|--------|----------|
| **M1** | IDF HID demo 独立跑通 | Windows 配对,记事本可输入 |
| **M2** | `ble_hid.c` 迁入工程 | 替换自定义 GATT,编译通过 |
| **M3** | BOOT → `ble_hid_send_combo` | 记事本 / 浏览器验证 F5 或 Ctrl+C |
| **M4** | NVS 快捷键表 | 多按键 / UI 槽位可配置 |
| **M5** | 配置通道(GATT 或 WiFi 网页) | 浏览器修改映射,重启后仍生效 |
建议严格按 M1 → M5 顺序推进,避免同时调试 HID、NVS、网页。
---
## 10. 风险与约束
1. **HID 不能承载配置协议**:网页改映射必须走 NVS + 独立配置通道。
2. **配对与配置是两件事**:用户需先在 OS 中配对键盘,配置页单独访问。
3. **安全**:配对后设备可向任意焦点窗口输入;丢失设备需用户在系统中删除配对。
4. **资源**ESP32-S3 + LVGL + BLE HID 可行;若 NimBLE host 任务栈不足需调大。
5. **WiFi + BLE 共存**:启用网页配置时需额外验证射频共存与内存。
---
## 11. 参考
- ESP-IDF 路径:`C:\esp\v6.0.1\esp-idf\examples\bluetooth\nimble\*hid*`
- 工程 BLE 入口:`main/services/ble_manager.c`
- 板级 GPIO`main/bsp/board_config.h`BOOT=GPIO0, KEY=GPIO4, LED=GPIO16
- USB HID Usage TablesKeyboard/Keypad Page (0x07)
---
## 修订记录
| 版本 | 日期 | 说明 |
|------|------|------|
| 1.0 | 2026-07-01 | 初版:BLE HID 实现步骤与架构规格 |