✔ 基于 OPC DA 的多通道数据采集
✔ 使用 Qt 多线程实现不卡 UI
✔ 实时数据显示 + 曲线绘制
目录
1. OPC COM 初始化
2. 创建 Host 并连接 OPC Server
3. 创建 Group 并配置参数
4. 批量添加 Item
5. 定时轮询读取数据
6. 跨线程通信与类型注册
7. 图表初始化
8. 数据接收与曲线更新(信号槽中的处理)
9. UI 控制(按钮与启动/停止)
运行效果如下:
数据如下:
完整代码如下:
1. OPC COM 初始化
// OPCDaTDWork::initOPC() 中 COPCClient::init(APARTMENTTHREADED);2. 创建 Host 并连接 OPC Server
// OPCDaTDWork::initOPC() 中 m_host = COPCClient::makeHost(L"127.0.0.1"); if (m_host == nullptr) { qDebug() << "Failed to create host"; return false; } m_server = m_host->connectDAServer(L"Kepware.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(L"Test01", false, 500, refreshRate, 0.0); // 参数含义:组名,是否异步回调,更新周期,实际刷新率(输出),死区4. 批量添加 Item
// OPCDaTDWork::initOPC() 中 std::vector<std::wstring> names; names.push_back(L"Modbus.Test.temp"); names.push_back(L"Modbus.Test.temp2"); names.push_back(L"Modbus.Test.temp3"); names.push_back(L"Modbus.Test.temp4"); names.push_back(L"Modbus.Test.temp5"); std::vector<COPCItem*> items; std::vector<HRESULT> 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::vector<OPCItemData> 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 构造函数中 qRegisterMetaType<QVector<short>>(); connect(m_work, &OPCDaTDWork::dataUpdated, this, [=](const QVector<short> 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(); QVector<QPointF> 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 QVector<short> 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(); } QVector<QPointF> 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 QVector<short> datas); private: QTimer *m_time; std::vector<COPCItem *> m_items; COPCClient *m_client; COPCGroup *m_group; COPCHost *m_host; COPCServer *m_server; QVector<short> 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(L"127.0.0.1"); if(m_host == nullptr) { qDebug()<<"Failed to create host"; return false; } m_server = m_host->connectDAServer(L"Kepware.KEPServerEX.V6"); if(m_server==nullptr) { delete m_host; qDebug()<<"Failed to create m_server"; return false; } unsigned long refreshRate = 500; m_group = m_server->makeGroup(L"Test01",false,500,refreshRate,0.0); //第二个参数bool 是否异步回调,第三个参数刷新周期,第四个参数实际刷新周期,第五个参数死区 std::vector<std::wstring> names; names.push_back(L"Modbus.Test.temp"); names.push_back(L"Modbus.Test.temp2"); names.push_back(L"Modbus.Test.temp3"); names.push_back(L"Modbus.Test.temp4"); names.push_back(L"Modbus.Test.temp5"); std::vector<COPCItem*> items; std::vector<HRESULT> errors; /* * 参数 类型 含义 names std::vector<std::wstring>& 输入:要添加的 Item ID 列表(如 {"Modbus.Test.temp1", "temp2", ...})。 items std::vector<COPCItem*>& 输出:成功创建的 COPCItem* 指针列表。如果某个 Item 创建失败,对应位置为 nullptr。 errors std::vector<HRESULT>& 输出:每个 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(errCnt>0) //有坏的 { 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:异步订阅(Group=true) 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::vector<OPCItemData> 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;i<datas.size();i++) // { // qDebug()<<"通路"<<i+1<<" 最新数据:"<<datas[i]; // } emit dataUpdated(datas); }#ifndef OPCDAASYNCPOLLOT_H #define OPCDAASYNCPOLLOT_H #include <QChart> #include <QChartView> #include <QWidget> #include <deque> #include<QLineSeries> #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; QVector<std::deque<int>> 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;i<5;i++) { for(int j =0;j<100;j++) { m_datas[i].push_back(0); } } m_work = new OPCDaTDWork(); th_01 = new QThread(); m_work->moveToThread(th_01); // 确保在连接前注册 qRegisterMetaType<QVector<short>>(); connect(m_work,&OPCDaTDWork::dataUpdated,this,[=](const QVector<short> 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(); } QVector<QPointF> 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; } } } }); 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(); QVector<QPointF> 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(); }