水泵
与 OPC UA 服务器交互,为一台简单的水泵机器建立基于 QML 的人机界面。
水泵示例展示了如何使用Qt OPC UA 与 OPC UA 服务器交互,为简单的机器建立基于 QML 的人机界面。
构建服务器
在使用水泵示例之前,您需要构建水泵模拟服务器。你可以在 QtCreator 或终端中打开并像往常一样构建它。
模拟
本示例中包含的 OPC UA 服务器运行一台机器的模拟,该机器包含两个水箱、一个水泵和一个阀门。水可以从第一个水箱泵入第二个水箱,然后通过打开阀门从第二个水箱冲出。这两种操作都有一个用户可配置的设定点,从而可以控制泵入或冲出第二个水箱的水量。
服务器上有以下节点
节点 ID | 功能 |
---|---|
ns=2;s=Machine | 包含机器方法和变量节点的文件夹 |
ns=2;s=机器状态 | 机器的状态 |
ns=2;s=Machine.Tank1.PercentFilled | 第一个油箱的当前加注状态 |
ns=2;s=Machine.Tank2.PercentFilled | 第二个油箱的当前加注状态 |
ns=2;s=Machine.Tank2.TargetPercent(目标百分比 | 泵送和冲洗的设定值 |
ns=2;s=Machine.Tank2.ValveState | 第二个水箱阀门的状态 |
ns=2;s=机器名称 | 用于显示的机器名称,可由人工读取 |
ns=2;s=Machine.Start | 调用此方法启动泵 |
ns=2;s=Machine.Stop | 调用此方法停止泵 |
ns=2;s=Machine.FlushTank2 | 调用此方法冲洗水箱 2 |
ns=2;s=Machine.Reset | 调用此方法重置模拟 |
如果操作成功,所有方法都返回Good ;如果操作非法(例如,在第一个水箱为空的情况下试图启动水泵),则返回BadUserAccessDenied 。
客户端功能
本例使用读、写、方法调用和数据更改订阅,并展示了如何为QOpcUaClient 和QOpcUaNode 提供的异步操作设置处理程序。
实现
后端类用于处理与 OPC UA 服务器的通信,并通过属性和封装 OPC UA 方法调用的Q_INVOKABLE
方法公开该服务器的内容。
成员变量
连接管理需要一个指向QOpcUaClient 的指针。人机界面与之交互的每个 OPC UA 节点还需要一个指向QOpcUaNode 对象的指针。对于这些节点的值,需要添加包含服务器最后报告值的成员变量。
... QScopedPointer<QOpcUaClient> m_client; QScopedPointer<QOpcUaNode> m_machineStateNode; QScopedPointer<QOpcUaNode> m_percentFilledTank1Node; QScopedPointer<QOpcUaNode> m_percentFilledTank2Node; QScopedPointer<QOpcUaNode> m_tank2TargetPercentNode; QScopedPointer<QOpcUaNode> m_tank2ValveStateNode; QScopedPointer<QOpcUaNode> m_machineNode; QScopedPointer<QOpcUaNode> m_machineDesignationNode; double m_percentFilledTank1; double m_percentFilledTank2; double m_tank2TargetPercent; bool m_tank2ValveState; MachineState m_machineState; QString m_machineDesignation; ...
对于人机界面中使用的每个值,都要添加一个获取器、一个更改信号和一个属性,以便在 QML 中实现属性绑定
... Q_PROPERTY(double percentFilledTank1 READ percentFilledTank1 NOTIFY percentFilledTank1Changed) Q_PROPERTY(double percentFilledTank2 READ percentFilledTank2 NOTIFY percentFilledTank2Changed) Q_PROPERTY(double tank2TargetPercent READ tank2TargetPercent NOTIFY tank2TargetPercentChanged) Q_PROPERTY(OpcUaMachineBackend::MachineState machineState READ machineState NOTIFY machineStateChanged) Q_PROPERTY(bool tank2ValveState READ tank2ValveState NOTIFY tank2ValveStateChanged) Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) Q_PROPERTY(QString machineDesignation READ machineDesignation NOTIFY machineDesignationChanged) Q_PROPERTY(QString message READ message NOTIFY messageChanged) ...
异步处理程序
Qt OPC UA 的异步 API 要求为所有操作提供信号处理器。
数据更改订阅使用QOpcUaNode::attributeUpdated 报告其更新。连接到该信号的处理程序会以QVariant 的形式获取新值,并可将该值写入变量或发出包含新值的信号。
void OpcUaMachineBackend::percentFilledTank1Updated(QOpcUa::NodeAttribute attr, const QVariant &value) { Q_UNUSED(attr); m_percentFilledTank1 = value.toDouble(); emit percentFilledTank1Changed(m_percentFilledTank1); }
读取操作完成后会发出QOpcUaNode::attributeRead 信号。客户端必须检查状态代码,然后从节点获取结果。
void OpcUaMachineBackend::machineDesignationRead(QOpcUa::NodeAttributes attr) { if (attr & QOpcUa::NodeAttribute::Value) { // Make sure the value attribute has been read if (m_machineDesignationNode->attributeError(QOpcUa::NodeAttribute::Value) == QOpcUa::UaStatusCode::Good) { // Make sure there was no error m_machineDesignation = m_machineDesignationNode->attribute(QOpcUa::NodeAttribute::Value).toString(); // Get the attribute from the cache emit machineDesignationChanged(m_machineDesignation); } } }
与服务器交互
在构造函数中,会创建一个QOpcUaProvider 并保存可用的后端,以便为后端选择下拉菜单提供一个模型。
... QOpcUaProvider provider; setBackends(provider.availableBackends()); ...
在尝试连接之前,会创建一个与所选后端相连的QOpcUaClient 。其QOpcUaClient::endpointsRequestFinished 信号与后端requestEndpointsFinished
插槽相连。QOpcUaClient::stateChanged 信号必须连接到后端clientStateHandler
插槽。
voidOpcUaMachineBackend::connectToEndpoint(constQString&url、 qint32index) {if(m_connected)return; QOpcUaProviderprovider;if(index< 0 ||index>=m_backends.size())return;// Invalid index if(!m_client||(m_client&& m_client->backend()!=m_backends.at(index))){ m_client.reset(provider.createClient(m_backends.at(index)));if(m_client) { QObject::connect(m_client.data(), &QOpcUaClient::endpointsRequestFinished, this, &OpcUaMachineBackend::requestEndpointsFinished); QObject::connect(m_client.data(), &QOpcUaClient::stateChanged, this, &OpcUaMachineBackend::clientStateHandler); } }if(!m_client) { qWarning() << "Could not create client"; m_successfullyCreated= false;return; } m_successfullyCreated= true; m_client->requestEndpoints(url); }
OpcUaMachineBackend::requestEndpointsFinished
插槽接收服务器上的可用端点列表,并启动与列表中第一个条目的连接。如果没有可用端点,则会中止建立连接。
voidOpcUaMachineBackend::requestEndpointsFinished(constQList<QOpcUaEndpointDescription> &endpoints) {if(endpoints.isEmpty()) { qWarning() << "The server did not return any endpoints"; clientStateHandler(QOpcUaClient::ClientState::Disconnected);return; } m_client->connectToEndpoint(endpoints.at(0)); }
clientStateHandler
对 连接或断开连接采取行动。如果连接成功,之前创建的节点成员变量将被填充为节点对象。QOpcUaClient
... if (state == QOpcUaClient::ClientState::Connected) { setMessage(u"Connected"_s); // Create node objects for reading, writing and subscriptions m_machineNode.reset(m_client->node(u"ns=2;s=Machine"_s)); m_machineStateNode.reset(m_client->node(u"ns=2;s=Machine.State"_s)); m_percentFilledTank1Node.reset(m_client->node(u"ns=2;s=Machine.Tank1.PercentFilled"_s)); m_percentFilledTank2Node.reset(m_client->node(u"ns=2;s=Machine.Tank2.PercentFilled"_s)); m_tank2TargetPercentNode.reset(m_client->node(u"ns=2;s=Machine.Tank2.TargetPercent"_s)); m_tank2ValveStateNode.reset(m_client->node(u"ns=2;s=Machine.Tank2.ValveState"_s)); m_machineDesignationNode.reset(m_client->node(u"ns=2;s=Machine.Designation"_s)); ...
创建所有节点对象后,数据更改处理程序将连接到节点对象并启用监控。
... // Connect signal handlers for subscribed values QObject::connect(m_machineStateNode.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::machineStateUpdated); QObject::connect(m_percentFilledTank1Node.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::percentFilledTank1Updated); QObject::connect(m_percentFilledTank2Node.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::percentFilledTank2Updated); QObject::connect(m_tank2TargetPercentNode.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::tank2TargetPercentUpdated); QObject::connect(m_tank2ValveStateNode.data(), &QOpcUaNode::dataChangeOccurred, this, &OpcUaMachineBackend::tank2ValveStateUpdated); // Subscribe to data changes m_machineStateNode->enableMonitoring( QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters(100)); m_percentFilledTank1Node->enableMonitoring( QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters(100)); m_percentFilledTank2Node->enableMonitoring( QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters(100)); m_tank2TargetPercentNode->enableMonitoring( QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters(100)); m_tank2ValveStateNode->enableMonitoring( ...
机器名称不应更改,并将在启动时读取一次。
... // Connect the handler for async reading QObject::connect(m_machineDesignationNode.data(), &QOpcUaNode::attributeRead, this, &OpcUaMachineBackend::machineDesignationRead); // Request the value attribute of the machine designation node m_machineDesignationNode->readAttributes(QOpcUa::NodeAttribute::Value); ...
设置点的设定器被添加到后台。
void OpcUaMachineBackend::machineWriteTank2TargetPercent(double value) { if (m_tank2TargetPercentNode) m_tank2TargetPercentNode->writeAttribute(QOpcUa::NodeAttribute::Value, value); }
对于方法,创建了调用 OPC UA 服务器方法的包装器。
void OpcUaMachineBackend::startPump() { m_machineNode->callMethod(u"ns=2;s=Machine.Start"_s); }
人机界面
创建一个后端实例,并将其作为名为uaBackend
的上下文属性交给 QML 部分。
... OpcUaMachineBackend backend; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("uaBackend", &backend); ...
QML 代码现在可以访问 uaBackend 的属性、信号和Q_INVOKABLE
方法。例如,只有当后端连接到服务器、机器空闲且水箱水位高于设定值时,才能启用冲洗第二个水箱的按钮。点击后,服务器将调用flushTank2()
方法。
Button { id: flushButton text: "Flush" enabled: uaBackend.connected && uaBackend.machineState === OpcUaMachineBackend.MachineState.Idle && uaBackend.percentFilledTank2 > uaBackend.tank2TargetPercent onClicked: { uaBackend.flushTank2() } }
来自后台的信号也可直接在 QML 代码中使用。
Connections { target: uaBackend function onPercentFilledTank2Changed(value) { if (uaBackend.machineState === OpcUaMachineBackend.MachineState.Pumping) rotation += 15 } }
使用方法
人机界面应用程序会自动启动服务器。单击Connect 按钮连接服务器后,拖动滑块设置设定点。然后,单击Start 开始将水从第一个水箱抽到第二个水箱。如果设定点低于第二个水箱的当前值,单击Flush 将打开阀门。
如果没有水了,请单击Reset simulation 为第一个水箱注水。
文件:
另请参见 Qt Quick 水泵。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.