实习笔记(七)GFP自动化插拔测试功能协议解析

一、背景与目标

主机向从机(辅箱 / 插件模块固件)下发自动化插拔测试参数,并查询 / 解析插拔测试状态。协议基于 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

十、注意事项 / 踩坑记录

  1. 字节序必须与固件对齐,本项目小端;发送 / 接收都要小端且对称。
  2. 协议改动先子模块 commit + push fork + 发 MR,父仓库只记指针,等 MR 合并后再更新指针(避免悬空)。
  3. 子模块先 checkout 建分支,不在 detached 下提交。
  4. read_le32 用引用捕获 [&payload,&index],让游标跨调用前进。
  5. 长度 / 子类型校验先行(payload[0]==0x02len>=27),防越界。
  6. 接收解析函数放 public(被 GfpDockDevice 跨类调用,private 编译不过)。
  7. GFP_INFO{} 占位符,与项目风格一致。
  8. 测试环境:真实板子 + 固定设备地址临时验证;正式触发由业务决定。
相关推荐
宁渡AI大模型1 小时前
河南宁渡科技有限公司|宁渡课堂 AI 全栈面试分享,AI 应用开发高频考点汇总
java·c++·人工智能·python·深度学习·神经网络·rag
GG-_-Bond1 小时前
9.1 kv存储内存池模拟面试问题
c++·c
AI吃大瓜1 小时前
人脸检测和行人检测3:C/C++实现YOLOv8 YOLO11 YOLO26人脸检测和人体检测(含源码,可实时检测)
c++·yolov8·人脸检测·人体检测·yolo11·yolo26
Persistent的粽子!2 小时前
C++的内存管理
c++·经验分享·笔记
hetao17338372 小时前
2026-09-13 hetao1733837 的刷题记录
c++·算法
hansang_IR2 小时前
【题解】[JSOI2016] 扭动的回文串
c++·算法
有点。2 小时前
C++03阶段练习(一)
数据结构·c++
刃神太酷啦2 小时前
Redis 核心进阶:哨兵、集群、缓存问题与分布式锁详解----《Hello Redis!》(6)
linux·c语言·数据库·c++·redis·分布式·缓存
郝学胜-神的一滴3 小时前
C++20模板元编程 01:从零吃透模板核心底层逻辑
开发语言·c++·vscode·程序人生·开源