水泵
通过与OPC UA服务器交互,为一台简单的水泵构建基于QML的人机界面(HMI)。
“水泵”示例演示了如何使用Qt OPC UA 与OPC UA服务器交互,为一台简单设备构建基于QML的人机界面(HMI)。

构建服务器
在使用水泵示例之前,您需要构建水泵仿真服务器。您可以像往常一样在 QtCreator 中打开并构建它,或者通过终端进行构建。
模拟
本示例中包含的 OPC UA 服务器运行着一个包含两个水箱、一台水泵和一个阀门的机器的仿真。 水可以从第一个水箱泵入第二个水箱,然后通过打开阀门将水从第二个水箱排出。这两项操作均设有用户可配置的设定点,从而能够控制泵入或排出第二个水箱的水量。
服务器上存在以下节点:
| 节点ID | 功能 |
|---|---|
| ns=2;s=Machine | 包含该机器的方法和变量节点的文件夹 |
| ns=2;s=Machine.State | 机器的状态 |
| 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=Machine.Designation | 用于显示的人机可读设备标识 |
| 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 的指针。对于HMI交互的每个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;
...对于 HMI 中使用的每个值,都会添加一个获取器、一个变化信号和一个属性,以便在 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,qint32 index)
{
if(m_connected)
return;
QOpcUaProvider provider;
if(index< 0||index>=m_backends.size())
return;// 索引无效
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);
}HMI
创建一个后端实例,并将其作为名为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
}
}用法
服务器由HMI应用程序自动启动。点击“Connect ”按钮连接到服务器后,拖动滑块设置设定点。然后,点击“Start ”开始将水从第一个水箱泵送到第二个水箱。如果设置的设定点低于第二个水箱的当前值,点击“Flush ”将打开阀门。
如果水已用尽,请点击“Reset simulation ”为第一个水箱补水。
另请参阅 Qt Quick 水泵。
© 2026 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.