Pompe à eau
Interagir avec un serveur OPC UA pour construire une IHM basée sur QML pour une simple pompe à eau.
L'exempleWater Pump montre comment utiliser Qt OPC UA pour interagir avec un serveur OPC UA afin de construire une IHM basée sur QML pour une machine simple.

Construction du serveur
Avant de pouvoir utiliser les exemples Water Pump, vous devez construire le serveur de simulation Water Pump. Vous pouvez l'ouvrir et le construire dans QtCreator ou à partir du terminal comme d'habitude.
La simulation
Le serveur OPC UA inclus dans cet exemple exécute une simulation d'une machine contenant deux réservoirs, une pompe à eau et une vanne. L'eau peut être pompée du premier réservoir dans le second, puis évacuée du second en ouvrant la vanne. Les deux opérations ont un point de consigne configurable par l'utilisateur, ce qui permet de contrôler la quantité d'eau pompée dans le deuxième réservoir ou évacuée de celui-ci.
Les nœuds suivants existent sur le serveur :
| NodeId | Fonction |
|---|---|
| ns=2;s=Machine | Le dossier contenant les nœuds de méthodes et de variables pour la machine |
| ns=2;s=Machine.State | L'état de la machine |
| ns=2;s=Machine.Tank1.PercentFilled | L'état de remplissage actuel du premier réservoir |
| ns=2;s=Machine.Tank2.PercentFilled | L'état de remplissage actuel du deuxième réservoir |
| ns=2;s=Machine.Tank2.TargetPercent | Le point de consigne pour le pompage et le rinçage |
| ns=2;s=Machine.Tank2.ValveState | L'état de la vanne du deuxième réservoir |
| ns=2;s=Machine.Designation | Désignation de la machine lisible par l'homme à des fins d'affichage |
| ns=2;s=Machine.Start | Appeler cette méthode pour démarrer la pompe |
| ns=2;s=Machine.Stop | Appeler cette méthode pour arrêter la pompe |
| ns=2;s=Machine.FlushTank2 | Appel de cette méthode pour rincer le réservoir 2 |
| ns=2;s=Machine.Reset | Appelle cette méthode pour réinitialiser la simulation |
Toutes les méthodes renvoient Good en cas de succès et BadUserAccessDenied si l'opération est illégale (par exemple, essayer de démarrer la pompe si le premier réservoir est vide).
Caractéristiques du client
Cet exemple utilise la lecture, l'écriture, les appels de méthode et les abonnements aux changements de données et montre comment configurer des gestionnaires pour les opérations asynchrones proposées par QOpcUaClient et QOpcUaNode.
Mise en œuvre
Une classe backend est utilisée pour gérer la communication avec le serveur OPC UA et exposer le contenu de ce serveur au moyen de propriétés et de méthodes Q_INVOKABLE enveloppant les appels de méthode OPC UA.
Variables membres
Un pointeur sur QOpcUaClient est nécessaire pour la gestion des connexions. Un pointeur supplémentaire vers un objet QOpcUaNode est nécessaire pour chaque nœud OPC UA avec lequel l'IHM interagit. Pour les valeurs de ces nœuds, des variables membres contenant la dernière valeur rapportée par le serveur sont ajoutées.
...
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;
...Pour chaque valeur utilisée dans l'IHM, un getter, un signal de changement et une propriété sont ajoutés pour permettre les liaisons de propriété en 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)
...Manipulateurs asynchrones
L'API asynchrone de Qt OPC UA nécessite des gestionnaires de signaux pour toutes les opérations.
Les abonnements aux modifications de données signalent leurs mises à jour à l'aide de QOpcUaNode::attributeUpdated. Un gestionnaire connecté à ce signal reçoit la nouvelle valeur à l'adresse QVariant et peut écrire cette valeur dans une variable ou émettre un signal avec la nouvelle valeur, par exemple.
void OpcUaMachineBackend::percentFilledTank1Updated(QOpcUa::NodeAttribute attr, const QVariant &value) { Q_UNUSED(attr); m_percentFilledTank1 = value.toDouble(); emit percentFilledTank1Changed(m_percentFilledTank1); }
Une opération de lecture émet le signal QOpcUaNode::attributeRead lorsqu'elle est terminée. Le client doit vérifier le code d'état et obtenir le résultat du nœud.
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); } } }
Interaction avec le serveur
Dans le constructeur, un site QOpcUaProvider est créé et les backends disponibles sont enregistrés afin de fournir un modèle pour le menu déroulant de sélection des backends.
...
QOpcUaProvider provider;
setBackends(provider.availableBackends());
...Avant de tenter une connexion, un QOpcUaClient avec le backend sélectionné est créé. Son signal QOpcUaClient::endpointsRequestFinished est connecté à l'emplacement requestEndpointsFinished du backend. Le signal QOpcUaClient::stateChanged doit être connecté à l'emplacement clientStateHandler du backend.
void OpcUaMachineBackend::connectToEndpoint(const QString&url, qint32 index) { if (m_connected) return; QOpcUaProvider provider ; if (index < 0 || index >= m_backends.size()) return; // Index invalide 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) ; }
Le slot OpcUaMachineBackend::requestEndpointsFinished reçoit la liste des points d'accès disponibles sur le serveur et démarre une connexion à la première entrée de la liste. S'il n'y a pas de point d'accès disponible, l'établissement de la connexion est interrompu.
void OpcUaMachineBackend::requestEndpointsFinished(const QList<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 agit lorsque QOpcUaClient est connecté ou déconnecté. En cas de connexion réussie, les variables membres de nœud créées précédemment sont remplies avec des objets de nœud.
...
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));
...Une fois que tous les objets nœuds ont été créés, les gestionnaires de changement de données sont connectés aux objets nœuds et la surveillance est activée.
...
// 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(
...La désignation de la machine n'est pas censée changer et sera lue une fois au démarrage.
...
// 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);
...Un dispositif de réglage du point de consigne est ajouté au backend.
void OpcUaMachineBackend::machineWriteTank2TargetPercent(double value) { if (m_tank2TargetPercentNode) m_tank2TargetPercentNode->writeAttribute(QOpcUa::NodeAttribute::Value, value); }
Pour les méthodes, des wrappers qui appellent la méthode du serveur OPC UA sont créés.
void OpcUaMachineBackend::startPump() { m_machineNode->callMethod(u"ns=2;s=Machine.Start"_s); }
L'IHM
Une instance de backend est créée et transmise à la partie QML en tant que propriété de contexte nommée uaBackend.
...
OpcUaMachineBackend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("uaBackend", &backend);
...Les propriétés, les signaux et les méthodes Q_INVOKABLE de uaBackend sont désormais accessibles au code QML. Par exemple, le bouton permettant de rincer le deuxième réservoir n'est activé que si le backend est connecté au serveur, que la machine est à l'arrêt et que le niveau du réservoir est supérieur au point de consigne. Au clic, la méthode flushTank2() est appelée sur le serveur.
Button { id: flushButton text: "Flush" enabled: uaBackend.connected && uaBackend.machineState === OpcUaMachineBackend.MachineState.Idle && uaBackend.percentFilledTank2 > uaBackend.tank2TargetPercent onClicked: { uaBackend.flushTank2() } }
Les signaux provenant du backend peuvent également être utilisés directement dans le code QML.
Connections { target: uaBackend function onPercentFilledTank2Changed(value) { if (uaBackend.machineState === OpcUaMachineBackend.MachineState.Pumping) rotation += 15 } }
Utilisation
Le serveur est démarré automatiquement par l'application IHM. Après s'être connecté au serveur en cliquant sur le bouton Connect, faites glisser le curseur pour définir un point de consigne. Cliquez ensuite sur Start pour commencer à pomper l'eau du premier réservoir vers le second. Si vous définissez un point de consigne inférieur à la valeur actuelle du deuxième réservoir, cliquez sur Flush pour ouvrir la vanne.
S'il n'y a plus d'eau, cliquez sur Reset simulation pour remplir le premier réservoir.
Fichiers :
- waterpump/waterpump-qmlcpp/CMakeLists.txt
- waterpump/waterpump-qmlcpp/Pump.qml
- waterpump/waterpump-qmlcpp/ServerControl.qml
- waterpump/waterpump-qmlcpp/Tank.qml
- waterpump/waterpump-qmlcpp/Tank1Unit.qml
- waterpump/waterpump-qmlcpp/Tank2Unit.qml
- waterpump/waterpump-qmlcpp/TankSimulation.qml
- waterpump/waterpump-qmlcpp/ValueDisplay.qml
- waterpump/waterpump-qmlcpp/main.cpp
- waterpump/waterpump-qmlcpp/main.qml
- waterpump/waterpump-qmlcpp/opcuamachinebackend.cpp
- waterpump/waterpump-qmlcpp/opcuamachinebackend.h
- waterpump/waterpump-qmlcpp/qml.qrc
- waterpump/waterpump-qmlcpp/waterpump-qmlcpp.pro
Voir aussi Qt Quick Pompe à eau.
© 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.