一、背景与目标
主机向从机(辅箱 / 插件模块固件)下发自动化插拔测试参数,并查询 / 解析插拔测试状态。协议基于 GFP 通信,新增消息类型 0x41(AutoPlugTest),其下有子类型:0x01 设置、0x02 查询 / 应答。
二、协议布局
报文 = 15 字节包头 + 负载(payload)
设置指令(0x01)payload(15 字节)
| 偏移 | 内容说明 |
|---|---|
| 0 | 子类型0x01 |
| 1 | 插拔模式 (0x00主机与底座 /0x01辅箱与插件) |
| 2 | 测试开关 (0x00退出 /0x01进入) |
| 3-6 | 插拔次数 (小端) |
| 7-10 | 接入延时秒 (小端) |
| 11-14 | 拔出延时秒 (小端) |
应答(0x02)payload(27 字节)
| 偏移 | 内容说明 |
|---|---|
| 0 | 子类型0x02 |
| 1 | 插拔模块 |
| 2 | 运行模式 |
| 3-6 | 次数设置值 |
| 7-10 | 接入延时设置 |
| 11-14 | 拔出延时设置 |
| 15-18 | 剩余次数实时 |
| 19-22 | 接入延时实时 |
| 23-26 | 拔出延时实时(全部 4 字节) |
三、枚举定义(gfp_types.h)
cpp
// GfpMsgType 枚举里新增消息类型
AutoPlugTest = 0x41,
// 子命令枚举(独立枚举,不加在 GfpMsgType 内)
enum class AutoPlugType : uint8_t {
SetAutoPlugTest = 0x01, // 设置自动化插拔
QueryAutoPlugStatus = 0x02,// 查询/应答状态
};
四、声明(gfppnpdevice.h)
cpp
static constexpr size_t AUTOPLUG_SET_PAYLOAD_LENGTH = 15; // 设置负载长
static constexpr size_t AUTOPLUG_RESP_PAYLOAD_LENGTH = 27; // 应答负载长
void send_set_auto_plug_test(uint8_t plug_mode,uint8_t test_mode_switch,
uint32_t plug_times, uint32_t insert_delay,
uint32_t eject_delay); // 发送设置(public)
void send_query_auto_plug_status(); // 发送查询(public)
void unpack_auto_plug_status(const MessageHead& head, const uint8_t* payload,
std::size_t len); // 解析应答(public)
五、发送设置指令 send_set_auto_plug_test
主要步骤: ① 初始化包头并消息类型 / 地址 / 应答方式 → ② 定负载长、开收包数组 → ③ 填子类型、模式、开关 → ④ 插拔次数 / 延时按小端拆分填入 → ⑤ 算校验和、补包头、发送。
cpp
void GfpPnpDevice::send_set_auto_plug_test(uint8_t plug_mode,
uint8_t test_mode_switch,
uint32_t plug_times,
uint32_t insert_delay,
uint32_t eject_delay) {
// ① 组装包头
MessageHead head = request_msg_head(); // 拿初始化好的包头
head.ack_method = AckMethod::ACK; // 需要应答
head.address = get_address(); // 发给当前设备
head.msg_type = GfpMsgType::AutoPlugTest;// 消息类型0x41
constexpr size_t kHeadLength = MessageHead::length(); // 包头15字节
constexpr uint16_t kPayloadLength = AUTOPLUG_SET_PAYLOAD_LENGTH; // 负载15
head.payload_length = kPayloadLength;
uint8_t packet[kHeadLength + kPayloadLength]; //30字节数组(头+负载)
// ② 填子类型 + 两个单字节参数
packet[kHeadLength + 0] =
static_cast<uint8_t>(AutoPlugType::SetAutoPlugTest); // 子类型0x01
packet[kHeadLength + 1] = plug_mode; // 插拔模式
packet[kHeadLength + 2] = test_mode_switch; // 测试开关
// ③ 插拔次数(小端:低字节在前;>>0/8/16/24 由低到高)
packet[kHeadLength + 3] = static_cast<uint8_t>(plug_times & 0xFF);
packet[kHeadLength + 4] = static_cast<uint8_t>((plug_times >> 8) & 0xFF);
packet[kHeadLength + 5] = static_cast<uint8_t>((plug_times >> 16) & 0xFF);
packet[kHeadLength + 6] = static_cast<uint8_t>((plug_times >> 24) & 0xFF);
// 接入延时(秒,小端)
packet[kHeadLength + 7] = static_cast<uint8_t>(insert_delay & 0xFF);
packet[kHeadLength + 8] = static_cast<uint8_t>((insert_delay >> 8) & 0xFF);
packet[kHeadLength + 9] = static_cast<uint8_t>((insert_delay >> 16) & 0xFF);
packet[kHeadLength + 10] = static_cast<uint8_t>((insert_delay >> 24) & 0xFF);
// 拔出延时(秒,小端)
packet[kHeadLength + 11] = static_cast<uint8_t>(eject_delay & 0xFF);
packet[kHeadLength + 12] = static_cast<uint8_t>((eject_delay >> 8) & 0xFF);
packet[kHeadLength + 13] = static_cast<uint8_t>((eject_delay >> 16) & 0xFF);
packet[kHeadLength + 14] = static_cast<uint8_t>((eject_delay >> 24) & 0xFF);
// ④ 校验和 / 补包头 / 发送
head.check_sum =
GfpDevice::calc_check_sum(&packet[kHeadLength], kPayloadLength);
GfpDevice::padding_head(packet, kHeadLength, head);
send_packet(packet, sizeof(packet), head);
}
六、发送查询指令 send_query_auto_plug_status
主要步骤 :① 组装包头 → ② 负载长度 = 1(仅子类型)→ ③ 填子类型0x02 → ④ 校验和 / 补包头 / 发送。(无整数,无字节序转换)
cpp
void GfpPnpDevice::send_query_auto_plug_status() {
// ① 组装包头
MessageHead head = request_msg_head();
head.ack_method = AckMethod::ACK;
head.address = get_address();
head.msg_type = GfpMsgType::AutoPlugTest;
constexpr size_t kHeadLength = MessageHead::length();
constexpr uint16_t kPayloadLength = 1; // ② 负载仅子类型1字节
head.payload_length = kPayloadLength;
uint8_t packet[kHeadLength + kPayloadLength];
// ③ 填子类型0x02
packet[kHeadLength + 0] =
static_cast<uint8_t>(AutoPlugType::QueryAutoPlugStatus);
// ④ 校验和 / 补包头 / 发送
head.check_sum =
GfpDevice::calc_check_sum(&packet[kHeadLength], kPayloadLength);
GfpDevice::padding_head(packet, kHeadLength, head);
send_packet(packet, sizeof(packet), head);
}
七、解析应答 unpack_auto_plug_status
主要步骤 :① 空指针保护 → ② 校验子类型0x02(不是则丢弃)→ ③ 校验长度≥27 → ④ 读模块、模式(各 1 字节)→ ⑤ 用read_le32小端连读 6 个整数(游标推进)→ ⑥ 打日志。
cpp
void GfpPnpDevice::unpack_auto_plug_status(const MessageHead& head,
const uint8_t* payload,
std::size_t len) {
// ① 空指针保护
if (get_dock() == nullptr || payload == nullptr) {
return;
}
// ② 判断子类型是否为查询应答
if (payload[0] != static_cast<uint8_t>(AutoPlugType::QueryAutoPlugStatus)) {
return;
}
constexpr size_t kRespLen = AUTOPLUG_RESP_PAYLOAD_LENGTH;
// ③ 校验负载长度
if (len < kRespLen) {
GFP_ERROR("auto plug status resp payload length error! expected={}, actual={}",
kRespLen, len);
return;
}
// ④ 读插拔模块、运行模式(各1字节),游标从1开始(跳过子类型)
size_t index = 1;
uint8_t plug_module = payload[index++];
uint8_t run_mode = payload[index++];
// ⑤ 小端读取4字节整数,每读一次游标前进4
auto read_le32 = [&payload, &index](uint32_t* out) {
*out = static_cast<uint32_t>(payload[index]) |
(static_cast<uint32_t>(payload[index + 1]) << 8) |
(static_cast<uint32_t>(payload[index + 2]) << 16) |
(static_cast<uint32_t>(payload[index + 3]) << 24);
index += 4;
};
uint32_t set_plug_times = 0;
uint32_t set_insert_delay = 0;
uint32_t set_eject_delay = 0;
uint32_t remain_times = 0;
uint32_t real_insert_delay = 0;
uint32_t real_eject_delay = 0;
read_le32(&set_plug_times); // 读 3-6
read_le32(&set_insert_delay); // 读 7-10
read_le32(&set_eject_delay); // 读 11-14
read_le32(&remain_times); // 读 15-18
read_le32(&real_insert_delay); // 读 19-22
read_le32(&real_eject_delay); // 读 23-26
// ⑥ 记录解析结果
GFP_INFO(
"[AUTO_PLUG_STATUS] addr={}, module={}, run_mode={}, "
"set_times={}, set_insert_delay={}, set_eject_delay={}, "
"remain_times={}, real_insert_delay={}, real_eject_delay={}",
get_address(), plug_module, run_mode, set_plug_times, set_insert_delay,
set_eject_delay, remain_times, real_insert_delay, real_eject_delay);
}
八、接收分发链(gfpdockdevice.cpp / .h)
cpp
// 构造函数注册:
{GfpMsgType::AutoPlugTest,
[this](const MessageHead &head, const uint8_t *buf, std::size_t len) {
do_auto_plug_unpack(head, buf, len);
}},
// 实现(仿 do_log_info_unpack,.h 需声明 do_auto_plug_unpack):
void GfpDockDevice::do_auto_plug_unpack(const MessageHead &head,
const uint8_t *payload,
std::size_t len) {
if (GfpDevice::address_valid(head.address)) {
auto device = find_pnp_device(head.address);
if (device) {
device->unpack_auto_plug_status(head, payload, len);
} else {
GFP_ERROR("addr:{}, device not exist!", head.address);
return;
}
}
}
九、大小端转换(重点)
- 项目统一小端 (参考老化测试
&0xFF, >>8, >>16, >>24)。 - 发送(整→字节):
&0xFF, >>8, >>16, >>24(低字节在前)。 - 接收(字节→整):首字节不移位,
<<8, <<16, <<24。
十、注意事项 / 踩坑记录
- 字节序必须与固件对齐,本项目小端;发送 / 接收都要小端且对称。
- 协议改动先子模块 commit + push fork + 发 MR,父仓库只记指针,等 MR 合并后再更新指针(避免悬空)。
- 子模块先 checkout 建分支,不在 detached 下提交。
read_le32用引用捕获[&payload,&index],让游标跨调用前进。- 长度 / 子类型校验先行(
payload[0]==0x02、len>=27),防越界。 - 接收解析函数放 public(被 GfpDockDevice 跨类调用,private 编译不过)。
GFP_INFO用{}占位符,与项目风格一致。- 测试环境:真实板子 + 固定设备地址临时验证;正式触发由业务决定。