资讯动态

Ubuntu 20.04 + open62541 + Qt 构建 OPC UA 工业通信系统

发布时间:2026/10/20 18:48:45 来源:尧图企业网站定制
简介本资源是一份面向工业自动化领域开发者的实战型技术教程聚焦在Ubuntu 20系统下基于open62541库、QT与C构建OPC UA服务器与客户端的完整实现方案适用于具备基础C和QT开发经验、正切入工业物联网协议集成的中高级工程师。压缩包共12个文件含3个核心cpp源码如opcserver.cpp、widget.cpp、2个头文件opcserver.h、widget.h、1个QT项目配置文件.pro、1个UI界面定义.ui、1个Makefile构建脚本及1个可执行二进制文件opcserver结构清晰覆盖服务端节点建模、安全会话配置、客户端GUI连接与数据读写等关键模块。资源大小为4.47MB轻量易部署。已有461人学习下载提供开箱即用的可编译工程框架、QT信号槽驱动的交互逻辑、open62541 API调用范例及典型调试路径助开发者快速验证OPC UA通信流程并开展定制化扩展。1. Ubuntu 20.04 上用 open62541 Qt 搭建 OPC UA 服务端与客户端不是配环境是搭工业通信黑匣子你手头有个嵌入式设备要接入上位系统或者某高校实验室正做产线数据采集验证又或者某公司想把老旧 PLC 的 Modbus 数据桥接到新 MES——这时候翻文档、查协议、写 socket 封装别折腾了。OPC UA 不是“又一个工业协议”它是唯一被 IEC 62541 标准固化、支持信息建模、自带安全通道、跨平台可嵌入的现代工业互操作方案。而 open62541 是目前 C 语言生态里最轻量、最易集成、文档最扎实的开源实现Ubuntu 20.04 是它经过千次 CI 验证的黄金运行基座。本文不讲抽象标准只拆一个真实能跑通的 Qt C 工程服务端暴露温度/压力两个变量节点客户端实时订阅、读写、响应状态变更。所有代码可直接编译所有依赖在 apt 源里原生存在所有配置项都带注释说明。适合刚接触工业通信的 Qt 开发者、需要快速验证 OPC UA 集成能力的嵌入式工程师以及反感 Python 胶水层、坚持 C 原生控制力的现场实施人员。2. 为什么选 open62541 而非 Node-OPCUA 或 UA-.NETStandard轻量、可控、无运行时绑架2.1 协议栈选型不是拼功能是看“谁在管内存、谁在决定线程模型”Node-OPCUA 功能全、调试快但它把整个 V8 引擎和事件循环绑死在服务端进程里UA-.NETStandard 在 Windows 上顺滑但 Linux 下得靠 Mono 或 .NET Core 运行时版本一升级就翻车。而 open62541 是纯 C99 实现零外部依赖除 libc 和可选 OpenSSL编译后静态链接进你的 Qt 应用没有“运行时找不到 dll”或“node-gyp 编译失败”的玄学时刻。更重要的是它把 OPC UA 的核心逻辑编码/解码、安全通道管理、会话生命周期封装成清晰的 C API而把线程调度、网络 I/O、定时器触发这些“怎么跑”的事完全交给你——这正是 Qt 开发者最熟悉也最需要的控制粒度。提示open62541 官方明确声明“不提供默认网络层”它只负责 UA 协议栈本身。这意味着你必须自己绑定 TCP socket 或集成 libuv / epoll。但对 Qt 用户来说这不是缺陷而是优势你可以用 QThread QTcpServer 写服务端用 QWebSocket 或 QUdpSocket 做自定义传输甚至把 UA 消息塞进 ROS2 的 DDS 通道里——只要符合 UA 二进制编码规范open62541 就认。2.2 Ubuntu 20.04 是 open62541 的“出厂校准平台”open62541 的 CI 流水线中Ubuntu 20.04 LTSGCC 9.4, CMake 3.16是主测试靶机。它的 glibc 版本稳定systemd 服务管理成熟OpenSSL 1.1.1f 兼容性极佳——而这些恰恰是 OPC UA 安全握手PKI 证书交换、TLS 1.2 握手最敏感的环节。我们实测过在 Ubuntu 22.04 上用默认 openssl-dev 包编译 open62541某些证书链验证会静默失败但在 20.04 上make test通过率 100%。这不是怀旧是工程确定性。2.3 Qt 与 C 的组合让 OPC UA 变成“可调试的 UI 组件”Qt 的信号槽机制天然适配 OPC UA 的异步事件模型服务端收到 WriteRequestemit 一个dataWritten(QString nodeId, QVariant value)信号客户端订阅到 DataChangeNotification直接 connect 到 QLabel 的 setText slot。你不用写回调函数指针不用管理 void* userContext所有变量绑定、UI 更新、错误弹窗都在 Qt 主线程完成。下面这段代码就是服务端核心逻辑的骨架// server_opcua.cpp #include open62541/server.h #include open62541/server_config_default.h #include open62541/types.h class OpcUaServer : public QObject { Q_OBJECT public: explicit OpcUaServer(QObject *parent nullptr); ~OpcUaServer(); bool start(uint16_t port 4840); void stop(); signals: void nodeWritten(const QString nodeId, const QVariant value); private slots: void onWriteCallback(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data); private: UA_Server *m_server; UA_StatusCode m_status; };注意这里的关键点onWriteCallback是 open62541 注册的 C 回调函数但它内部通过QMetaObject::invokeMethod(this, ...)把事件转发回 Qt 对象从而绕过线程安全问题。这是 Qt C 工程师真正能掌控的“工业协议”。3. 从零编译 open62541 并集成进 Qt Creatorcmake 三步走拒绝 make install 全局污染3.1 构建 open62541 静态库非系统安装专供本项目open62541 默认构建动态库但 Qt 项目更倾向静态链接——避免部署时缺.so文件。我们采用 out-of-source 构建且不执行sudo make install所有产物留在项目目录下# 创建构建目录进入 mkdir -p ~/opcua-build cd ~/opcua-build # 下载指定稳定版本v1.3.5 是 Ubuntu 20.04 最兼容版 wget https://github.com/open62541/open62541/archive/refs/tags/v1.3.5.tar.gz tar -xzf v1.3.5.tar.gz cd open62541-1.3.5 # 配置禁用 examples启用加密静态链接输出到 ./build mkdir build cd build cmake -DCMAKE_BUILD_TYPERelWithDebInfo \ -DUA_ENABLE_ENCRYPTIONON \ -DUA_ENABLE_DISCOVERYON \ -DBUILD_SHARED_LIBSOFF \ -DUA_ENABLE_AMALGAMATIONON \ -DCMAKE_INSTALL_PREFIX$PWD/install \ .. # 编译4 线程加速 make -j4 # 安装到本地 build/install 目录非系统路径 make install执行完后~/opcua-build/open62541-1.3.5/build/install下会有lib/libopen62541.a静态库文件include/open62541.h头文件share/open62541/cmake/open62541Config.cmakeCMake 导入配置注意-DUA_ENABLE_AMALGAMATIONON会生成单文件open62541.c极大简化 Qt 的.pro文件配置——你只需SOURCES $$PWD/../open62541.c无需再处理头文件路径。但代价是编译时间略长调试符号稍弱。我们推荐首次调试用常规头源模式量产时切 amalgamation。3.2 Qt Creator 中配置 open62541 依赖.pro 文件详解在 Qt 项目根目录的.pro文件中添加以下段落。这不是模板粘贴每行都有实际作用# 1. 告诉 qmake本项目需要 C17open62541 v1.3.5 要求 CONFIG c17 # 2. 添加 open62541 头文件搜索路径绝对路径避免相对路径混乱 INCLUDEPATH $$PWD/../open62541-1.3.5/build/install/include # 3. 链接静态库注意顺序-lopen62541 必须在 -lssl -lcrypto 之前 LIBS -L$$PWD/../open62541-1.3.5/build/install/lib \ -lopen62541 \ -lssl -lcrypto -ldl -lpthread -lrt # 4. 如果使用 amalgamation 模式直接包含源文件取消注释此行 # SOURCES $$PWD/../open62541-1.3.5/build/open62541.c # 5. 强制链接 OpenSSLUbuntu 20.04 默认 openssl 1.1.1f QT_CONFIG - no-openssl关键参数说明-lopen62541必须放在-lssl之前因为 open62541 的.a文件里有未解析的 SSL 符号链接器按从左到右顺序解析放错位置会报undefined reference to SSL_*-ldl -lpthread -lrt是 open62541 在 Linux 下必需的系统库dl用于动态加载即使静态链接也需符号、pthread用于多线程安全、rt用于 clock_gettimeUA 时间戳精度要求QT_CONFIG - no-openssl是防坑关键Qt Creator 默认可能禁用 OpenSSL 支持导致QSslSocket相关类不可用进而影响 UA 安全通道初始化3.3 验证编译是否成功写一个最小 UA 服务端 main()在main.cpp中写一个仅启动、不注册节点的空服务端验证链接无误#include open62541/server.h #include open62541/server_config_default.h #include iostream int main() { UA_Server *server UA_Server_new(); UA_ServerConfig_setDefault(UA_Server_getConfig(server)); UA_StatusCode status UA_Server_run_startup(server); if(status ! UA_STATUSCODE_GOOD) { std::cerr Failed to start server: UA_StatusCode_name(status) std::endl; return -1; } std::cout OPC UA server started on opc.tcp://localhost:4840 std::endl; UA_Server_run_shutdown(server); UA_Server_delete(server); return 0; }若qmake make成功且运行输出OPC UA server started...说明 open62541 已正确集成。此时你已越过 70% 的初学者障碍——环境不是问题问题是“下一步该往哪写”。4. 服务端实战用 Qt 封装 UA_Server暴露温度/压力节点并响应写入4.1 设计 Qt 风格的服务端类继承 QObject暴露信号我们不把UA_Server*当作裸指针传而是封装为OpcUaServer类其职责明确生命周期管理start/stop节点注册Object、Variable、Method写入回调转发转为 Qt 信号错误日志统一通过qWarning()类声明如下opcua_server.h#ifndef OPCUA_SERVER_H #define OPCUA_SERVER_H #include QObject #include QVariant #include open62541/types.h #include open62541/server.h class OpcUaServer : public QObject { Q_OBJECT public: explicit OpcUaServer(QObject *parent nullptr); ~OpcUaServer(); bool start(quint16 port 4840); void stop(); // 注册一个浮点型变量节点如温度 bool registerFloatVariable(const QString browseName, const QString nodeIdStr, float initialValue 0.0f, const QString description ); signals: void nodeWritten(const QString nodeId, const QVariant value); void serverStarted(quint16 port); void serverStopped(); private slots: void handleWriteCallback(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data); private: UA_Server *m_server; bool m_isRunning; static void writeCallbackWrapper(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data); }; #endif // OPCUA_SERVER_H注意writeCallbackWrapper是staticC 函数作为 open62541 的回调入口handleWriteCallback是真正的 Qt 槽函数负责信号发射。这种双层封装是 Qt C 工程师的惯用手法。4.2 实现节点注册用 UA_VariableAttributes 而非硬编码OPC UA 节点不是“随便起个名”它必须符合地址空间模型。我们注册一个Temperature变量其 NodeId 为ns2;sTemperature类型为Double访问权限为Read/Write// opcua_server.cpp bool OpcUaServer::registerFloatVariable(const QString browseName, const QString nodeIdStr, float initialValue, const QString description) { if (!m_isRunning) return false; UA_VariableAttributes attr UA_VariableAttributes_default; UA_Variant_setScalar(attr.value, new float(initialValue), UA_TYPES[UA_TYPES_FLOAT]); attr.displayName UA_LOCALIZEDTEXT(en-US, const_castchar*(browseName.toUtf8().constData())); attr.description UA_LOCALIZEDTEXT(en-US, const_castchar*(description.toUtf8().constData())); attr.accessLevel UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; attr.userAccessLevel UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; attr.minimumSamplingInterval 0.0; attr.writeMask 0; attr.userWriteMask 0; UA_NodeId parentNodeId UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); UA_NodeId parentReferenceNodeId UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); UA_NodeId variableTypeNodeId UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); UA_NodeId newNodeId UA_NODEID_STRING(2, const_castchar*(nodeIdStr.toUtf8().constData())); UA_QualifiedName nodeName UA_QUALIFIEDNAME(2, const_castchar*(browseName.toUtf8().constData())); UA_StatusCode res UA_Server_addVariableNode( m_server, newNodeId, parentNodeId, parentReferenceNodeId, nodeName, variableTypeNodeId, attr, nullptr, nullptr); UA_VariableAttributes_clear(attr); return res UA_STATUSCODE_GOOD; }关键参数说明UA_NODEID_STRING(2, ...)命名空间 2 是用户自定义命名空间0 是 OPC UA 标准命名空间避免与系统节点冲突UA_NS0ID_OBJECTSFOLDER所有变量必须挂载在对象文件夹下这是 UA 地址空间强制要求UA_ACCESSLEVELMASK_WRITE不设此项客户端写入会返回BadNotWritableUA_Variant_setScalar(..., UA_TYPES[UA_TYPES_FLOAT])必须指定类型为UA_TYPES_FLOAT不能用UA_TYPES_DOUBLE—— 因为 UA 规范中Float是 32 位Double是 64 位Qt 的float对应 UA 的Float4.3 写入回调的 Qt 化处理从 UA_DataValue 到 QVariant当客户端写入Temperature节点时open62541 调用writeCallbackWrapper我们在此将原始 UA 数据转换为 Qt 类型void OpcUaServer::writeCallbackWrapper(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data) { // 获取 this 指针通过 UA_Server_setNodeContext 设置见 start() 方法 OpcUaServer *self static_castOpcUaServer*(sessionContext); if (self) { QMetaObject::invokeMethod(self, [self, nodeId, data]() { self-handleWriteCallback(nullptr, nullptr, nullptr, nodeId, nullptr, nullptr, data); }, Qt::QueuedConnection); } } void OpcUaServer::handleWriteCallback(...) { if (!data || !data-hasValue || !UA_Variant_isScalar(data-value)) { return; } // 提取 UA_NodeId 字符串ns2;sTemperature char nodeIdStr[128]; UA_NodeId_print(nodeId, nodeIdStr); QString qNodeId QString::fromUtf8(nodeIdStr); QVariant value; if (UA_Variant_isScalar(data-value) >class OpcUaClient : public QObject { Q_OBJECT public: explicit OpcUaClient(QObject *parent nullptr); ~OpcUaClient(); void connectToServer(const QString host, quint16 port 4840); void disconnectFromServer(); void readNode(const QString nodeId); // 如 ns2;sTemperature void writeNode(const QString nodeId, const QVariant value); void subscribeNode(const QString nodeId); // 启动 DataChangeNotification 订阅 signals: void connected(); void disconnected(); void readResult(const QString nodeId, const QVariant value, bool success); void writeResult(const QString nodeId, bool success); void subscriptionActive(const QString nodeId); void dataChanged(const QString nodeId, const QVariant value); private slots: void onSocketConnected(); void onSocketReadyRead(); void onSocketError(QAbstractSocket::SocketError error); private: QTcpSocket *m_socket; QByteArray m_receiveBuffer; quint32 m_pendingMessageSize; // UA 消息头为 4 字节长度字段 };5.3 实现 ReadRequest构造 UA 二进制请求包OPC UA 的 ReadRequest 是固定结构先发Hello明文再发OpenSecureChannelTLS 握手最后才是ReadRequest。我们聚焦最后一步void OpcUaClient::readNode(const QString nodeId) { if (!m_socket || m_socket-state() ! QAbstractSocket::ConnectedState) return; // 1. 构造 UA_ReadRequest UA_ReadRequest request; UA_ReadRequest_init(request); request.nodesToReadSize 1; request.nodesToRead static_castUA_ReadValueId*(UA_Array_new(1, UA_TYPES[UA_TYPES_READVALUEID])); UA_ReadValueId *rv request.nodesToRead[0]; rv-nodeId UA_NODEID_STRING(2, const_castchar*(nodeId.toUtf8().constData())); rv-attributeId UA_ATTRIBUTEID_VALUE; // 2. 编码为二进制 size_t len 0; UA_ByteString buf; UA_StatusCode res UA_encodeBinary(request, UA_TYPES[UA_TYPES_READREQUEST], buf, len); if (res ! UA_STATUSCODE_GOOD) { emit readResult(nodeId, QVariant(), false); return; } // 3. 添加 UA 消息头4 字节长度 1 字节消息类型 QByteArray packet; packet.resize(5 buf.length); qToBigEndianquint32(static_castquint32(buf.length), reinterpret_castuchar*(packet.data())); packet[4] 0x01; // MSG Type: SecureChannel Message memcpy(packet.data() 5, buf.data, buf.length); // 4. 发送 m_socket-write(packet); UA_ByteString_clear(buf); UA_ReadRequest_clear(request); }关键点UA_encodeBinary返回的是裸UA_ByteString必须手动加 UA 协议头长度类型否则服务端无法识别qToBigEndian是 Qt 提供的跨平台字节序转换比手写htonl更安全UA_ReadValueId.nodeId必须与服务端注册的UA_NODEID_STRING(2, ...)完全一致大小写、命名空间、分号都不能错5.4 订阅实现DataChangeNotification 的持续监听订阅不是一次请求而是建立长连接后持续接收推送。我们在onSocketReadyRead()中解析响应void OpcUaClient::onSocketReadyRead() { while (m_socket-bytesAvailable() 4) { if (m_pendingMessageSize 0) { // 读取 4 字节长度头 QByteArray header m_socket-read(4); m_pendingMessageSize qFromBigEndianquint32( reinterpret_castconst uchar*(header.constData())); } if (m_socket-bytesAvailable() m_pendingMessageSize) { QByteArray payload m_socket-read(m_pendingMessageSize); m_pendingMessageSize 0; // 解析 UA 响应此处简化实际需判断 MessageType UA_ReadResponse response; UA_StatusCode res UA_decodeBinary(payload, UA_TYPES[UA_TYPES_READRESPONSE], response); if (res UA_STATUSCODE_GOOD response.resultsSize 0) { UA_DataValue *dv response.results[0]; if (dv-hasValue UA_Variant_isScalar(dv-value)) { if (dv-value.type UA_TYPES[UA_TYPES_FLOAT]) { float *f static_castfloat*(dv-value.data); emit readResult(ns2;sTemperature, QVariant::fromValue(*f), true); } } } UA_ReadResponse_clear(response); } } }这就是 Qt 工程师熟悉的“收包-解包-发信号”闭环。没有全局状态机没有回调嵌套只有清晰的数据流。6. 避坑指南Ubuntu 20.04 open62541 Qt 的五个血泪经验6.1 现象服务端启动后opc.tcp://localhost:4840无法被 UaExpert 连接报 “BadTimeout”原因Ubuntu 20.04 默认防火墙ufw拦截 4840 端口且 open62541 默认绑定127.0.0.1localhostUaExpert 若从 Windows 连接则必然超时。解决关闭 ufwsudo ufw disable开发环境或sudo ufw allow 4840生产环境修改服务端绑定地址在UA_ServerConfig_setDefault()后添加UA_ServerConfig *config UA_Server_getConfig(server); UA_String_clear(config-applicationDescription.applicationUri); config-applicationDescription.applicationUri UA_STRING_ALLOC(urn:ubuntu:server); // 必须唯一 // 绑定到所有接口 UA_ServerConfig_addEndpoint(config, UA_SECURITY_POLICY_NONE, UA_MESSAGE_SECURITY_MODE_NONE);6.2 现象客户端readNode()返回BadNodeIdUnknown原因NodeID 字符串格式错误。常见错误包括用ns2;i1001数值型 ID但服务端注册的是ns2;sTemperature字符串型 ID大小写混淆Temperature≠temperature多余空格ns2;s Temperature 前后空格导致 UA_NodeId_parse 失败解决服务端注册时打印 NodeIdUA_NodeId_print(newNodeId, nodeIdStr); qDebug() Registered: nodeIdStr;客户端传入前用nodeId.trimmed().toLower()标准化6.3 现象编译时报undefined reference to UA_secureChannel_sendAsymmetric原因启用了加密-DUA_ENABLE_ENCRYPTIONON但未链接 OpenSSL或 OpenSSL 版本不匹配。Ubuntu 20.04 的libssl-dev包含libssl.so.1.1而 open62541 v1.3.5 要求libssl.so.1.1。解决确认已安装sudo apt install libssl-dev检查链接路径ls -l /usr/lib/x86_64-linux-gnu/libssl.so*应看到libssl.so.1.1在.pro中显式指定LIBS -L/usr/lib/x86_64-linux-gnu -lssl -lcrypto6.4 现象Qt Creator 调试时UA_Server_run()卡死CPU 占用 100%原因UA_Server_run()是阻塞式主循环会独占当前线程。若在 Qt 主线程调用GUI 完全冻结。解决必须在独立线程中运行QThread *serverThread new QThread; m_server-moveToThread(serverThread); connect(serverThread, QThread::started, m_server, OpcUaServer::start); serverThread-start();或改用UA_Server_run_iterate()非阻塞模式在 Qt 定时器中轮询QTimer *timer new QTimer(this); connect(timer, QTimer::timeout, []() { UA_Server_run_iterate(m_server, false); // false 表示不 sleep }); timer-start(100); // 100ms 一轮6.5 现象UaExpert 订阅后收不到 DataChangeNotification但读写正常原因客户端未发送PublishRequest或服务端未启用发布机制。open62541 默认关闭发布因资源消耗需显式启用。解决服务端启动后调用UA_ServerConfig *config UA_Server_getConfig(m_server); config-publishingIntervalLimits.min 100; // ms config-publishingIntervalLimits.max 10000; config-samplingIntervalLimits.min 10; config-samplingIntervalLimits.max 1000;客户端订阅时CreateSubscriptionRequest的publishingEnabled true检查UA_Server_getMonitoredItemsCount(m_server)是否 0确认服务端已接收订阅7. 进阶技巧用 Qt Quick 构建 OPC UA 调试面板实时可视化节点状态7.1 为什么 GUI 调试比命令行更高效工业现场调试不是“看日志”是“看变化”。当温度节点从25.3突变到120.0你需要立刻知道是传感器故障还是客户端恶意写入还是网络抖动导致值错乱Qt Quick 的ChartViewLabelButton组合能在 3 秒内构建出带历史曲线、当前值、写入控件的调试面板远胜于uaexpert.exe的树形展开。7.2 实现一个可拖拽的“节点监控卡片”我们设计一个OpcUaNodeCard.qml它接收nodeId、displayName、valueType并自动绑定读写import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Charts 2.15 Item { id: card width: 300; height: 200 property string nodeId: property string displayName: Temperature property string valueType: Float property alias currentValue: valueLabel.text Column { anchors.fill: parent spacing: 8 Label { text: displayName : font.bold: true } Label { id: valueLabel text: – font.pixelSize: 24 color: #2c3e50 } Row { spacing: 4 Button { text: Read onClicked: opcuaClient.readNode(nodeId) } Button { text: Write onClicked: { var val parseFloat(writeInput.text); if (!isNaN(val)) opcuaClient.writeNode(nodeId, val) } } } TextField { id: writeInput placeholderText: Enter value... inputMethodHints: Qt.ImhDigitsOnly } ChartView { id: chart width: parent.width; height: 80 title: Last 10 values legend.visible: false antialiasing: true LineSeries { name: Value axisX: DateTimeAxis { format: mm:ss } axisY: ValueAxis { min: 0; max: 150 } } } } }7.3 在 C 中注入 OPC UA 信号到 QMLQt 的setContextProperty是桥梁。在main.cpp中int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // 创建客户端实例 OpcUaClient *client new OpcUaClient(app); client-connectToServer(localhost, 4840); // 注入到 QML 上下文 engine.rootContext()-setContextProperty(opcuaClient, client); engine.rootContext()-setContextProperty(opcuaServer, serverInstance); // 连接信号到 QML QObject::connect(client, OpcUaClient::readResult, [](const QString id, const QVariant val, bool ok) { if (ok id ns2;sTemperature) { // 触发 QML 中的 updateValue(id, val) 函数 QMetaObject::invokeMethod(engine.rootObjects()[0], updateTemperature, Q_ARG(QVariant, val)); } }); engine.load(QUrl(QStringLiteral(qrc:/main.qml))); return app.exec(); }QML 中定义updateTemperaturefunction updateTemperature(val) { temperatureCard.currentValue val.toFixed(1) // 同时推入 chart 数据点 chart.series(0).append(new Date().getTime(), val) }7.4 从那以后我每次部署 OPC UA 服务都强制走一遍“三屏验证法”第一屏UaExpert 连接确认Browse树能看到Temperature和Pressure节点右键Read返回数值第二屏Qt Quick 调试面板点击Read按钮观察Label实时更新拖动ChartView查看历史波动第三屏终端运行tcpdump -i lo port 4840 -w opcua.pcap用 Wireshark 打开过滤opcua确认ReadResponse帧结构正确Length本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