Files
KeyBoard_QT/KeyBorad/PROTOBUF_ALIGN_STEPS.md

306 lines
7.4 KiB
Markdown
Raw Permalink Normal View History

# KeyBorad Protobuf 对齐步骤
这份步骤是按当前工程目录写的,目标是:
1. 保留当前 CDC 传输帧: `AA 55 + len + type + data + checksum`
2.`data` 改成 protobuf 二进制载荷
3. 上位机使用 protobuf C++
4. 下位机使用 nanopb
## 1. 先确定边界
当前工程里三层职责不要打乱:
- `KeyBorad\KeyBorad\DRI\Dri_Cdc.*`
作用: 串口枚举、握手、收发字节流
- `KeyBorad\KeyBorad\COM\Com_CdcEncode.*`
作用: 把一帧 CDC 包组出来
- `KeyBorad\KeyBorad\COM\Com_CdcDecode.*`
作用: 从 CDC 字节流缓存里拆出一帧
引入 protobuf 以后,建议职责变成:
- `DRI`
只管串口
- `COM`
只管外层帧
- `proto`
只管 `data` 的结构定义
也就是说:
- `type` 继续保留在外层帧里
- `data` 改为 protobuf 序列化结果
- `len` 改为 protobuf 载荷实际长度
## 2. 当前已经准备好的文件
本次已经加了两份协议源文件:
- `KeyBorad\proto\keyboard.proto`
- `KeyBorad\proto\keyboard.options`
其中:
- `keyboard.proto`
定义了 `HelloReqPayload / HelloRspPayload / BitmapPayload / FunctionKeyEventPayload / TimeSyncPayload` 等消息
- `keyboard.options`
`nanopb``bytes usage_bitmap` 指定了 `max_size:29`
## 3. 下载工具
### 上位机 protobuf
按 protobuf 官方文档,`protoc``--cpp_out` 生成 C++ 代码。
你需要准备:
- `protoc.exe`
- protobuf C++ runtime 头文件和库
官方参考:
- protobuf C++ generated code guide
- protobuf releases / protoc binary
### 下位机 nanopb
你需要准备:
- `nanopb` runtime
`pb.h`, `pb_common.c/h`, `pb_encode.c/h`, `pb_decode.c/h`
- `nanopb_generator.py`
官方参考:
- nanopb overview
- nanopb generator reference
## 4. 生成代码
### 4.1 生成上位机 C++ 代码
在工程根目录 `C:\Users\lst\Desktop\动态链接库版本\20260320_new_keyboard` 下执行:
```powershell
New-Item -ItemType Directory -Force -Path KeyBorad\generated\cpp | Out-Null
protoc --proto_path=KeyBorad\proto --cpp_out=KeyBorad\generated\cpp KeyBorad\proto\keyboard.proto
```
预期生成:
- `KeyBorad\generated\cpp\keyboard.pb.h`
- `KeyBorad\generated\cpp\keyboard.pb.cc`
### 4.2 生成下位机 nanopb 代码
如果你是 nanopb 源码目录:
```powershell
New-Item -ItemType Directory -Force -Path KeyBorad\generated\nanopb | Out-Null
python third_party\nanopb\generator\nanopb_generator.py --output-dir=KeyBorad\generated\nanopb --strip-path KeyBorad\proto\keyboard.proto
```
如果你用的是 nanopb 二进制包,自带 generator也可以直接换成对应可执行文件。
预期生成:
- `KeyBorad\generated\nanopb\keyboard.pb.h`
- `KeyBorad\generated\nanopb\keyboard.pb.c`
## 5. 先做一处关键改造
### 必改: 不再把 payload 长度当成固定值
一旦 `data` 改成 protobuf`HelloRsp / TimeSync / Ack``data` 长度就不一定再等于今天的固定字节数。
所以迁移时要先把 `COM` 层规则改成:
- `len` 表示 protobuf 载荷实际字节数
- `Com_CdcDecode` 不再用 `type -> 固定 len` 做强校验
- 改成:
- 校验帧头
- 校验 `len`
- 校验 checksum
- 可选校验某个 `type` 的最大长度,不再校验固定长度
建议做法:
- 先保留 `Packet_Type`
- 删除或弱化 `Packet_len` 在解码期的“硬匹配”
- `Packet_len` 只保留给旧协议兼容或做上限参考
## 6. 上位机接入方式
### 6.1 在 VCXPROJ 里加入生成文件
把下面文件加入 `KeyBorad\KeyBorad\KeyBorad.vcxproj`:
- `..\generated\cpp\keyboard.pb.cc`
- `..\generated\cpp\keyboard.pb.h`
并加 include 目录:
- `KeyBorad\generated\cpp`
- protobuf runtime include 目录
再链接 protobuf C++ runtime 库。
### 6.2 改造原则
不要再在 `Com_Cdc.h` 里维护两份字段和 `data`
建议改成两层:
- 原始帧:
`Packet`
- protobuf payload:
`keyboard::cdc::HelloReqPayload` 等生成类
### 6.3 发送流程
`HelloReq` 为例:
1. 创建 `keyboard::cdc::HelloReqPayload`
2. 填字段
3. `SerializeToString()` / `SerializeToArray()`
4. 把序列化结果塞进 `Packet.data`
5. `Packet.type = Com_Type_HelloReq`
6.`Com_Cdc_Func_BuildFrame()` 打外层包
伪代码:
```cpp
keyboard::cdc::HelloReqPayload payload;
payload.set_protocol_version(1);
std::string bytes;
payload.SerializeToString(&bytes);
Packet frame;
frame.type = Com_Type_HelloReq;
frame.data = QByteArray(bytes.data(), static_cast<int>(bytes.size()));
QByteArray tx = Com_Cdc_Func_BuildFrame(frame);
```
### 6.4 接收流程
1. `Dri_Cdc_Read()` 读到完整 CDC 帧
2. `Com_Cdc_Func_ParseFrame()` 拿到 `Packet`
3. 根据 `Packet.type` 选择 protobuf 消息类型
4.`Packet.data``ParseFromArray()`
伪代码:
```cpp
Packet frame;
if (!Com_Cdc_Func_ParseFrame(frameBytes, &frame))
{
return false;
}
if (frame.type == Com_Type_HelloRsp)
{
keyboard::cdc::HelloRspPayload payload;
if (!payload.ParseFromArray(frame.data.constData(), frame.data.size()))
{
return false;
}
}
```
## 7. 下位机接入方式
下位机不要碰外层 CDC 帧格式,直接沿用:
- 帧头
- 长度
- type
- checksum
只把 `type` 对应的 `data` 改成 nanopb 编解码。
建议流程:
1. 串口 ISR / DMA 收满一帧
2. 校验 `AA 55 / len / checksum`
3. 根据 `type``keyboard.pb.h` 里的消息描述符
4. `pb_decode()` 解 protobuf 载荷
5. 业务处理
6. 回复时 `pb_encode()` 出 payload再挂回外层 CDC 帧
## 8. 推荐迁移顺序
建议按下面顺序,不要一次全换:
1. 只提交 `.proto``.options`
2. 上位机先接 `HelloReq / HelloRsp`
3. 下位机把 `HelloReq / HelloRsp` 改成 nanopb
4. 确认握手通了,再迁移 `Ack / Error`
5. 然后迁移 `TimeSync / ThemeRgb`
6. 最后迁移 `Bitmap / FunctionKeyEvent`
原因:
- `HelloReq / HelloRsp` 字段简单,最适合先打通生成链路
- `Bitmap``bytes[29]`,更适合在 `nanopb` 选项验证完后再切
## 9. 你当前 COM 文件还能起什么作用
即使引入 protobuf当前 `COM` 层仍然非常有价值:
- `Com_CdcEncode`
继续负责外层帧打包
- `Com_CdcDecode`
继续负责流式拆包
- `Packet_Type`
继续作为外层快速分发 ID
真正被 protobuf 替代的是:
- 旧的 `data` 手写字段布局
- 手工小端拼 payload 的代码
一句话:
- `COM` 继续保留
-`COM` 不再定义业务 payload 字段
- payload 交给 `.proto`
## 10. 一套最小可复现命令
假设你已经装好 `protoc``nanopb`,最小步骤就是:
```powershell
Set-Location C:\Users\lst\Desktop\动态链接库版本\20260320_new_keyboard
New-Item -ItemType Directory -Force -Path KeyBorad\generated\cpp | Out-Null
protoc --proto_path=KeyBorad\proto --cpp_out=KeyBorad\generated\cpp KeyBorad\proto\keyboard.proto
New-Item -ItemType Directory -Force -Path KeyBorad\generated\nanopb | Out-Null
python third_party\nanopb\generator\nanopb_generator.py --output-dir=KeyBorad\generated\nanopb --strip-path KeyBorad\proto\keyboard.proto
```
然后做两边接入:
1. 上位机 `vcxproj` 加入 `keyboard.pb.cc`
2. 下位机工程加入 `keyboard.pb.c`
3.`HelloReq / HelloRsp``data` 改为 protobuf payload
4.`Com_CdcDecode` 的固定长度校验去掉
## 11. 最后一个关键建议
如果你只是想“共享结构定义”,那就:
- 保留外层 `type`
- 每个 `type` 对应一个 protobuf message
不要一开始就把外层 `type` 也塞进 protobuf `oneof` 里。
原因很简单:
- 外层 `type` 对调试串口抓包很直观
- 下位机分发也更省
- 迁移成本最小