C++/Qt +OPCDA + ModbusTcp + 折线图 多线程实时采集数据

发布时间:2026/7/28 1:29:39
C++/Qt +OPCDA + ModbusTcp + 折线图 多线程实时采集数据 ✔ 基于 OPC DA 的多通道数据采集✔ 使用 Qt 多线程实现不卡 UI✔ 实时数据显示 曲线绘制目录1. OPC COM 初始化2. 创建 Host 并连接 OPC Server3. 创建 Group 并配置参数4. 批量添加 Item5. 定时轮询读取数据6. 跨线程通信与类型注册7. 图表初始化8. 数据接收与曲线更新信号槽中的处理9. UI 控制按钮与启动/停止运行效果如下数据如下完整代码如下1. OPC COM 初始化// OPCDaTDWork::initOPC() 中 COPCClient::init(APARTMENTTHREADED);2. 创建 Host 并连接 OPC Server// OPCDaTDWork::initOPC() 中 m_host COPCClient::makeHost(L127.0.0.1); if (m_host nullptr) { qDebug() Failed to create host; return false; } m_server m_host-connectDAServer(LKepware.KEPServerEX.V6); if (m_server nullptr) { delete m_host; qDebug() Failed to create m_server; return false; }3. 创建 Group 并配置参数// OPCDaTDWork::initOPC() 中 unsigned long refreshRate 500; m_group m_server-makeGroup(LTest01, false, 500, refreshRate, 0.0); // 参数含义组名是否异步回调更新周期实际刷新率输出死区4. 批量添加 Item// OPCDaTDWork::initOPC() 中 std::vectorstd::wstring names; names.push_back(LModbus.Test.temp); names.push_back(LModbus.Test.temp2); names.push_back(LModbus.Test.temp3); names.push_back(LModbus.Test.temp4); names.push_back(LModbus.Test.temp5); std::vectorCOPCItem* items; std::vectorHRESULT errors; int errCnt m_group-addItems(names, items, errors, true); if (errCnt 0) { qDebug() Failed to add errCnt items; delete m_server; delete m_host; return false; } m_items items;5. 定时轮询读取数据// OPCDaTDWork 构造函数中 m_time new QTimer(this); connect(m_time, QTimer::timeout, this, OPCDaTDWork::readData); // OPCDaTDWork::startWork() 中 if (m_time) { m_time-setInterval(500); m_time-start(); } // OPCDaTDWork::readData() 完整实现 void OPCDaTDWork::readData() { datas.clear(); std::vectorOPCItemData dataList(m_items.size()); for (int i 0; i m_items.size(); i) { HRESULT hr m_items[i]-readSync(dataList[i], OPC_DS_DEVICE); if (FAILED(hr) || dataList[i].wQuality ! OPC_QUALITY_GOOD) { qDebug() Read failed: QString::fromStdWString(m_items[i]-getName()); } } int i 0; for (const auto data : dataList) { VARIANT val data.vDataValue; datas.append(val.iVal); // 假设数据类型为 short i; } emit dataUpdated(datas); }6. 跨线程通信与类型注册// OPCDaASyncPollot 构造函数中 qRegisterMetaTypeQVectorshort(); connect(m_work, OPCDaTDWork::dataUpdated, this, [](const QVectorshort datas) { // 更新界面见功能点 8 }); connect(this, OPCDaASyncPollot::startWork, m_work, OPCDaTDWork::startWork); connect(this, OPCDaASyncPollot::stopWork, m_work, OPCDaTDWork::stopWork);7. 图表初始化// OPCDaASyncPollot::initChartView() 完整实现 void OPCDaASyncPollot::initChartView() { chartView new QtCharts::QChartView(this); chart new QtCharts::QChart(); line01 new QtCharts::QLineSeries(); line02 new QtCharts::QLineSeries(); line03 new QtCharts::QLineSeries(); line04 new QtCharts::QLineSeries(); line05 new QtCharts::QLineSeries(); line01-setColor(QColor(200, 100, 100)); line02-setColor(QColor(100, 200, 100)); line03-setColor(QColor(100, 100, 200)); line04-setColor(QColor(200, 200, 100)); line05-setColor(QColor(100, 200, 200)); line02-setName(temp01); // 注意这里重复调用 setName实际应分别设置 line02-setName(temp02); line02-setName(temp03); line02-setName(temp04); line02-setName(temp05); QtCharts::QValueAxis *axisX new QtCharts::QValueAxis(); axisX-setRange(0, 100); axisX-setLabelFormat(%d); axisX-setTitleText(Sample-points); QtCharts::QValueAxis *axisY new QtCharts::QValueAxis(); axisY-setRange(0, 100); axisY-setLabelFormat(%d); axisY-setTitleText(value); chart-addAxis(axisX, Qt::AlignBottom); chart-addAxis(axisY, Qt::AlignLeft); chart-addSeries(line01); chart-addSeries(line02); chart-addSeries(line03); chart-addSeries(line04); chart-addSeries(line05); line01-attachAxis(axisX); line01-attachAxis(axisY); line02-attachAxis(axisX); line02-attachAxis(axisY); line03-attachAxis(axisX); line03-attachAxis(axisY); line04-attachAxis(axisX); line04-attachAxis(axisY); line05-attachAxis(axisX); line05-attachAxis(axisY); line01-clear(); line02-clear(); line03-clear(); line04-clear(); line05-clear(); QVectorQPointF points; points.reserve(100); for (int i 0; i 5; i) { points.clear(); for (int j 0; j 100; j) { points.push_back(QPointF(j, m_datas[i][j])); } switch (i) { case 0: line01-replace(points); break; case 1: line02-replace(points); break; case 2: line03-replace(points); break; case 3: line04-replace(points); break; case 4: line05-replace(points); break; default: break; } } chart-update(); chartView-setChart(chart); chartView-setGeometry(50, 150, 1000, 800); chartView-show(); }8. 数据接收与曲线更新信号槽中的处理// OPCDaASyncPollot 构造函数中的 connect 部分 connect(m_work, OPCDaTDWork::dataUpdated, this, [](const QVectorshort datas) { qDebug() OPCDaASyncPollot 收到数据更新信号最新数据; for (int i 0; i datas.size(); i) { qDebug() 通路 i 1 最新数据: datas[i]; m_datas[i].push_back(datas[i]); if (m_datas[i].size() 100) { m_datas[i].pop_front(); } QVectorQPointF points; points.reserve(100); for (int i 0; i 5; i) { points.clear(); for (int j 0; j 100; j) { points.push_back(QPointF(j, m_datas[i][j])); } switch (i) { case 0: line01-replace(points); break; case 1: line02-replace(points); break; case 2: line03-replace(points); break; case 3: line04-replace(points); break; case 4: line05-replace(points); break; default: break; } } } });9. UI 控制按钮与启动/停止// OPCDaASyncPollot 构造函数中 btn_Auto new QPushButton(this); btn_Auto-setText(tr(自动采集)); btn_Auto-move(50, 50); connect(btn_Auto, QPushButton::clicked, this, []() { if (m_work) { emit startWork(); btn_Auto-setEnabled(false); btn_Manual-setEnabled(true); } }); btn_Manual new QPushButton(this); btn_Manual-setText(tr(停止采集)); btn_Manual-move(200, 50); connect(btn_Manual, QPushButton::clicked, this, []() { if (m_work) { emit stopWork(); btn_Auto-setEnabled(true); btn_Manual-setEnabled(false); } });运行效果如下数据如下modbus 数据如下kepserver完整代码如下#ifndef OPCDATDWORK_H #define OPCDATDWORK_H #include QObject class QTimer; class COPCItem; class COPCGroup; class COPCHost; class COPCServer; class COPCClient; class OPCDaTDWork : public QObject { Q_OBJECT public: explicit OPCDaTDWork(QObject *parent nullptr); ~OPCDaTDWork(); bool initOPC(); public slots: bool startWork();//开始采集 bool stopWork();//停止采集 void readData(); signals: void dataUpdated(const QVectorshort datas); private: QTimer *m_time; std::vectorCOPCItem * m_items; COPCClient *m_client; COPCGroup *m_group; COPCHost *m_host; COPCServer *m_server; QVectorshort datas; }; #endif // OPCDATDWORK_H #include opcdatdwork.h #include OPCClientToolKit/OPCClient.h #include OPCClientToolKit/OPCHost.h #include OPCClientToolKit/OPCServer.h #include OPCClientToolKit/OPCItem.h #include OPCClientToolKit/OPCGroup.h #include OPCClientToolKit/opcda.h #include QTimer #include QDebug OPCDaTDWork::OPCDaTDWork(QObject *parent) : QObject{parent} ,m_time(nullptr) ,m_group(nullptr) ,m_host(nullptr) ,m_server(nullptr) { } OPCDaTDWork::~OPCDaTDWork() { if(m_server) { delete m_server; m_server nullptr; } if(m_host) { delete m_host; m_host nullptr; } stopWork(); qDebug()In OPCDaTDWork 析构; } bool OPCDaTDWork::initOPC() { COPCClient::init(APARTMENTTHREADED); m_host COPCClient::makeHost(L127.0.0.1); if(m_host nullptr) { qDebug()Failed to create host; return false; } m_server m_host-connectDAServer(LKepware.KEPServerEX.V6); if(m_servernullptr) { delete m_host; qDebug()Failed to create m_server; return false; } unsigned long refreshRate 500; m_group m_server-makeGroup(LTest01,false,500,refreshRate,0.0); //第二个参数bool 是否异步回调第三个参数刷新周期第四个参数实际刷新周期第五个参数死区 std::vectorstd::wstring names; names.push_back(LModbus.Test.temp); names.push_back(LModbus.Test.temp2); names.push_back(LModbus.Test.temp3); names.push_back(LModbus.Test.temp4); names.push_back(LModbus.Test.temp5); std::vectorCOPCItem* items; std::vectorHRESULT errors; /* * 参数 类型 含义 names std::vectorstd::wstring 输入要添加的 Item ID 列表如 {Modbus.Test.temp1, temp2, ...}。 items std::vectorCOPCItem* 输出成功创建的 COPCItem* 指针列表。如果某个 Item 创建失败对应位置为 nullptr。 errors std::vectorHRESULT 输出每个 Item 添加操作的 HRESULT 错误码S_OK 表示成功。与 names 顺序一致。 active bool 输入true 表示创建后立即激活服务器开始采集false 表示创建但不激活需后续调用 SetActive(true)。 */ int errCnt m_group-addItems(names,items,errors,true); // int : 创建失败的 Item 数量即 errors 中不是 S_OK 的条目数。 if(errCnt0) //有坏的 { qDebug()Failed to add errCnt items; delete m_server; delete m_host; return false; } m_items items; m_time new QTimer(this); connect(m_time,QTimer::timeout,this,OPCDaTDWork::readData); } /* 通路1同步读取你现在用的 Client → readSync → OPC Server → 返回数据 特点 主动请求 一次一读 阻塞但你放在线程里了 ✔ 通路2异步订阅Grouptrue OPC Server → 数据变化 → 回调通知 Client 特点 被动接收 持续推送 不用你调用 read Group 的 true/false 只影响“回调机制” 不影响 readSync */ bool OPCDaTDWork::startWork() { static bool is_first false; if(!is_first) { initOPC(); is_first true; } if(m_time) { m_time-setInterval(500); m_time-start(); } return true; } bool OPCDaTDWork::stopWork() { if(m_time) { m_time-stop(); } return true; } void OPCDaTDWork::readData() { datas.clear(); std::vectorOPCItemData dataList(m_items.size()); for (int i 0; i m_items.size(); i) { HRESULT hr m_items[i]-readSync(dataList[i], OPC_DS_DEVICE); if (FAILED(hr) || dataList[i].wQuality ! OPC_QUALITY_GOOD) { qDebug() Read failed: QString::fromStdWString(m_items[i]-getName()); } } //将数据存储到 datas 中 int i 0; for( const auto data : dataList) { VARIANT val data.vDataValue; datas.append(val.iVal);// 这里假设数据类型都是 short如果不是需要根据实际类型进行转换 i; } // for(int i 0;idatas.size();i) // { // qDebug()通路i1 最新数据:datas[i]; // } emit dataUpdated(datas); }#ifndef OPCDAASYNCPOLLOT_H #define OPCDAASYNCPOLLOT_H #include QChart #include QChartView #include QWidget #include deque #includeQLineSeries #include opcdatdwork.h class QPushButton; class QThread; class OPCDaASyncPollot : public QWidget { Q_OBJECT public: explicit OPCDaASyncPollot(QWidget *parent nullptr); ~OPCDaASyncPollot(); void initChartView(); signals: void startWork(); void stopWork(); private: QPushButton * btn_Manual nullptr; QPushButton *btn_Auto nullptr; OPCDaTDWork *m_work nullptr; QThread *th_01 nullptr; QVectorstd::dequeint m_datas; QtCharts::QChartView *chartView nullptr; QtCharts::QChart *chart nullptr; QtCharts::QLineSeries *line01; QtCharts::QLineSeries *line02; QtCharts::QLineSeries *line03; QtCharts::QLineSeries *line04; QtCharts::QLineSeries *line05; QTimer *m_time nullptr; }; #endif // OPCDAASYNCPOLLOT_H #include opcdaasyncpollot.h #include QThread #include QDebug #include QPushButton #include QChart #include QXYSeries #include QChartView #include QLineSeries #include QValueAxis #include QTimer OPCDaASyncPollot::OPCDaASyncPollot(QWidget *parent) : QWidget{parent} ,line01(nullptr) ,line02(nullptr) ,line03(nullptr) ,line04(nullptr) ,line05(nullptr) ,m_time(nullptr) { this-setWindowTitle(tr(OPCDa_多线程_轮询采集)); this-resize(QSize(1600,1000)); m_datas.resize(5); for(int i 0;i5;i) { for(int j 0;j100;j) { m_datas[i].push_back(0); } } m_work new OPCDaTDWork(); th_01 new QThread(); m_work-moveToThread(th_01); // 确保在连接前注册 qRegisterMetaTypeQVectorshort(); connect(m_work,OPCDaTDWork::dataUpdated,this,[](const QVectorshort datas) { qDebug()OPCDaASyncPollot 收到数据更新信号最新数据; for(int i 0;idatas.size();i) { qDebug()通路i1 最新数据:datas[i]; m_datas[i].push_back(datas[i]); if(m_datas[i].size()100) { m_datas[i].pop_front(); } QVectorQPointF points; points.reserve(100); // 为每个系列批量更新 for(int i 0;i5;i) { points.clear(); for(int j 0;j100;j) { points.push_back(QPointF(j, m_datas[i][j])); } switch (i) { case 0: line01-replace(points); break; case 1: line02-replace(points); break; case 2: line03-replace(points); break; case 3: line04-replace(points); break; case 4: line05-replace(points); break; default: break; } } } }); connect(this,OPCDaASyncPollot::startWork,m_work,OPCDaTDWork::startWork); connect(this,OPCDaASyncPollot::stopWork,m_work,OPCDaTDWork::stopWork); th_01-start(); btn_Auto new QPushButton(this); btn_Auto-setText(tr(自动采集)); btn_Auto-move(50,50); connect(btn_Auto,QPushButton::clicked,this,[]() { if(m_work) { emit startWork(); btn_Auto-setEnabled(false); btn_Manual-setEnabled(true); } }); btn_Manual new QPushButton(this); btn_Manual-setText(tr(停止采集)); btn_Manual-move(200,50); connect(btn_Manual,QPushButton::clicked,this,[]() { if(m_work) { emit stopWork(); btn_Auto-setEnabled(true); btn_Manual-setEnabled(false); } }); initChartView(); } OPCDaASyncPollot::~OPCDaASyncPollot() { stopWork(); if(th_01 th_01-isRunning()) { th_01-quit(); th_01-wait(); } if(m_work) { delete m_work; m_work nullptr; } if(th_01) { delete th_01; th_01 nullptr; } qDebug()In OPCDaASyncPollot 析构; } void OPCDaASyncPollot::initChartView() { chartView new QtCharts::QChartView(this); chart new QtCharts::QChart(); line01 new QtCharts::QLineSeries(); line02 new QtCharts::QLineSeries(); line03 new QtCharts::QLineSeries(); line04 new QtCharts::QLineSeries(); line05 new QtCharts::QLineSeries(); line01-setColor(QColor(200,100,100)); line02-setColor(QColor(100,200,100)); line03-setColor(QColor(100,100,200)); line04-setColor(QColor(200,200,100)); line05-setColor(QColor(100,200,200)); line02-setName(temp01); line02-setName(temp02); line02-setName(temp03); line02-setName(temp04); line02-setName(temp05); QtCharts::QValueAxis *axisX new QtCharts::QValueAxis(); axisX-setRange(0, 100); axisX-setLabelFormat(%d); axisX-setTitleText(Sample-points); QtCharts::QValueAxis *axisY new QtCharts::QValueAxis(); axisY-setRange(0, 100); axisY-setLabelFormat(%d); axisY-setTitleText(value); chart-addAxis(axisX, Qt::AlignBottom); chart-addAxis(axisY, Qt::AlignLeft); chart-addSeries(line01); chart-addSeries(line02); chart-addSeries(line03); chart-addSeries(line04); chart-addSeries(line05); line01-attachAxis(axisX); line01-attachAxis(axisY); line02-attachAxis(axisX); line02-attachAxis(axisY); line03-attachAxis(axisX); line03-attachAxis(axisY); line04-attachAxis(axisX); line04-attachAxis(axisY); line05-attachAxis(axisX); line05-attachAxis(axisY); line01-clear(); line02-clear(); line03-clear(); line04-clear(); line05-clear(); QVectorQPointF points; points.reserve(100); // 为每个系列批量更新 for(int i 0;i5;i) { points.clear(); for(int j 0;j100;j) { points.push_back(QPointF(j, m_datas[i][j])); } switch (i) { case 0: line01-replace(points); break; case 1: line02-replace(points); break; case 2: line03-replace(points); break; case 3: line04-replace(points); break; case 4: line05-replace(points); break; default: break; } } chart-update(); chartView-setChart(chart); chartView-setGeometry(50,150,1000,800); chartView-show(); }

相关新闻

最新新闻

日新闻

周新闻

月新闻