Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. How to automatically reconnect to QtOpcUa server after disconnection
Forum Updated to NodeBB v4.3 + New Features

How to automatically reconnect to QtOpcUa server after disconnection

Scheduled Pinned Locked Moved Unsolved General and Desktop
1 Posts 1 Posters 158 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    Mark81
    wrote on last edited by
    #1

    My code is based on the QtOpcUaViewer example, but I have hard time to automatically reconnect my client to a server after a disconnection (i.e. the machine is turned off and after some time turned on again).

    I catch the following signals:

    void MyOpcUa::clientError(QOpcUaClient::ClientError error)
    {
        _clientError = true;
    }
    
    void MyOpcUa::clientState(QOpcUaClient::ClientState state)
    {
        switch (state)
        {
        case QOpcUaClient::ClientState::Connected:
            _clientConnected = true;
            break;
    
        case QOpcUaClient::ClientState::Disconnected:
            _clientConnected = false;
            break;
    
        default:
            break;
        }
    }
    

    and if I get a disconnection I try again to connect to the endpoint.
    Here I summarize the behavior:

    1. the first time I run the code I execute the findServers and getEndpoints functions.
    2. then I try to connect to the desired endpoint (always the first one in my usecase)
    3. when a disconnection is detected I try again to connect to the endopoint after some time (I mean, I don't discover again the host and the endpoints, since they don't change)

    The problem is when it tries to reconnect, it still fails, like the machine is still offline.
    Here my code:

    myopcua.h

    #ifndef MYOPCUA_H
    #define MYOPCUA_H
    
    #include <QObject>
    #include <QOpcUaProvider>
    #include <QOpcUaClient>
    #include <QOpcUaNode>
    #include <QProcess>
    #include <QTimer>
    
    class MyOpcUa : public QObject
    {
        Q_OBJECT
    
    public:
        typedef struct
        {
            QOpcUaNode *node;
            QVariant value;
            bool updated;
            bool enableRead;
        } Node_t;
    
        explicit MyOpcUa(QObject *parent = nullptr);
        ~MyOpcUa();
    
        void setConfiguration(QOpcUaPkiConfiguration *pkiConfig);
        void setName(QString name) { _name = name; }
        void discoverHost(QUrl url);
        bool discoverComplete() { return _endpointList.size(); }
        bool isConnected() { return _clientConnected; }
        bool isError() { return _clientError; }
        void clearNodes();
        void insertNode(QString key, QString id, bool enableRead);
        bool writeNodeValue(QString key, QVariant value, QOpcUa::Types type);
        void enableMonitoring();
        void disableMonitoring();
    
    private:
        enum class States
        {
            Idle,
            Connecting,
            Connected,
            Error,
        };
        States _state;
        bool _timerConnectedTimeout = false;
    
        bool _clientConnected = false;
        bool _clientError = false;
        QUrl _url;
        QString _name;
    
        QOpcUaProvider *_opcUaProvider;
        QOpcUaClient *_opcUaClient = nullptr;
        QList<QOpcUaEndpointDescription> _endpointList;
        QOpcUaApplicationIdentity _identity;
        QOpcUaPkiConfiguration *_pkiConfig;
        QOpcUaEndpointDescription _endpoint;
    
        QStringList _servers;
        QStringList _endpoints;
    
        QMap<QString, Node_t> _mapOpcNodes;
    
        void createClient();
        QString retrieveNode(QString nodeId);
    
        QTimer _timerFsm;
        QTimer _timerConnection;
    
    signals:
        void clientConnectedChanged(bool connected);
        void valueChanged(QString key, QVariant value);
        void dataUploaded();
    
    private slots:
        void connectToServer(int idxEndpoint);
        void reconnect() { connectToServer(0); }
        void findServersComplete(const QList<QOpcUaApplicationDescription> &servers, QOpcUa::UaStatusCode statusCode);
        void getEndpoints(QString server);
        void getEndpointsComplete(const QList<QOpcUaEndpointDescription> &endpoints, QOpcUa::UaStatusCode statusCode);
        void clientConnected();
        void clientDisconnected();
        void namespacesArrayUpdated(const QStringList &namespaceArray);
        void clientError(QOpcUaClient::ClientError);
        void clientState(QOpcUaClient::ClientState);
        void connectError(QOpcUaErrorState *errorState);
        void handleAttributes();
        void fsm();
        void timerConnected_timeout();
    
    public slots:
        void disconnectFromServer();
    
    };
    
    #endif // MYOPCUA_H
    

    myopcua.cpp

    #include "myopcua.h"
    #include <QCoreApplication>
    #include <QDir>
    #include <QOpcUaAuthenticationInformation>
    #include <QOpcUaErrorState>
    #include <QDebug>
    #include <QHostInfo>
    
    #define DEBUG
    
    const QString ID("[OPCUA]");
    const QString OPC_UA_BACKEND("open62541");
    const int RECONNECTION_INTERVAL = 10000;
    const int POLLING_INTERVAL = 1000;
    
    MyOpcUa::MyOpcUa(QObject *parent) : QObject(parent)
    {
        _state = States::Idle;
    
        connect(&_timerFsm, &QTimer::timeout, this, &MyOpcUa::fsm);
        _timerFsm.start(1000);
    
        _timerConnection.setInterval(RECONNECTION_INTERVAL);
        _timerConnection.setSingleShot(true);
        connect(&_timerConnection, &QTimer::timeout, this, &MyOpcUa::timerConnected_timeout);
    
        _opcUaProvider = new QOpcUaProvider(this);
    }
    
    MyOpcUa::~MyOpcUa()
    {
    #ifdef DEBUG
        qInfo() << ID << _name << "Disconnecting from host";
    #endif
        _timerConnection.stop();
        _timerFsm.stop();
        disconnectFromServer();
    }
    
    void MyOpcUa::setConfiguration(QOpcUaPkiConfiguration *pkiConfig)
    {
        if (pkiConfig == nullptr)
        {
            qWarning() << ID << _name << "Invalid config";
            return;
        }
    
        _pkiConfig = pkiConfig;
        _identity = _pkiConfig->applicationIdentity();
    }
    
    void MyOpcUa::createClient()
    {
        Q_ASSERT(_opcUaClient == nullptr);
    
        if (_opcUaClient == nullptr)
        {
          _opcUaClient = _opcUaProvider->createClient(OPC_UA_BACKEND);
          if (!_opcUaClient)
          {
              qWarning() << ID << _name << "Connection to server failed:" << _opcUaClient->error();
              return;
          }
    
          connect(_opcUaClient, &QOpcUaClient::connectError, this, &MyOpcUa::connectError);
          _opcUaClient->setApplicationIdentity(_identity);
          _opcUaClient->setPkiConfiguration(*_pkiConfig);
    
          if (_opcUaClient->supportedUserTokenTypes().contains(QOpcUaUserTokenPolicy::TokenType::Certificate))
          {
              QOpcUaAuthenticationInformation authInfo;
              authInfo.setCertificateAuthentication();
              _opcUaClient->setAuthenticationInformation(authInfo);
          }
    
          connect(_opcUaClient, &QOpcUaClient::connected, this, &MyOpcUa::clientConnected);
          connect(_opcUaClient, &QOpcUaClient::disconnected, this, &MyOpcUa::clientDisconnected);
          connect(_opcUaClient, &QOpcUaClient::errorChanged, this, &MyOpcUa::clientError);
          connect(_opcUaClient, &QOpcUaClient::stateChanged, this, &MyOpcUa::clientState);
          connect(_opcUaClient, &QOpcUaClient::endpointsRequestFinished, this, &MyOpcUa::getEndpointsComplete);
          connect(_opcUaClient, &QOpcUaClient::findServersFinished, this, &MyOpcUa::findServersComplete);
        }
    }
    
    QString MyOpcUa::retrieveNode(QString nodeId)
    {
        QMapIterator<QString, Node_t> i(_mapOpcNodes);
        while (i.hasNext())
        {
            i.next();
            Node_t node = i.value();
            if (node.node->nodeId() == nodeId) return i.key();
        }
    
        return QString();
    }
    
    void MyOpcUa::connectError(QOpcUaErrorState *errorState)
    {
        int result = 0;
    #ifdef DEBUG
        qWarning() << ID << _name << errorState;
    #endif
    
        const QString statuscode = QOpcUa::statusToString(errorState->errorCode());
        QString msg = errorState->isClientSideError() ? tr("The client reported: ") : tr("The server reported: ");
    
        switch (errorState->connectionStep())
        {
        case QOpcUaErrorState::ConnectionStep::Unknown:
            break;
    
        case QOpcUaErrorState::ConnectionStep::CertificateValidation:
            msg += tr("Server certificate validation failed with error 0x%1 (%2).\nClick 'Abort' to abort the connect, or 'Ignore' to continue connecting.").arg(static_cast<ulong>(errorState->errorCode()), 8, 16, QLatin1Char('0')).arg(statuscode);
            qWarning() << ID << msg;
            errorState->setIgnoreError(result == 1);
            break;
    
        case QOpcUaErrorState::ConnectionStep::OpenSecureChannel:
            msg += tr("OpenSecureChannel failed with error 0x%1 (%2).").arg(errorState->errorCode(), 8, 16, QLatin1Char('0')).arg(statuscode);
            qWarning() << ID << msg;
            break;
    
        case QOpcUaErrorState::ConnectionStep::CreateSession:
            msg += tr("CreateSession failed with error 0x%1 (%2).").arg(errorState->errorCode(), 8, 16, QLatin1Char('0')).arg(statuscode);
            qWarning() << ID << msg;
            break;
    
        case QOpcUaErrorState::ConnectionStep::ActivateSession:
            msg += tr("ActivateSession failed with error 0x%1 (%2).").arg(errorState->errorCode(), 8, 16, QLatin1Char('0')).arg(statuscode);
            qWarning() << ID << msg;
            break;
        }
    
        _clientError = true;
        _clientConnected = false;
    }
    
    void MyOpcUa::handleAttributes()
    {
        QOpcUaNode *node = qobject_cast<QOpcUaNode*>(sender());
        QString key = retrieveNode(node->nodeId());
        if (key.isEmpty())
        {
            qWarning() << ID << "Key not found" << key;
        }
        QVariant value = node->attribute(QOpcUa::NodeAttribute::Value);
    
        if (_mapOpcNodes.contains(key))
        {
            _mapOpcNodes[key].updated = true;
            _mapOpcNodes[key].value = value;
            emit valueChanged(key, value);
        }
    }
    
    void MyOpcUa::fsm()
    {
        switch (_state)
        {
        case States::Idle:
            if (!isConnected())
            {
                reconnect();
                _state = States::Connecting;
            }
            else
            {
                _state = States::Connected;
            }
            break;
    
        case States::Connecting:
            if (isConnected())
            {
    #ifdef DEBUG
                qInfo() << ID << _name  << "Connected.";
    #endif
                emit clientConnectedChanged(_clientConnected);
                _state = States::Connected;
            }
            else if (isError())
            {
    #ifdef DEBUG
                qWarning() << ID << _name  << "Detected error. Retry in 10 s";
    #endif
                _timerConnection.start();
                _state = States::Error;
            }
            break;
    
        case States::Connected:
            if (!isConnected())
            {
    #ifdef DEBUG
                qWarning() << ID << _name  << "Detected disconnection. Retry in 10 s";
    #endif
                _timerConnection.start();
                emit clientConnectedChanged(_clientConnected);
                _state = States::Error;
            }
            break;
    
        case States::Error:
            if (_timerConnectedTimeout)
            {
                _timerConnectedTimeout = false;
                _state = States::Idle;
            }
            break;
        }
    }
    
    void MyOpcUa::timerConnected_timeout()
    {
        _timerConnectedTimeout = true;
    }
    
    void MyOpcUa::discoverHost(QUrl url)
    {    
        if (_name.isEmpty())
        {
            qWarning() << "Machine name not defined yet!";
            return;
        }
    
        QStringList localeIds;
        QStringList serverUris;
    
        _url = url;
        createClient();
        if (url.port() == -1) url.setPort(4840);
    
        if (_opcUaClient)
        {
            _opcUaClient->findServers(url, localeIds, serverUris);
        }
        else qDebug() << ID << _name << "error";
    }
    
    void MyOpcUa::clearNodes()
    {
        QMapIterator<QString, Node_t> i(_mapOpcNodes);
        while (i.hasNext())
        {
            i.next();
            Node_t node = i.value();
            node.node->disconnect();
        }
        _mapOpcNodes.clear();
    }
    
    void MyOpcUa::insertNode(QString key, QString id, bool enableRead)
    {
        Node_t node;
        node.node = _opcUaClient->node(id);
        node.value = QVariant();
        node.updated = false;
        node.enableRead = enableRead;
        _mapOpcNodes.insert(key, node);
        connect(node.node, &QOpcUaNode::attributeRead, this, &MyOpcUa::handleAttributes);
        connect(node.node, &QOpcUaNode::attributeUpdated, this, &MyOpcUa::handleAttributes);
    }
    
    bool MyOpcUa::writeNodeValue(QString key, QVariant value, QOpcUa::Types type)
    {
        if (!_mapOpcNodes.contains(key)) return false;
    
        QOpcUaNode *node = _mapOpcNodes[key].node;
        if (node == nullptr) return false;
        return node->writeValueAttribute(value, type);
    }
    
    void MyOpcUa::enableMonitoring()
    {
    #ifdef DEBUG
        qInfo() << ID << _name << "Enable monitoring";
    #endif
    
        QMapIterator<QString, Node_t> i(_mapOpcNodes);
        while (i.hasNext())
        {
            i.next();
            Node_t node = i.value();
            if (node.enableRead)
            {
                QOpcUaMonitoringParameters params(POLLING_INTERVAL);
                params.setMaxKeepAliveCount(1);
                params.setLifetimeCount(3);
                bool ret = node.node->enableMonitoring(QOpcUa::NodeAttribute::Value, params);
    #ifdef DEBUG
                qDebug() << ID << _name << node.node->nodeId() << ret;
    #endif
            }
        }
    }
    
    void MyOpcUa::disableMonitoring()
    {
    #ifdef DEBUG
        qInfo() << ID << _name << "Disable monitoring";
    #endif
        QMapIterator<QString, Node_t> i(_mapOpcNodes);
        while (i.hasNext())
        {
            i.next();
            Node_t node = i.value();
            node.node->disableMonitoring(QOpcUa::NodeAttribute::Value);
        }
    }
    
    void MyOpcUa::findServersComplete(const QList<QOpcUaApplicationDescription> &servers, QOpcUa::UaStatusCode statusCode)
    {
        if (!isSuccessStatus(statusCode))
        {
    #ifdef DEBUG
            qWarning() << ID << _name << statusCode;
    #endif
            _clientError = true;
            _clientConnected = false;
            return;
        }
    
        _servers.clear();
        for (const auto &server : servers)
        {
            const auto urls = server.discoveryUrls();
            for (const auto &url : qAsConst(urls)) _servers.append(url);
        }
    
        getEndpoints(_servers.last());
    }
    
    void MyOpcUa::getEndpoints(QString server)
    {
        _endpoints.clear();
        createClient();
        _opcUaClient->requestEndpoints(QUrl(server));
    }
    
    void MyOpcUa::getEndpointsComplete(const QList<QOpcUaEndpointDescription> &endpoints, QOpcUa::UaStatusCode statusCode)
    {
        const std::array<const char *, 4> modes =
        {
            "Invalid",
            "None",
            "Sign",
            "SignAndEncrypt"
        };
    
        if (!isSuccessStatus(statusCode)) return;
    
        _endpointList = endpoints;
        for (const auto &endpoint : endpoints)
        {
            const QString EndpointName = QString("%1 (%2)").arg(endpoint.securityPolicy(), modes[endpoint.securityMode()]);
            _endpoints.append(EndpointName);
        }
    
    #ifdef DEBUG
        qInfo() << ID << _name << _endpoints;
    #endif
    }
    
    void MyOpcUa::connectToServer(int idxEndpoint)
    {
    #ifdef DEBUG
        qInfo() << ID << _name << "Connect to endpoint" << idxEndpoint;
    #endif
    
        if (idxEndpoint < _endpointList.size())
        {
            _clientConnected = false;
            _clientError = false;
            _endpoint = _endpointList[idxEndpoint];
            createClient();
            _opcUaClient->connectToEndpoint(_endpoint);
        }
    }
    
    void MyOpcUa::disconnectFromServer()
    {
        if (_clientConnected)
        {
            _timerConnection.stop();
            _timerFsm.stop();
    #ifdef DEBUG
            qInfo() << ID << _name << "Disconnecting from host";
    #endif
            disableMonitoring();
            emit clientConnectedChanged(false);
            _opcUaClient->disconnectFromEndpoint();
        }
    }
    
    void MyOpcUa::clientConnected()
    {
    #ifdef DEBUG
        qDebug() << ID << _name << "clientConnected";
    #endif
        _timerConnection.stop();
        _clientConnected = true;
        _clientError = false;
        connect(_opcUaClient, &QOpcUaClient::namespaceArrayUpdated, this, &MyOpcUa::namespacesArrayUpdated);
        _opcUaClient->updateNamespaceArray();
    }
    
    void MyOpcUa::clientDisconnected()
    {
    #ifdef DEBUG
        qDebug() << ID << _name << "disconnected!";
    #endif
        _clientConnected = false;
        _opcUaClient->deleteLater();
        _opcUaClient = nullptr;
    }
    
    void MyOpcUa::namespacesArrayUpdated(const QStringList &namespaceArray)
    {
        if (namespaceArray.isEmpty())
        {
            qWarning() << ID << _name << "Failed to retrieve the namespaces array";
            return;
        }
        disconnect(_opcUaClient, &QOpcUaClient::namespaceArrayUpdated, this, &MyOpcUa::namespacesArrayUpdated);
    }
    
    void MyOpcUa::clientError(QOpcUaClient::ClientError error)
    {
    #ifdef DEBUG
        qWarning() << ID << _name << "Client error changed" << error;
    #endif
        _clientError = true;
    }
    
    void MyOpcUa::clientState(QOpcUaClient::ClientState state)
    {
    #ifdef DEBUG
        qInfo() << ID << _name << "Client state changed" << state;
    #endif
    
        switch (state)
        {
        case QOpcUaClient::ClientState::Connected:
            _clientConnected = true;
            break;
    
        case QOpcUaClient::ClientState::Disconnected:
            _clientConnected = false;
            _clientError = true;
            break;
    
        default:
            break;
        }
    }
    

    I create an instance of this class for each machine I have to connect to.

    1 Reply Last reply
    0

    • Login

    • Login or register to search.
    • First post
      Last post
    0
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Get Qt Extensions
    • Unsolved