实习笔记(七)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]==0x02、len>=27),防越界。
  6. 接收解析函数放 public(被 GfpDockDevice 跨类调用,private 编译不过)。
  7. GFP_INFO 用 {} 占位符,与项目风格一致。
  8. 测试环境:真实板子 + 固定设备地址临时验证;正式触发由业务决定。
相关推荐
潼心1412o1 小时前
C++初阶(长期更新)第6讲:类和对象(下)
开发语言·c++·算法
-森屿安年-1 小时前
按身高排序
c++·算法
(Charon)2 小时前
【C++面试】vector底层原理:扩容、拷贝移动与迭代器失效
c++·面试
朔北之忘 Clancy3 小时前
青少年软编等考七级题解目录
开发语言·c++·青少年编程·gesp·csp·信奥赛·noi
汉克老师3 小时前
GESP2026年9月认证C++七级( 第一部分选择题(8~15题)精讲
c++·gesp·小学生·学c++编程
Mr_Macallon3 小时前
C++回顾(基础)(01)
c语言·开发语言·c++·学习
如意猴4 小时前
【C++】007 C/C++ 内存管理机制、malloc与new的区别及模板初阶
java·c++·算法
朝朝辞暮i4 小时前
C++ 第 32 课:main()、rclcpp::init()、spin()、shutdown()
开发语言·c++·算法
汉克老师4 小时前
GESP2026年9月认证C++一级( 第三部分编程题(1、新龟兔赛跑))精讲
c++·gesp·小学生·学c++编程
沫璃染墨4 小时前
《从零入门Linux系统篇(五十七):线程篇·十——生产者消费者模型进阶:从环形缓冲区到POSIX信号量》
linux·运维·服务器·开发语言·c++·系统架构·信号处理