一、任务目标实现固件通信丢包率统计功能0x31‑0x13接收固件上报的丢包数据并记录到日志。二、整体框架2.1 消息协议格式完整消息包从第 16 字节开始字节位置含义说明字节 160x13子命令类型通信丢包率统计字节 170x00/0x01查询 / 设置标志0x00 查询0x01 设置字节 18N丢包项数字节 19 开始N×5 字节每一条丢包项1 字节 ID 4 字节大端计数值示例报文0x13 0x00 0x02 0x01 0x00 0x00 0x00 0x050x13子命令0x00查询0x02一共 2 条统计项第 1 项 ID0x01计数值 52.2 丢包 ID 定义ID名称含义0x01HostParseError主机数据包解析错误0x02HostRecvQueueError主机数据接收队列错误0x03HostSendQueueError主机数据发送队列错误说明这里的 Host指仪器主机统计的是仪器和外接辅箱之间通信错误不是电脑上位机。2.3 消息类型定义enum class GfpMsgType : uint8_t { SystemCmd 0x31, //系统命令 //...其他类型 };2.4 三层处理架构消息接收流向收到0x31SystemCmd 消息第一层 do_sys_unpack (gfpdockdevice.cpp)判断消息类型是不是 0x31第二层 unpack_sys_cmd (gfppnpdevice.cpp)判断子命令是不是 0x13 丢包统计第三层 handle_comm_packet_loss_stats (gfppnpdevice.cpp)真正解析丢包数据输出日志三、代码实现3.1 枚举定义/** * brief 系统命令类型枚举 * details 用于0x31消息的子命令类型标识 */ enum class SystemCmdType : uint8_t { CommPacketLossStats 0x13, //通信丢包率统计 //...其他类型 };3.2 常量定义//通信丢包统计相关常量 static constexpr int PACKET_LOSS_QUERY_PAYLOAD_LENGTH 2; //查询指令负载长度 //丢包信息ID常量 static constexpr int PACKET_LOSS_ID_HOST_PARSE_ERROR 0x01; //主机数据包解析错误 static constexpr int PACKET_LOSS_ID_HOST_RECV_QUEUE_ERROR 0x02; //主机接收队列错误 static constexpr int PACKET_LOSS_ID_HOST_SEND_QUEUE_ERROR 0x03; //主机发送队列错误3.3 函数声明class GfpPnpDevice { public: //系统命令解包函数 virtual void unpack_sys_cmd(const MessageHead head, const uint8_t* payload, std::size_t len); //丢包率数据处理函数 void handle_comm_packet_loss_stats(const MessageHead head, const uint8_t* payload, std::size_t len); //发送查询指令函数 void send_query_packet_loss_stats(); //...其他函数 };class GfpDockDevice { public: //系统命令解包分发函数 virtual void do_sys_unpack(const MessageHead head, const uint8_t* payload, std::size_t len); //...其他函数 };3.4 消息处理器注册在 GfpDockDevice 构造函数里面注册消息回调msg_handler_map_ { {GfpMsgType::SystemCmd, [this](const MessageHead head, const uint8_t* buf, std::size_t len) { do_sys_unpack(head, buf, len); }}, //...其他消息处理器 };3.5 Dock 层分发函数 do_sys_unpack/** * brief 系统命令解包分发函数 * details 判断消息类型分发到对应PnP设备处理 */ void GfpDockDevice::do_sys_unpack(const MessageHead head, const uint8_t* payload, std::size_t len) { //检查消息类型是否0x31 SystemCmd if (head.msg_type ! GfpMsgType::SystemCmd) { return; } //找到对应的PnP设备 if (auto* pnp get_pnp(head.address)) { pnp-unpack_sys_cmd(head, payload, len); } }3.6 子命令分发函数 unpack_sys_cmd/** * brief 系统命令解包处理函数 * details 解析系统命令子命令类型分发到具体处理函数 * param head 消息包头 * param payload 负载数据指针从第16字节开始 * param len 负载数据长度从第16字节算 */ void GfpPnpDevice::unpack_sys_cmd(const MessageHead head, const uint8_t* payload, std::size_t len) { //步骤1检查Dock设备是否存在 if (get_dock() nullptr) { return; } //步骤2检查payload指针有效性 if (payload nullptr) { return; } //步骤3读取子命令类型payload[0]对应原始第16字节子命令0x13 uint8_t msg_type payload[0]; //步骤4判断子命令是否为丢包统计0x13 if (msg_type ! static_castuint8_t(SystemCmdType::CommPacketLossStats)) { GFP_WARN(unknown system cmd type: 0x%02x, msg_type); return; } //步骤5包长校验至少3字节子命令查询标志项数 if (len 3) { GFP_ERROR(packet loss stats payload too short! len%zu, len); return; } //步骤6指针偏移payload1跳过子命令(第16字节)指向第17字节 handle_comm_packet_loss_stats(head, payload 1, len - 1); }3.7 丢包率数据处理函数 handle_comm_packet_loss_stats/** * brief 处理通信丢包率统计信息 * details 解析固件发送的丢包统计数据记录到日志文件 * * 数据包格式传入的payload从第17字节开始 * 字节0命令类型(0x00查询0x01设置) * 字节1丢包信息项数 N * 字节2开始N个丢包项每项5字节 * -1字节丢包信息ID * -4字节丢包计数值大端字节序 * * param head 消息包头 * param payload 负载指针已经偏移到第17字节 * param len 负载长度 */ void GfpPnpDevice::handle_comm_packet_loss_stats(const MessageHead head, const uint8_t* payload, std::size_t len) { //步骤1解析命令类型、项数 uint8_t cmd_type payload[0]; //第17字节查询/设置 uint8_t item_count payload[1]; //第18字节多少条统计项 GFP_INFO(Device addr:%d - Packet Loss Stats, cmd_type%u, item_count%u, get_address(), cmd_type, item_count); //步骤2校验包长度头部占2字节每一条占5字节 size_t expected_len 2 (item_count * 5); if (len expected_len) { GFP_ERROR( packet loss stats payload length error! expected%zu, actual%zu, expected_len, len); return; } //步骤3构建PAM总日志 char log_buf[512]; snprintf(log_buf, sizeof(log_buf), [COMM_PACKET_LOSS] addr%d, cmd_type%u, items%u, get_address(), cmd_type, item_count); LOG_PAM_FILE(log_buf); //步骤4循环解析每一条丢包统计项 for (uint8_t i 0; i item_count; i) { //偏移跳过前面2字节(cmd_type item_count)每一项占5字节 size_t offset 2 (i * 5); //4.1 获取丢包ID1字节 uint8_t loss_id payload[offset]; //4.2 4字节大端转本地小端 uint32_t loss_count (static_castuint32_t(payload[offset 1]) 24) | (static_castuint32_t(payload[offset 2]) 16) | (static_castuint32_t(payload[offset 3]) 8) | static_castuint32_t(payload[offset 4]); //4.3 ID转可读字符串 const char* loss_name ; switch (loss_id) { case PACKET_LOSS_ID_HOST_PARSE_ERROR: loss_name HostParseError; break; case PACKET_LOSS_ID_HOST_RECV_QUEUE_ERROR: loss_name HostRecvQueueError; break; case PACKET_LOSS_ID_HOST_SEND_QUEUE_ERROR: loss_name HostSendQueueError; break; default: loss_name Unknown; break; } //4.4 GFP控制台日志 GFP_INFO( [%s] count%u, loss_name, loss_count); //4.5 写入PAM日志文件 char item_buf[128]; snprintf(item_buf, sizeof(item_buf), loss_id%u, count%u, loss_id, loss_count); LOG_PAM_FILE(item_buf); } }3.8 发送查询指令函数 send_query_packet_loss_stats/** * brief 发送查询丢包率统计指令对外接口 * details 供外部调用定时器/前端按钮 * 调用后固件会应答上报丢包统计数据包 */ void GfpPnpDevice::send_query_packet_loss_stats() { //步骤1构建消息头 MessageHead head request_msg_head(); head.ack_method AckMethod::ACK; //需要固件应答 head.address get_address(); head.msg_type GfpMsgType::SystemCmd; //消息类型0x31 constexpr size_t kHeadLength MessageHead::length(); constexpr uint16_t kPayloadLength PACKET_LOSS_QUERY_PAYLOAD_LENGTH; head.payload_length kPayloadLength; //步骤2构建数据包负载 uint8_t packet[kHeadLength kPayloadLength]; packet[kHeadLength 0] static_castuint8_t(SystemCmdType::CommPacketLossStats); //子命令0x13 packet[kHeadLength 1] 0x00; //0x00代表查询 GFP_INFO(dock is {} address is {}, send_query_packet_loss_stats, get_dock_name(), get_address()); //步骤3计算校验和、填充头部 head.check_sum GfpDevice::calc_check_sum(packet[kHeadLength], kPayloadLength); GfpDevice::padding_head(packet, kHeadLength, head); //步骤4发送数据包 send_packet(packet, sizeof(packet), head); }四、文件修改清单文件修改内容gfp_types.h添加枚举SystemCmdType::CommPacketLossStats 0x13gfppnpdevice.h添加常量定义、函数声明gfppnpdevice.cpp实现unpack_sys_cmd、handle_comm_packet_loss_stats、send_query_packet_loss_statsgfpdockdevice.h声明do_sys_unpack函数gfpdockdevice.cpp注册SystemCmd(0x31)消息处理器实现do_sys_unpack分发函数五、数据流向发送查询指令APP→固件send_query_packet_loss_stats()组装0x31‑0x13‑0x00查询数据包send_packet()发送给固件接收固件应答固件→APP固件上报0x31消息msg_handler_map_匹配 SystemCmd 回调do_sys_unpack()第一层分发GfpPnpDevice::unpack_sys_cmd()第二层子命令判断GfpPnpDevice::handle_comm_packet_loss_stats()第三层解析解析每条统计项输出 GFP 日志 PAM 磁盘日志六、日志输出示例GFP 控制台日志[INFO] dock is Dock address is 1, send_query_packet_loss_stats [INFO] Device addr:1 - Packet Loss Stats, cmd_type0, item_count2 [INFO] [HostParseError] count5 [INFO] [HostRecvQueueError] count3PAM 日志文件输出[COMM_PACKET_LOSS] addr1, cmd_type0, items2 loss_id1, count5 loss_id2, count3七、关键知识点7.1 字节序转换固件传输使用大端字节序ARM CPU 本地是小端必须手动移位转换。uint32_t loss_count (payload[0] 24) | (payload[1] 16) | (payload[2] 8) | payload[3];7.2 指针偏移unpack_sys_cmd传入的payload指向原始第 16 字节子命令 0x13handle_comm_packet_loss_stats(head, payload 1, len -1)payload1指针向后偏移 1 字节指向原始第 17 字节供解析函数使用。7.3 循环解析变长数组项数item_count是固件告诉我们一共有多少条统计每条固定 5 字节。for (uint8_t i 0; i item_count; i) { size_t offset 2 (i * 5); //跳过前面2字节头部每一项5字节 //解析第i条数据 }八、测试验证编译验证./nx-build.sh -p rk3568 -m arcadia链接验证看符号是否编译进程序grep packet_loss build/build-rk3568-arcadia-Release/src/app/nxcore/nxcore.map部署测试#ssh登录设备 systemctl restart nxcore nxapp #实时过滤查看pam日志 tail -f /var/log/nxcore/device.pam.log | grep COMM_PACKET_LOSS