DESIGN-Praxis / embedded-HMI Qt OPC UA – Ein Tutorial

Implementierung einer Beispielanwendung

Das Backend instanziiert zunächst einen QOpcUaClient, welcher die Verbindung zum Server herstellt. Der Verbindungsaufbau ist, wie der Rest der API, asynchron um die ereignisgetriebene Verarbeitungsschleife von Qt nicht zu blockieren. Der Status der Verbindung wird über das stateChanged-Signal des QOpcUaClient übermittelt. Zur Reaktion auf Änderungen des Verbindungsstatus wird ein Slot implementiert, der mit dem stateChanged-Signal verbunden wird.

Erstellen und Verbinden des Clients

void OpcUaMachineBackend::connectToEndpoint(const QString &url)
{
    QOpcUaProvider provider;
    m_client.reset(provider.createClient(provider.availableBackends().at(0)));
    QObject::connect(m_client.data(), &QOpcUaClient::stateChanged, this, &OpcUaMachineBackend::clientStateHandler);
    m_client->connectToEndpoint(QUrl("opc.tcp://127.0.0.1:43344"));
}

 

Bei einem Wechsel zu Disconnected wird die Verbindung neu aufgebaut. Im Zustand Connected wird für jeden von der Anwendung benötigten Knoten aus dem OPC UA-Adressraum ein QOpcUaNode-Objekt erstellt. Knoten werden durch eine Kombination aus Namespace und Identifier eindeutig adressiert. Zum Speichern der Nodes werden Member-Variablen vom Typ QScopedPointer<QOpcUaNode> verwendet, was u.a. die Speicherverwaltung im Falle eines Reconnects vereinfacht.
Ein QOpcUaNode ermöglicht die Interaktion mit einem OPC UA-Knoten auf dem Server, das Lesen und Schreiben von Attributen, das Aktivieren, Deaktivieren und Modifizieren von DataChange-Monitorings für alle Attribute, das Aufrufen von mit dem Knoten assoziierten Methoden und das Auffinden von Kindern des Knotens (Browse). Alle diese Operationen werden asynchron durchgeführt; die Ergebnisse werden durch Signale übermittelt.

Für die Behandlung der benötigten Signale müssen Slots implementiert und mit den entsprechenden Signalen verbunden werden. Der Zustand der Maschine und des Ventils, die Füllstände beider Tanks und der Sollwert für den zweiten Tank werden mittels DataChange-Monitoring auf Änderungen überwacht. Die Bezeichnung der Maschine wird nur einmal am Anfang gelesen. Nachdem die Slots verbunden sind, wird das Monitoring mit einem Intervall von 100 ms für die Value-Attribute der Knoten aktiviert. Das enableMonitoringFinished-Signal des QOpcUaNode kann optional mit einem Slot verbunden werden, um den Erfolg der Operation zu erfahren. Außerdem wird die Bezeichnung der Maschine angefordert.
 

Monitoring und Lesen

void OpcUaMachineBackend::clientStateHandler(QOpcUaClient::ClientState state)
{
    m_connected = (state == QOpcUaClient::ClientState::Connected);
    emit connectedChanged(m_connected);

    if (state == QOpcUaClient::ClientState::Connected) {
        // Anlegen von Knoten-Objekten und Monitored Items
        m_machineNode.reset(m_client->node("ns=2;s=Machine"));
        m_machineStateNode.reset(m_client->node("ns=2;s=Machine.State"));
        m_percentFilledTank1Node.reset(m_client->node("ns=2;s=Machine.Tank1.PercentFilled"));
        m_machineDesignationNode.reset(m_client->node("ns=2;s=Machine.Designation"));
        // ...
        QObject::connect(m_percentFilledTank1Node.data(), &QOpcUaNode::attributeUpdated, this,
                 &OpcUaMachineBackend::percentFilledTank1Updated);
        QObject::connect(m_machineDesignationNode.data(), &QOpcUaNode::attributeRead, this,
                 &OpcUaMachineBackend::machineDesignationRead);
        m_machineStateNode->enableMonitoring(QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters p(100));
        m_machineDesignationNode->readAttributes(QOpcUa::NodeAttribute::Value);
        // ...
    }
    // ...
}