欢迎光临
我们一直在努力

基于 Qt 5.12.2 实现 CAN 总线数据流向可视化(线条光点联动篇)

引言

本文是我在学习 Qt 开发和 CAN 总线应用过程中的实践总结,基于 Qt 5.12.2 开发 CAN 总线数据分析软件。本文将聚焦软件核心功能 ——CAN 总线数据与 QGraphicsScene 线条光点的精准联动,详细说明如何通过唯一标识匹配数据流向,并展示当前功能进度与后续优化方向。代码和思路还有很多可以优化的地方,欢迎各位大佬在评论区多多指点,一起交流进步。

效果展示

一、软件整体架构与核心功能规划

1. 整体界面布局设计

软件采用 **“左 – 中 – 下 – 右” 四段式布局 **,结合 QGraphicsScene 图形化能力与规则配置逻辑,实现 “协议规则查看 – 可视化画布 – 数据日志 – 参数配置” 的完整工作流:

区域核心控件核心功能
左侧:协议规则区 QTreeWidget 展示 CAN 协议规则(帧 ID、协议标识、数据范围),为右侧配置提供唯一标识依据
中间:可视化画布 QGraphicsScene + QGraphicsView 核心展示区,承载 CAN 通道、电气设备、数据控件节点,通过蓝色线条连接节点,在指定线条上显示数据流向光点
底部:设备数据区 QTableWidget 实时记录 CAN 数据接收日志(时间、端口、帧 ID、原始数据),便于调试与追溯
右侧:信息配置区 QListWidget + QTableWidget 上半区展示画布中选中节点;下半区配置节点唯一标识、协议属性、收发关系,确保数据精准流向对应线条

2. 核心功能实现进度

