From 52546e35fe5db26c590a250db99c18de671d7be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= <2712495353@qq.com> Date: Wed, 1 Jul 2026 23:05:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20BLE=20HID=20?= =?UTF-8?q?=E9=94=AE=E7=9B=98=E5=AE=9E=E7=8E=B0=E8=A7=84=E6=A0=BC=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ble-hid-keyboard.md 文件,详细描述 BLE HID 键盘的背景、目标、系统架构、HID 报告格式及对外 API 规格 - 包含实现步骤和配置要求,指导开发者如何将 ESP32-S3 开发板实现为标准 BLE HID 键盘 --- docs/spec/ble-hid-keyboard.md | 473 ++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 docs/spec/ble-hid-keyboard.md diff --git a/docs/spec/ble-hid-keyboard.md b/docs/spec/ble-hid-keyboard.md new file mode 100644 index 0000000..9bc6c7d --- /dev/null +++ b/docs/spec/ble-hid-keyboard.md @@ -0,0 +1,473 @@ +# BLE HID 键盘实现规格 + +> 项目:HeavenlyHeroStar +> 目标芯片:ESP32-S3 +> ESP-IDF:6.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 Report(8 字节) + +与 USB HID 标准键盘 boot/report 格式一致: + +| 偏移 | 字段 | 说明 | +|------|------|------| +| 0 | modifier | 修饰键位图 | +| 1 | reserved | 保留,填 0 | +| 2–7 | 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 20–50 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 +#include + +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 等可见效果) + +### 阶段 4:NVS 快捷键表 + +#### 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 Tables:Keyboard/Keypad Page (0x07) + +--- + +## 修订记录 + +| 版本 | 日期 | 说明 | +|------|------|------| +| 1.0 | 2026-07-01 | 初版:BLE HID 实现步骤与架构规格 |