- 新增docs/device_communication_protocol_v1.md文档,定义V1修订版协议 - CDC和BLE GATT均改为直接传输业务消息,去掉外层协议封装 - BLE改为使用NUS(Nordic UART Service)替代原有GATT服务 - 统一键盘位图为29字节格式,FunctionKeyEvent改为上报全键盘位图 - 顶层消息增加msg_id和reply_to字段用于请求响应匹配 - Ack和Error合并为统一Response消息类型 - CDC和NUS均增加统一外层帧格式:magic(2) + len(1) + protobuf - 添加Proto frame常量定义及长度验证逻辑 - 更新proto文件定义,包含DeviceMessage统一信封和ResponseCode枚举 - 重构hid_flowctrl_module.c中的上下文访问方式,统一使用ctx前缀
303 lines
9.1 KiB
Markdown
303 lines
9.1 KiB
Markdown
# 下位机通信协议 v1 修订版
|
||
|
||
本版基于原 `device_communication_protocol_v1.md` 修订,变更点如下:
|
||
|
||
- `CDC` 去掉外层协议,直接传业务消息
|
||
- `BLE GATT` 改为使用 `NUS (Nordic UART Service)`
|
||
- `Bitmap` 统一为 29 字节键盘位图格式
|
||
- `FunctionKeyEvent` 不再上报单键按下/释放,改为上报全键盘位图,格式与 `Bitmap` 一致
|
||
- 顶层消息增加 `msg_id` / `reply_to`
|
||
- `Ack` 与 `Error` 合并为统一 `Response`
|
||
- `CDC` 与 `NUS` 均增加统一外层帧:`magic(2) + len(1) + protobuf`
|
||
|
||
## 1. 总体原则
|
||
|
||
- 标准键盘输入继续走 `HID / BLE HID`,不变。
|
||
- 私有通信继续保留两条通道:
|
||
- `USB`:走 `CDC`
|
||
- `BLE`:走 `NUS`
|
||
- 两条通道统一承载同一套 `protobuf` 业务消息。
|
||
- `CDC` 与 `NUS` 均使用完全相同的外层帧格式,帧内承载 `protobuf` 业务消息。
|
||
- 当前版本不做应用层分片。
|
||
|
||
说明:
|
||
|
||
- 顶层 `protobuf` 消息名改为 `DeviceMessage`,表示独立于具体传输通道的统一业务信封。
|
||
- 当前版本使用统一外层帧,`CDC` 与 `NUS` 都按完整帧进行发送与解析。
|
||
|
||
## 2. 共享业务消息
|
||
|
||
统一的 `protobuf` 业务消息为 `DeviceMessage`,内部 `oneof body` 可取以下类型:
|
||
|
||
| 消息 | 方向 | 说明 |
|
||
| --- | --- | --- |
|
||
| `HelloReq` | Host -> Device | 握手请求 |
|
||
| `HelloRsp` | Device -> Host | 握手响应 |
|
||
| `Bitmap` | Host -> Device | 下发功能键位图配置 |
|
||
| `FunctionKeyEvent` | Device -> Host | 上报当前全键盘状态位图 |
|
||
| `LedState` | Device -> Host | 上报键盘 LED 状态 |
|
||
| `TimeSync` | Host -> Device | 下发时间同步 |
|
||
| `ThemeRgb` | Host -> Device | 下发主题颜色 |
|
||
| `Response` | Device -> Host | 控制命令统一响应 |
|
||
|
||
顶层公共字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `msg_id` | `uint32` | 当前消息 ID;`0` 表示未使用 |
|
||
| `reply_to` | `uint32` | 当前消息响应的请求 ID;`0` 表示不是响应 |
|
||
|
||
规则:
|
||
|
||
- Host 发起的请求消息应填写唯一 `msg_id`。
|
||
- `HelloRsp` 与 `Response` 应填写 `reply_to = 原请求.msg_id`。
|
||
- `FunctionKeyEvent`、`LedState` 等异步上报消息应设置 `reply_to = 0`。
|
||
- 当前版本建议每条链路同一时刻仅保留 `1` 条 in-flight 控制请求。
|
||
|
||
## 3. CDC 协议
|
||
|
||
CDC 在线路上的真实字节格式如下:
|
||
|
||
```text
|
||
magic(2B) + len(1B) + protobuf(DeviceMessage)
|
||
```
|
||
|
||
约束:
|
||
|
||
- `magic` 固定为 `0xAA55`,按小端发送,即线路字节序为 `0x55 0xAA`。
|
||
- `len` 为后续 `protobuf(DeviceMessage)` 的字节长度,范围 `0..64`。
|
||
- Host -> Device 与 Device -> Host 都发送完整帧。
|
||
- 当前版本要求单条业务消息一次完整发送,不做应用层分片与重组。
|
||
|
||
## 4. NUS 协议
|
||
|
||
BLE 私有通信改为使用 `NUS (Nordic UART Service)`。
|
||
|
||
NUS 线路上的真实字节格式如下:
|
||
|
||
```text
|
||
magic(2B) + len(1B) + protobuf(DeviceMessage)
|
||
```
|
||
|
||
方向定义:
|
||
|
||
- Host -> Device:向 `RX` 特征写入完整帧
|
||
- Device -> Host:通过 `TX Notify` 上报完整帧
|
||
|
||
## 5. NUS 服务定义
|
||
|
||
Service UUID:
|
||
|
||
```text
|
||
6E400001-B5A3-F393-E0A9-E50E24DCCA9E
|
||
```
|
||
|
||
Characteristic 定义:
|
||
|
||
| 名称 | UUID | 属性 | 用途 |
|
||
| --- | --- | --- | --- |
|
||
| `RX` | `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` | `Write`, `Write Without Response` | Host -> Device |
|
||
| `TX` | `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` | `Notify` | Device -> Host |
|
||
|
||
约束:
|
||
|
||
- 连接后主机必须先订阅 `TX`。
|
||
- 当前版本不做应用层分片。
|
||
- 因为增加了 `magic + len` 外层帧,建议协商 `ATT_MTU >= 64`,保证完整帧可一次发送。
|
||
|
||
## 6. 外层帧定义
|
||
|
||
外层帧字段如下:
|
||
|
||
| 字段 | 大小 | 说明 |
|
||
| --- | --- | --- |
|
||
| `magic` | `2` 字节 | 固定 `0xAA55`,线路字节序 `55 AA` |
|
||
| `len` | `1` 字节 | `protobuf(DeviceMessage)` 长度,最大 `64` |
|
||
| `payload` | `len` 字节 | `protobuf(DeviceMessage)` |
|
||
|
||
## 7. protobuf 字段定义
|
||
|
||
以下是业务字段语义。
|
||
|
||
### HelloReq
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `protocol_version` | `uint32` | 当前固定为 `1` |
|
||
|
||
### HelloRsp
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `protocol_version` | `uint32` | 当前固定为 `1` |
|
||
| `vendor_id` | `uint32` | 当前建议 `0x1209` |
|
||
| `product_id` | `uint32` | 当前建议 `0x0001` |
|
||
| `firmware_major` | `uint32` | 固件主版本 |
|
||
| `firmware_minor` | `uint32` | 固件次版本 |
|
||
| `capability_flags` | `uint32` | 能力位 |
|
||
|
||
### Bitmap
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `usage_bitmap` | `bytes` | 固定 29 字节,表示功能键配置位图 |
|
||
|
||
语义:
|
||
|
||
- `Bitmap.usage_bitmap` 长度固定为 `29` 字节。
|
||
- 第 `0` 字节表示 `0xE0` 到 `0xE7` 这 8 个控制键。
|
||
- 第 `1` 到第 `28` 字节表示 `0x00` 到 `0xDF`。
|
||
- 位值为 `1` 表示该 usage 被配置为功能键。
|
||
- 位值为 `0` 表示该 usage 不是功能键,继续按普通键处理。
|
||
|
||
位映射规则:
|
||
|
||
- 第 `0` 字节:
|
||
- `bit0 -> 0xE0`
|
||
- `bit1 -> 0xE1`
|
||
- ...
|
||
- `bit7 -> 0xE7`
|
||
- 第 `1` 字节:
|
||
- `bit0 -> 0x00`
|
||
- `bit1 -> 0x01`
|
||
- ...
|
||
- `bit7 -> 0x07`
|
||
- 第 `2` 字节:
|
||
- `bit0 -> 0x08`
|
||
- ...
|
||
- `bit7 -> 0x0F`
|
||
- 依此类推。
|
||
- 第 `28` 字节:
|
||
- `bit0 -> 0xD8`
|
||
- ...
|
||
- `bit7 -> 0xDF`
|
||
|
||
### FunctionKeyEvent
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `usage_bitmap` | `bytes` | 固定 29 字节,表示当前全键盘状态位图 |
|
||
|
||
语义:
|
||
|
||
- `FunctionKeyEvent` 不再使用 `usage + action` 的单键事件格式。
|
||
- `FunctionKeyEvent.usage_bitmap` 长度固定为 `29` 字节。
|
||
- 位图编码方式与 `Bitmap.usage_bitmap` 完全一致。
|
||
- 位值为 `1` 表示该 usage 当前处于按下状态。
|
||
- 位值为 `0` 表示该 usage 当前处于释放状态。
|
||
- 该消息表示一次完整键盘状态快照,而不是单个按键变化。
|
||
|
||
### LedState
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `led_mask` | `uint32` | NumLock/CapsLock 等位掩码 |
|
||
|
||
### TimeSync
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `version` | `uint32` | 当前固定 `1` |
|
||
| `flags` | `uint32` | 标志位 |
|
||
| `timezone_min` | `sint32` | 时区偏移,单位分钟 |
|
||
| `utc_ms` | `fixed64` | UTC 毫秒时间戳 |
|
||
| `accuracy_ms` | `fixed32` | 精度,毫秒 |
|
||
|
||
### ThemeRgb
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `red` | `uint32` | `0..255` |
|
||
| `green` | `uint32` | `0..255` |
|
||
| `blue` | `uint32` | `0..255` |
|
||
|
||
### Response
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `error_code` | `enum ResponseCode` | 响应结果;`OK` 表示成功,其余表示失败原因 |
|
||
|
||
## 8. 枚举定义
|
||
|
||
### ResponseCode
|
||
|
||
| 值 | 含义 |
|
||
| ---: | --- |
|
||
| `0` | `OK` |
|
||
| `1` | `UNKNOWN_TYPE` |
|
||
| `2` | `INVALID_LENGTH` |
|
||
| `3` | `INVALID_PARAM` |
|
||
| `4` | `NOT_READY` |
|
||
|
||
说明:
|
||
|
||
- 原 `KeyAction` 枚举已不再使用,因为 `FunctionKeyEvent` 改为完整位图上报。
|
||
|
||
## 9. CapabilityFlags 定义
|
||
|
||
`HelloRsp.capability_flags` 先按以下位定义:
|
||
|
||
| Bit | 含义 |
|
||
| ---: | --- |
|
||
| `0` | 支持功能位图配置 |
|
||
| `1` | 支持时间同步 |
|
||
| `2` | 支持主题切换 |
|
||
| `3` | 支持 LED 状态上报 |
|
||
| `4` | 支持全键盘位图事件上报 |
|
||
|
||
当前建议最小返回值:
|
||
|
||
- 若仅先做握手:可先返回 `0`
|
||
- 若 `Bitmap` 可用:打开 `bit0`
|
||
- 若 `FunctionKeyEvent` 全键盘位图上报可用:打开 `bit4`
|
||
|
||
## 10. 握手流程
|
||
|
||
### CDC 握手
|
||
|
||
1. Host 打开串口
|
||
2. Host 发送 `magic + len + protobuf(DeviceMessage{hello_req})`
|
||
3. Device 解析外层帧与 `protobuf(DeviceMessage)`
|
||
4. Device 取出 `hello_req`
|
||
5. Device 返回 `magic + len + protobuf(DeviceMessage{hello_rsp})`
|
||
|
||
### NUS 握手
|
||
|
||
1. Host 连接 BLE
|
||
2. Host 发现 `NUS Service`
|
||
3. Host 订阅 `TX Notify`
|
||
4. Host 向 `RX` 写入完整帧
|
||
5. Device 解析外层帧与 `protobuf(DeviceMessage)`
|
||
6. Device 通过 `TX Notify` 发回完整帧
|
||
|
||
说明:
|
||
|
||
- 文中 `protobuf(HelloReq)` / `protobuf(HelloRsp)` 指业务上发送对应的 `DeviceMessage` 消息,其 `oneof body` 分别为 `hello_req` / `hello_rsp`。
|
||
|
||
## 11. 设备行为规则
|
||
|
||
- 上电默认所有按键都是普通键。
|
||
- 普通键的标准输入行为继续走 `HID / BLE HID`,不变。
|
||
- 收到 `Bitmap` 后,位图中标记为功能键的按键按功能键逻辑处理。
|
||
- `FunctionKeyEvent` 上报的是当前全键盘状态快照,编码格式与 `Bitmap` 一致。
|
||
- `LedState` 在 LED 状态变化时上报。
|
||
- `TimeSync` 收到后更新时间管理模块。
|
||
- `ThemeRgb` 收到后更新主题模块。
|
||
- 控制类请求处理完成后统一返回 `Response`。
|
||
- 不支持的消息类型也应返回 `Response { error_code = UNKNOWN_TYPE }`,不得静默丢弃。
|
||
|
||
## 12. Response 规则
|
||
|
||
- `HelloReq` 不返回 `Response`,而是直接返回 `HelloRsp`,且 `reply_to = HelloReq.msg_id`。
|
||
- `Bitmap`、`TimeSync`、`ThemeRgb` 处理完成后应返回 `Response`。
|
||
- `Response.error_code = OK` 表示成功。
|
||
- 不支持的消息类型返回 `Response { error_code = UNKNOWN_TYPE }`。
|
||
- 长度错误、参数错误、模块未就绪时分别返回 `INVALID_LENGTH`、`INVALID_PARAM`、`NOT_READY`。
|
||
|
||
示例:
|
||
|
||
- 收到 `Bitmap` 成功处理
|
||
返回 `Response { error_code = OK }`
|
||
- 收到 `ThemeRgb` 参数非法
|
||
返回 `Response { error_code = INVALID_PARAM }`
|