✅ 已实现功能:

  • CAN 总线数据接收:支持配置 CAN 通道(如 can1),接收指定帧 ID 的总线数据并记录日志;
  • 线条唯一标识生成:节点间连线时,自动生成独立识别名称(格式:起点设备名_终点设备名),存储于公共 QList 中;
  • 光点流向匹配:接收 CAN 数据后,遍历公共线条列表,通过唯一标识匹配对应线条,在该线条上生成流动光点;
  • 数据控件实时更新:光点流经后,同步更新数据显示控件数值(如 5 V、64 RPM);
  • 唯一标识配置:右侧面板支持为 CAN 通道、电气设备、数据控件配置唯一标识,与左侧协议规则一一绑定。
  • ⏳ 待完善功能:

  • 大数据量光点优化:解决高频 CAN 数据的光点堆积、界面卡顿问题;
  • CAN 数据发送功能:完善主动发送逻辑,包括电气设备定时发送协议、手动触发单节点发送数据;
  • 发送数据可视化:为发送数据新增专属光点样式(如颜色区分收发),直观展示数据流向;
  • 异常数据高亮:对超出范围的异常数据,在光点和数据控件上做高亮警示。
  • 二、核心技术实现

    环境准备

    • Qt 版本:5.12.2(MinGW 7.3.0 32/64 位,兼容工业级开发环境);
    • 核心模块:QtWidgets(界面)、QtCore(线程、容器)、QtSerialPort/SocketCAN(CAN 通信);
    • 关键约束:避免使用 C++11 auto 关键字,确保与 Qt 5.12.2 兼容。

    模块 1:线条唯一标识与公共存储

    核心逻辑:节点拖拽连线时,自动生成 起点设备名_终点设备名 格式的唯一标识,存入全局 QList,为后续数据匹配提供依据。

    1. 生成线缆唯一ID(LinkItem.h)

    //生成线缆唯一ID(基于两端节点设备名)
    void LinkItem::generateLinkId()
    {
    if (!m_startNode || !m_endNode) {
    qWarning() << "[LinkItem] 起点/终点节点为空,无法生成唯一ID!";
    return;
    }
    // 格式:起点设备名_终点设备名(保证全局唯一)
    QString startName = m_startNode->getDeviceChart();
    QString endName = m_endNode->getDeviceChart();
    QString autoLinkId = QString("%1_%2").arg(startName).arg(endName);
    setLinkId(autoLinkId);
    }

    2. 匹配CAN接口线缆的光点(GraphicData.h)

    bool GraphicData::upData_GraphicPublicData_LinkItem(const QString& port, const QString& id, const QString &canData)
    {
    Q_UNUSED(canData);
    //判断MClassCanInfo公共数据中port:并进行匹配
    if(MClassCanInfo.contains(port)){
    //取出关于CAN接口下存储的所有ClassCanInfo
    QList<GraphicCanClass *> LGraphicCanClass = MClassCanInfo[port];
    for (int i = 0; i < LGraphicCanClass.size(); i++){
    //取出单个ClassCanInfo
    GraphicCanClass * graphicCanClass = LGraphicCanClass.at(i);
    //判断ID是匹配
    if(graphicCanClass->Name == id){
    //循环遍历LLinkItem中的存储线条
    for (int i = 0; i < LLinkItem.size(); i++) {
    //ID判断:获取线条ID,并匹配->graphicCanClass中存储的信息
    if(LLinkItem.at(i)->getLinkId() == graphicCanClass->sender+"_"+graphicCanClass->recipient
    || LLinkItem.at(i)->getLinkId() == graphicCanClass->recipient+"_"+graphicCanClass->sender){
    //获取线条中的起始节点
    GraphicNode_Bus* graphicNode = LLinkItem.at(i)->getStartNode();
    //获取该节点的widget类型
    if(getWidgetObjectName(graphicNode->getDeviceChart()) == "Can_Port_Chart"){
    //判断:如果是Device_Chart则正向,反之
    if(LLinkItem.at(i)->addAndStartLightPoint(id, graphicCanClass->timeN.toInt(), QColor(0,255,0), Direction_Forward,1,id)){
    return true;
    }
    }
    else{
    if(LLinkItem.at(i)->addAndStartLightPoint(id,graphicCanClass->timeN.toInt(),QColor(0,255,0),Direction_Backward,1,id)) {
    return true;
    }
    }
    }
    }
    return true;
    }
    }
    }
    return false;
    }

    3. 匹配数据控件线缆的光点(GraphicData.h)

    void GraphicData::upData_GraphicPublicData_LightPoint()
    {
    QMap<QString, QList<Result_info> > MResult_info;
    MResult_info = PublicRulesCollector::getInstance().getTempRulesInFo();
    QMap<QString,QList<Result_info>>::iterator itor1;
    for (itor1 = MResult_info.begin(); itor1 != MResult_info.end(); ++itor1){
    QList<Result_info> LResult_info = itor1.value();
    for(int i = 0; i < LResult_info.size(); i++){
    Result_info result_info = LResult_info.at(i);
    //4. 遍历视图中的widget中的code名称并进行匹配
    QMap<QString,QWidget*>::iterator it3;
    for (it3 = MWidget.begin(); it3 != MWidget.end(); ++it3){
    QWidget* targetWidget = it3.value();
    if (Can_Port_Chart *can_port_chart = qobject_cast<Can_Port_Chart*>(targetWidget)){
    //匹配code进行数据更新
    if(result_info.code == can_port_chart->getCodeName()){
    //更新控件数据
    }
    }
    if (Device_Chart *deviceChart = qobject_cast<Device_Chart*>(targetWidget)){
    if(result_info.code == deviceChart->getCodeName()){
    //更新控件数据
    }
    }
    else if(Dashboard_Chart *DashboardChart = qobject_cast<Dashboard_Chart*>(targetWidget)){
    if(result_info.code == DashboardChart->getCodeName())
    {
    //更新控件数据
    }
    }else if(State_Chart *StateChart = qobject_cast<State_Chart*>(targetWidget)){
    if(result_info.code == StateChart->getCodeName())
    {
    //更新控件数据
    }
    }
    else if(Value_Chart *ValueChart = qobject_cast<Value_Chart*>(targetWidget)){

    if(result_info.code == ValueChart->getCodeName()){
    for (int i = 0; i < LLinkItem.size(); i++) {

    if(LLinkItem.at(i)->getLinkId().contains(ValueChart->getTiteText())){
    //获取线条中的起始节点
    GraphicNode_Bus* graphicNode = LLinkItem.at(i)->getStartNode();
    //获取该节点的widget类型
    if(getWidgetObjectName(graphicNode->getDeviceChart()) == "Device_Chart") {
    //判断:如果是Device_Chart则正向,反之
    if(LLinkItem.at(i)->addAndStartLightPoint(result_info.code,900,QColor(0,255,0),Direction_Forward, 1,result_info.code)){
    break;
    }
    }
    else{
    if(LLinkItem.at(i)->addAndStartLightPoint(result_info.code,900,QColor(0,255,0),Direction_Backward, 1,
    result_info.code)) {
    break;
    }
    }
    }
    }
    }
    }
    }
    }
    }

    }

    三、功能演示与效果说明

    结合代码实现,软件运行效果与截图完全匹配:

  • 协议规则展示:左侧树清晰展示 14FF02F7 等帧 ID 下的协议标识(如 24V_Voltage、Fan_Speed);
  • 线条光点流动:接收 CAN 数据后,对应线条上出现绿色光点,从发送设备流向接收设备,1 秒内完成一次流动;
  • 数据控件更新:光点流经后,数据控件实时显示解析后数值(如 5 V、64 RPM);
  • 数据日志记录:底部表格实时记录每条 CAN 数据的接收时间、端口、帧 ID 和原始数据;
  • 配置联动:右侧配置唯一标识后,CAN 数据可精准匹配到对应线条,避免流向混乱。
  • 四、后续功能优化方向

    1. 大数据量光点显示优化

    • 数据聚合:相同协议数据,仅触发一次光点动画,避免界面卡顿;
    • 超时清理:光点动画结束后自动删除,避免堆积;
    • 高频高亮:高频数据对应的光点使用红色高亮,突出数据频率。

    2. CAN 数据发送功能完善

    • 定时发送:为电气设备配置发送周期,自动发送指定协议数据;
    • 手动触发:在数据控件上添加 “发送” 按钮,手动触发单条 CAN 数据发送;
    • 发送可视化:发送数据的光点使用橙色,与接收数据的绿色光点区分,直观展示收发流向。

    3. 性能与体验优化

    • 线程分离:将 CAN 数据接收、解析放入独立线程,避免阻塞主线程;
    • 配置持久化:优化节点配置、线条关系保存为 JSON 文件,下次启动自动加载;
    • 异常提示:对未配置唯一标识的节点,在界面上给出红色警告提示。

    五、总结

    本文基于 Qt 5.12.2 实现了 CAN 总线数据与 QGraphicsScene 线条光点的精准联动,通过唯一标识匹配和公共线条列表遍历,解决了数据流向可视化的核心问题。当前软件已完成基础接收与展示功能,后续将聚焦大数据量优化与主动发送功能,进一步提升工业级场景下的实用性。

    本文旨在为同领域开发者提供可复用的实现思路,也欢迎各位在评论区交流 Qt 图形化开发、CAN 总线通信的技术细节,共同完善工业级数据可视化工具。

    赞(0)
    未经允许不得转载:171主机测评 » 基于 Qt 5.12.2 实现 CAN 总线数据流向可视化(线条光点联动篇)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址