How to automatically reconnect to QtOpcUa server after disconnection
Unsolved
General and Desktop
-
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:- the first time I run the code I execute the
findServers
andgetEndpoints
functions. - then I try to connect to the desired endpoint (always the first one in my usecase)
- 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.
- the first time I run the code I execute the