summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJannis Voelker <jannis.voelker@basyskom.com>2020-09-14 12:15:49 +0200
committerJannis Voelker <jannis.voelker@basyskom.com>2021-02-02 08:46:37 +0100
commite09f1fea7ccb6f2dcc9d514578a688d47b919e5c (patch)
treeb239d8086a5a776a42555d1289e7af0ded87d5fb /src
parent1dc9d9f34b353dea1ab119480fa5aabcdc4079db (diff)
open62541: Use the async API for most service calls
- Read / Write - Browse - TranslateBrowsePathsToNodeIDs - Call - AddNodes / DeleteNodes - AddReferences / DeleteReferences This fixes the disconnect of the open62541 backend when a service call mentioned in the list above takes longer than the default timeout of 5 seconds. Change-Id: Ib991b8bb90651969a138fd70523aaac960d7715f Task-number: QTBUG-86360 Reviewed-by: Frank Meerkoetter <frank.meerkoetter@basyskom.com>
Diffstat (limited to 'src')
-rw-r--r--src/imports/opcua/opcuaattributevalue.cpp2
-rw-r--r--src/opcua/core/qopcuaprovider.cpp16
-rw-r--r--src/plugins/opcua/open62541/qopen62541backend.cpp772
-rw-r--r--src/plugins/opcua/open62541/qopen62541backend.h89
-rw-r--r--src/plugins/opcua/open62541/qopen62541client.cpp12
5 files changed, 619 insertions, 272 deletions
diff --git a/src/imports/opcua/opcuaattributevalue.cpp b/src/imports/opcua/opcuaattributevalue.cpp
index 493d7d0..c93f55e 100644
--- a/src/imports/opcua/opcuaattributevalue.cpp
+++ b/src/imports/opcua/opcuaattributevalue.cpp
@@ -64,7 +64,7 @@ bool OpcUaAttributeValue::operator ==(const OpcUaAttributeValue &rhs)
void OpcUaAttributeValue::setValue(const QVariant &value)
{
- if (value != m_value) {
+ if (value.metaType() != m_value.metaType() || value != m_value) {
m_value = value;
emit changed(m_value);
}
diff --git a/src/opcua/core/qopcuaprovider.cpp b/src/opcua/core/qopcuaprovider.cpp
index 352bed3..82a990a 100644
--- a/src/opcua/core/qopcuaprovider.cpp
+++ b/src/opcua/core/qopcuaprovider.cpp
@@ -237,8 +237,20 @@ static QOpcUaPlugin *loadPlugin(const QString &key)
\row
\li minimumClientIterateIntervalMs
\li open62541
- \li Defines the client iterate interval for the backend. This value can be used to make a tradeoff
- between reaction time for subscriptions and CPU load. The default value is 50ms.
+ \li This parameter is no longer evaluated by the backend and has been replaced by \c clientIterateIntervalMs.
+ \row
+ \li clientIterateIntervalMs
+ \li open62541
+ \li Defines the client iterate interval for the backend. If the client is causing too much CPU load,
+ setting this value higher than the default will reduce the CPU load at the price of an increased
+ response time to service requests and value updates from subscriptions.
+ The default value is 50ms.
+ \row
+ \li asyncRequestTimeoutMs
+ \li open62541
+ \li Defines the timeout for asynchronous requests to an OPC UA server. If the server doesn't reply to
+ a service request before the timeout occurs, the service call fails and the finished signal will
+ contain a \c bad status code. The default value is 15000ms.
\endtable
*/
QOpcUaClient *QOpcUaProvider::createClient(const QString &backend, const QVariantMap &backendProperties)
diff --git a/src/plugins/opcua/open62541/qopen62541backend.cpp b/src/plugins/opcua/open62541/qopen62541backend.cpp
index 92ddb9f..d80180e 100644
--- a/src/plugins/opcua/open62541/qopen62541backend.cpp
+++ b/src/plugins/opcua/open62541/qopen62541backend.cpp
@@ -61,8 +61,8 @@ Open62541AsyncBackend::Open62541AsyncBackend(QOpen62541Client *parent)
, m_uaclient(nullptr)
, m_clientImpl(parent)
, m_useStateCallback(false)
- , m_minimumIterateInterval(50)
- , m_maximumIterateInterval(5000)
+ , m_clientIterateInterval(50)
+ , m_asyncRequestTimeout(15000)
, m_clientIterateTimer(this)
, m_minPublishingInterval(0)
{
@@ -79,56 +79,51 @@ Open62541AsyncBackend::~Open62541AsyncBackend()
void Open62541AsyncBackend::readAttributes(quint64 handle, UA_NodeId id, QOpcUa::NodeAttributes attr, QString indexRange)
{
+ UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_clear);
+
UA_ReadRequest req;
UA_ReadRequest_init(&req);
- QList<UA_ReadValueId> valueIds;
+ UaDeleter<UA_ReadRequest> requestDeleter(&req, UA_ReadRequest_clear);
+ req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
- UA_ReadValueId readId;
- UA_ReadValueId_init(&readId);
- UaDeleter<UA_ReadValueId> readIdDeleter(&readId, UA_ReadValueId_deleteMembers);
- readId.nodeId = id;
+ qt_forEachAttribute(attr, [&req](QOpcUa::NodeAttribute attr) {
+ Q_UNUSED(attr);
+ ++req.nodesToReadSize;
+ });
- QList<QOpcUaReadResult> vec;
+ if (req.nodesToReadSize)
+ req.nodesToRead = static_cast<UA_ReadValueId *>(UA_Array_new(req.nodesToReadSize, &UA_TYPES[UA_TYPES_READVALUEID]));
+ QList<QOpcUaReadResult> resultMetadata;
+ size_t index = 0;
qt_forEachAttribute(attr, [&](QOpcUa::NodeAttribute attribute){
- readId.attributeId = QOpen62541ValueConverter::toUaAttributeId(attribute);
+ auto &current = req.nodesToRead[index++];
+
+ current.attributeId = QOpen62541ValueConverter::toUaAttributeId(attribute);
+ UA_NodeId_copy(&id, &current.nodeId);
if (indexRange.length())
- QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(indexRange, &readId.indexRange);
- valueIds.push_back(readId);
+ QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(indexRange, &current.indexRange);
+
QOpcUaReadResult temp;
temp.setAttribute(attribute);
- vec.push_back(temp);
+ resultMetadata.push_back(temp);
});
- UA_ReadResponse res;
- UA_ReadResponse_init(&res);
- req.nodesToRead = valueIds.data();
- req.nodesToReadSize = valueIds.size();
- req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH;
-
- res = UA_Client_Service_read(m_uaclient, req);
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &req, &UA_TYPES[UA_TYPES_READREQUEST],
+ &asyncReadCallback, &UA_TYPES[UA_TYPES_READRESPONSE], this,
+ &requestId, m_asyncRequestTimeout);
- UaDeleter<UA_ReadResponse> responseDeleter(&res, UA_ReadResponse_deleteMembers);
-
- for (int i = 0; i < vec.size(); ++i) {
- // Use the service result as status code if there is no specific result for the current value.
- // This ensures a result for each attribute when UA_Client_Service_read is called for a disconnected client.
- if (static_cast<size_t>(i) >= res.resultsSize) {
- vec[i].setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult));
- continue;
+ if (result != UA_STATUSCODE_GOOD) {
+ const auto statusCode = static_cast<QOpcUa::UaStatusCode>(result);
+ for (auto &entry : resultMetadata) {
+ entry.setStatusCode(statusCode);
}
- if (res.results[i].hasStatus)
- vec[i].setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.results[i].status));
- else
- vec[i].setStatusCode(QOpcUa::UaStatusCode::Good);
- if (res.results[i].hasValue && res.results[i].value.data)
- vec[i].setValue(QOpen62541ValueConverter::toQVariant(res.results[i].value));
- if (res.results[i].hasServerTimestamp)
- vec[i].setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(&res.results[i].sourceTimestamp));
- if (res.results[i].hasSourceTimestamp)
- vec[i].setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(&res.results[i].serverTimestamp));
- }
- emit attributesRead(handle, vec, static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult));
+ emit attributesRead(handle, resultMetadata, statusCode);
+ return;
+ }
+
+ m_asyncReadContext[requestId] = { handle, resultMetadata };
}
void Open62541AsyncBackend::writeAttribute(quint64 handle, UA_NodeId id, QOpcUa::NodeAttribute attrId, QVariant value, QOpcUa::Types type, QString indexRange)
@@ -138,7 +133,7 @@ void Open62541AsyncBackend::writeAttribute(quint64 handle, UA_NodeId id, QOpcUa:
UA_WriteRequest req;
UA_WriteRequest_init(&req);
- UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_deleteMembers);
+ UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_clear);
req.nodesToWriteSize = 1;
req.nodesToWrite = UA_WriteValue_new();
@@ -150,18 +145,22 @@ void Open62541AsyncBackend::writeAttribute(quint64 handle, UA_NodeId id, QOpcUa:
if (indexRange.length())
QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(indexRange, &req.nodesToWrite->indexRange);
- UA_WriteResponse res = UA_Client_Service_write(m_uaclient, req);
- UaDeleter<UA_WriteResponse> responseDeleter(&res, UA_WriteResponse_deleteMembers);
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &req, &UA_TYPES[UA_TYPES_WRITEREQUEST],
+ &asyncWriteAttributesCallback, &UA_TYPES[UA_TYPES_WRITERESPONSE], this,
+ &requestId, m_asyncRequestTimeout);
- QOpcUa::UaStatusCode status = res.resultsSize ?
- static_cast<QOpcUa::UaStatusCode>(res.results[0]) : static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult);
+ if (result != UA_STATUSCODE_GOOD) {
+ emit attributeWritten(handle, attrId, value, static_cast<QOpcUa::UaStatusCode>(result));
+ return;
+ }
- emit attributeWritten(handle, attrId, value, status);
+ m_asyncWriteAttributesContext[requestId] = { handle, {{attrId, value}} };
}
void Open62541AsyncBackend::writeAttributes(quint64 handle, UA_NodeId id, QOpcUaNode::AttributeMap toWrite, QOpcUa::Types valueAttributeType)
{
- UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_deleteMembers);
+ UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_clear);
if (toWrite.size() == 0) {
qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "No values to be written";
@@ -171,7 +170,7 @@ void Open62541AsyncBackend::writeAttributes(quint64 handle, UA_NodeId id, QOpcUa
UA_WriteRequest req;
UA_WriteRequest_init(&req);
- UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_deleteMembers);
+ UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_clear);
req.nodesToWriteSize = toWrite.size();
req.nodesToWrite = static_cast<UA_WriteValue *>(UA_Array_new(req.nodesToWriteSize, &UA_TYPES[UA_TYPES_WRITEVALUE]));
size_t index = 0;
@@ -182,15 +181,21 @@ void Open62541AsyncBackend::writeAttributes(quint64 handle, UA_NodeId id, QOpcUa
QOpcUa::Types type = it.key() == QOpcUa::NodeAttribute::Value ? valueAttributeType : attributeIdToTypeId(it.key());
req.nodesToWrite[index].value.value = QOpen62541ValueConverter::toOpen62541Variant(it.value(), type);
}
- UA_WriteResponse res = UA_Client_Service_write(m_uaclient, req);
- UaDeleter<UA_WriteResponse> responseDeleter(&res, UA_WriteResponse_deleteMembers);
- index = 0;
- for (auto it = toWrite.begin(); it != toWrite.end(); ++it, ++index) {
- QOpcUa::UaStatusCode status = index < res.resultsSize ?
- static_cast<QOpcUa::UaStatusCode>(res.results[index]) : static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult);
- emit attributeWritten(handle, it.key(), it.value(), status);
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &req, &UA_TYPES[UA_TYPES_WRITEREQUEST],
+ &asyncWriteAttributesCallback, &UA_TYPES[UA_TYPES_WRITERESPONSE], this,
+ &requestId, m_asyncRequestTimeout);
+
+ if (result != UA_STATUSCODE_GOOD) {
+ index = 0;
+ for (auto it = toWrite.begin(); it != toWrite.end(); ++it) {
+ emit attributeWritten(handle, it.key(), it.value(), static_cast<QOpcUa::UaStatusCode>(result));
+ }
+ return;
}
+
+ m_asyncWriteAttributesContext[requestId] = { handle, toWrite };
}
void Open62541AsyncBackend::enableMonitoring(quint64 handle, UA_NodeId id, QOpcUa::NodeAttributes attr, const QOpcUaMonitoringParameters &settings)
@@ -242,8 +247,6 @@ void Open62541AsyncBackend::enableMonitoring(quint64 handle, UA_NodeId id, QOpcU
if (usedSubscription->monitoredItemsCount() == 0)
removeSubscription(usedSubscription->subscriptionId()); // No items were added
-
- reevaluateClientIterateTimer();
}
void Open62541AsyncBackend::disableMonitoring(quint64 handle, QOpcUa::NodeAttributes attr)
@@ -257,7 +260,6 @@ void Open62541AsyncBackend::disableMonitoring(quint64 handle, QOpcUa::NodeAttrib
removeSubscription(sub->subscriptionId());
}
});
- reevaluateClientIterateTimer();
}
void Open62541AsyncBackend::modifyMonitoring(quint64 handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value)
@@ -272,7 +274,6 @@ void Open62541AsyncBackend::modifyMonitoring(quint64 handle, QOpcUa::NodeAttribu
}
subscription->modifyMonitoring(handle, attr, item, value);
- reevaluateClientIterateTimer();
}
QOpen62541Subscription *Open62541AsyncBackend::getSubscription(const QOpcUaMonitoringParameters &settings)
@@ -307,7 +308,6 @@ bool Open62541AsyncBackend::removeSubscription(UA_UInt32 subscriptionId)
sub.value()->removeOnServer();
delete sub.value();
m_subscriptions.remove(subscriptionId);
- reevaluateClientIterateTimer();
return true;
}
return false;
@@ -315,9 +315,6 @@ bool Open62541AsyncBackend::removeSubscription(UA_UInt32 subscriptionId)
void Open62541AsyncBackend::callMethod(quint64 handle, UA_NodeId objectId, UA_NodeId methodId, QList<QOpcUa::TypedVariant> args)
{
- UaDeleter<UA_NodeId> objectIdDeleter(&objectId, UA_NodeId_deleteMembers);
- UaDeleter<UA_NodeId> methodIdDeleter(&methodId, UA_NodeId_deleteMembers);
-
UA_Variant *inputArgs = nullptr;
if (args.size()) {
@@ -325,29 +322,29 @@ void Open62541AsyncBackend::callMethod(quint64 handle, UA_NodeId objectId, UA_No
for (int i = 0; i < args.size(); ++i)
inputArgs[i] = QOpen62541ValueConverter::toOpen62541Variant(args[i].first, args[i].second);
}
- UaArrayDeleter<UA_TYPES_VARIANT> inputArgsDeleter(inputArgs, args.size());
- size_t outputSize = 0;
- UA_Variant *outputArguments = nullptr;
- UA_StatusCode res = UA_Client_call(m_uaclient, objectId, methodId, args.size(), inputArgs, &outputSize, &outputArguments);
- UaArrayDeleter<UA_TYPES_VARIANT> outputArgsDeleter(outputArguments, outputSize);
+ quint32 requestId = 0;
- if (res != UA_STATUSCODE_GOOD)
- qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not call method:" << UA_StatusCode_name(res);
+ UA_CallRequest request;
+ UA_CallRequest_init(&request);
+ UaDeleter<UA_CallRequest> requestDeleter(&request, UA_CallRequest_clear);
- QVariant result;
+ request.methodsToCallSize = 1;
+ request.methodsToCall = UA_CallMethodRequest_new();
+ request.methodsToCall->objectId = objectId;
+ request.methodsToCall->methodId = methodId;
+ request.methodsToCall->inputArguments = inputArgs;
+ request.methodsToCall->inputArgumentsSize = args.size();
- if (outputSize > 1 && res == UA_STATUSCODE_GOOD) {
- QVariantList temp;
- for (size_t i = 0; i < outputSize; ++i)
- temp.append(QOpen62541ValueConverter::toQVariant(outputArguments[i]));
-
- result = temp;
- } else if (outputSize == 1 && res == UA_STATUSCODE_GOOD) {
- result = QOpen62541ValueConverter::toQVariant(outputArguments[0]);
- }
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &request, &UA_TYPES[UA_TYPES_CALLREQUEST],
+ &asyncMethodCallback,
+ &UA_TYPES[UA_TYPES_CALLRESPONSE],
+ this, &requestId, m_asyncRequestTimeout);
+ if (result != UA_STATUSCODE_GOOD)
+ emit methodCallFinished(handle, Open62541Utils::nodeIdToQString(methodId), QVariant(),
+ static_cast<QOpcUa::UaStatusCode>(result));
- emit methodCallFinished(handle, Open62541Utils::nodeIdToQString(methodId), result, static_cast<QOpcUa::UaStatusCode>(res));
+ m_asyncCallContext[requestId] = { handle, Open62541Utils::nodeIdToQString(methodId) };
}
void Open62541AsyncBackend::resolveBrowsePath(quint64 handle, UA_NodeId startNode, const QList<QOpcUaRelativePathElement> &path)
@@ -372,28 +369,20 @@ void Open62541AsyncBackend::resolveBrowsePath(quint64 handle, UA_NodeId startNod
path[i].targetName().name().toUtf8().constData());
}
- UA_TranslateBrowsePathsToNodeIdsResponse res = UA_Client_Service_translateBrowsePathsToNodeIds(m_uaclient, req);
- UaDeleter<UA_TranslateBrowsePathsToNodeIdsResponse> responseDeleter(
- &res, UA_TranslateBrowsePathsToNodeIdsResponse_deleteMembers);
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &req, &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSREQUEST],
+ &asyncTranslateBrowsePathCallback,
+ &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSRESPONSE],
+ this, &requestId, m_asyncRequestTimeout);
- if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD || res.resultsSize != 1) {
- qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Translate browse path failed:" << UA_StatusCode_name(res.responseHeader.serviceResult);
+ if (result != UA_STATUSCODE_GOOD) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Translate browse path failed:" << UA_StatusCode_name(result);
emit resolveBrowsePathFinished(handle, QList<QOpcUaBrowsePathTarget>(), path,
- static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult));
+ static_cast<QOpcUa::UaStatusCode>(result));
return;
}
- QList<QOpcUaBrowsePathTarget> ret;
- for (size_t i = 0; i < res.results[0].targetsSize ; ++i) {
- QOpcUaBrowsePathTarget temp;
- temp.setRemainingPathIndex(res.results[0].targets[i].remainingPathIndex);
- temp.targetIdRef().setNamespaceUri(QString::fromUtf8(reinterpret_cast<char *>(res.results[0].targets[i].targetId.namespaceUri.data)));
- temp.targetIdRef().setServerIndex(res.results[0].targets[i].targetId.serverIndex);
- temp.targetIdRef().setNodeId(Open62541Utils::nodeIdToQString(res.results[0].targets[i].targetId.nodeId));
- ret.append(temp);
- }
-
- emit resolveBrowsePathFinished(handle, ret, path, static_cast<QOpcUa::UaStatusCode>(res.results[0].statusCode));
+ m_asyncTranslateContext[requestId] = { handle, path };
}
void Open62541AsyncBackend::open62541LogHandler (void *logContext, UA_LogLevel level, UA_LogCategory category,
@@ -516,40 +505,17 @@ void Open62541AsyncBackend::readNodeAttributes(const QList<QOpcUaReadItem> &node
&req.nodesToRead[i].indexRange);
}
- UA_ReadResponse res = UA_Client_Service_read(m_uaclient, req);
- UaDeleter<UA_ReadResponse> responseDeleter(&res, UA_ReadResponse_deleteMembers);
-
- QOpcUa::UaStatusCode serviceResult = static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult);
-
- if (serviceResult != QOpcUa::UaStatusCode::Good) {
- qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch read failed:" << serviceResult;
- emit readNodeAttributesFinished(QList<QOpcUaReadResult>(), serviceResult);
- } else {
- QList<QOpcUaReadResult> ret;
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &req, &UA_TYPES[UA_TYPES_READREQUEST], &asyncBatchReadCallback,
+ &UA_TYPES[UA_TYPES_READRESPONSE], this, &requestId, m_asyncRequestTimeout);
- for (int i = 0; i < nodesToRead.size(); ++i) {
- QOpcUaReadResult item;
- item.setAttribute(nodesToRead.at(i).attribute());
- item.setNodeId(nodesToRead.at(i).nodeId());
- item.setIndexRange(nodesToRead.at(i).indexRange());
- if (static_cast<size_t>(i) < res.resultsSize) {
- if (res.results[i].hasServerTimestamp)
- item.setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime>(&res.results[i].serverTimestamp));
- if (res.results[i].hasSourceTimestamp)
- item.setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime>(&res.results[i].sourceTimestamp));
- if (res.results[i].hasValue)
- item.setValue(QOpen62541ValueConverter::toQVariant(res.results[i].value));
- if (res.results[i].hasStatus)
- item.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.results[i].status));
- else
- item.setStatusCode(serviceResult);
- } else {
- item.setStatusCode(serviceResult);
- }
- ret.push_back(item);
- }
- emit readNodeAttributesFinished(ret, serviceResult);
+ if (result != UA_STATUSCODE_GOOD) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch read failed:" << result;
+ emit readNodeAttributesFinished(QList<QOpcUaReadResult>(), static_cast<QOpcUa::UaStatusCode>(result));
+ return;
}
+
+ m_asyncBatchReadContext[requestId] = { nodesToRead };
}
void Open62541AsyncBackend::writeNodeAttributes(const QList<QOpcUaWriteItem> &nodesToWrite)
@@ -593,30 +559,17 @@ void Open62541AsyncBackend::writeNodeAttributes(const QList<QOpcUaWriteItem> &no
}
}
- UA_WriteResponse res = UA_Client_Service_write(m_uaclient, req);
- UaDeleter<UA_WriteResponse> responseDeleter(&res, UA_WriteResponse_deleteMembers);
-
- QOpcUa::UaStatusCode serviceResult = QOpcUa::UaStatusCode(res.responseHeader.serviceResult);
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &req, &UA_TYPES[UA_TYPES_WRITEREQUEST], &asyncBatchWriteCallback,
+ &UA_TYPES[UA_TYPES_WRITERESPONSE], this, &requestId, m_asyncRequestTimeout);
- if (serviceResult != QOpcUa::UaStatusCode::Good) {
- qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch write failed:" << serviceResult;
- emit writeNodeAttributesFinished(QList<QOpcUaWriteResult>(), serviceResult);
- } else {
- QList<QOpcUaWriteResult> ret;
-
- for (int i = 0; i < nodesToWrite.size(); ++i) {
- QOpcUaWriteResult item;
- item.setAttribute(nodesToWrite.at(i).attribute());
- item.setNodeId(nodesToWrite.at(i).nodeId());
- item.setIndexRange(nodesToWrite.at(i).indexRange());
- if (static_cast<size_t>(i) < res.resultsSize)
- item.setStatusCode(QOpcUa::UaStatusCode(res.results[i]));
- else
- item.setStatusCode(serviceResult);
- ret.push_back(item);
- }
- emit writeNodeAttributesFinished(ret, serviceResult);
+ if (result != UA_STATUSCODE_GOOD) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch read failed:" << result;
+ emit readNodeAttributesFinished(QList<QOpcUaReadResult>(), static_cast<QOpcUa::UaStatusCode>(result));
+ return;
}
+
+ m_asyncBatchWriteContext[requestId] = { nodesToWrite };
}
void Open62541AsyncBackend::addNode(const QOpcUaAddNodeItem &nodeToAdd)
@@ -648,95 +601,125 @@ void Open62541AsyncBackend::addNode(const QOpcUaAddNodeItem &nodeToAdd)
QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(
nodeToAdd.typeDefinition(), &req.nodesToAdd->typeDefinition);
- UA_AddNodesResponse res = UA_Client_Service_addNodes(m_uaclient, req);
- UaDeleter<UA_AddNodesResponse> responseDeleter(&res, UA_AddNodesResponse_deleteMembers);
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &req, &UA_TYPES[UA_TYPES_ADDNODESREQUEST],
+ &asyncAddNodeCallback,
+ &UA_TYPES[UA_TYPES_ADDNODESRESPONSE],
+ this, &requestId, m_asyncRequestTimeout);
- QOpcUa::UaStatusCode status = QOpcUa::UaStatusCode::Good;
- QString resultId;
- if (res.responseHeader.serviceResult == UA_STATUSCODE_GOOD) {
- if (res.results[0].statusCode == UA_STATUSCODE_GOOD)
- resultId = Open62541Utils::nodeIdToQString(res.results[0].addedNodeId);
- else {
- status = static_cast<QOpcUa::UaStatusCode>(res.results[0].statusCode);
- qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:" << status;
- }
- } else {
- status = static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult);
- qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:" << status;
+ if (result != UA_STATUSCODE_GOOD) {
+ qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:" << result;
+ emit addNodeFinished(nodeToAdd.requestedNewNodeId(), QString(), static_cast<QOpcUa::UaStatusCode>(result));
+ return;
}
- emit addNodeFinished(nodeToAdd.requestedNewNodeId(), resultId, status);
+ m_asyncAddNodeContext[requestId] = { nodeToAdd.requestedNewNodeId() };
}
void Open62541AsyncBackend::deleteNode(const QString &nodeId, bool deleteTargetReferences)
{
- UA_NodeId id = Open62541Utils::nodeIdFromQString(nodeId);
- UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_deleteMembers);
+ UA_DeleteNodesRequest request;
+ UA_DeleteNodesRequest_init(&request);
+ UaDeleter<UA_DeleteNodesRequest> requestDeleter(&request, UA_DeleteNodesRequest_deleteMembers);
- UA_StatusCode res = UA_Client_deleteNode(m_uaclient, id, deleteTargetReferences);
+ request.nodesToDeleteSize = 1;
+ request.nodesToDelete = UA_DeleteNodesItem_new();
- QOpcUa::UaStatusCode resultStatus = static_cast<QOpcUa::UaStatusCode>(res);
+ request.nodesToDelete->nodeId = Open62541Utils::nodeIdFromQString(nodeId);
+ request.nodesToDelete->deleteTargetReferences = deleteTargetReferences;
- if (resultStatus != QOpcUa::UaStatusCode::Good) {
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &request, &UA_TYPES[UA_TYPES_DELETENODESREQUEST],
+ &asyncDeleteNodeCallback,
+ &UA_TYPES[UA_TYPES_DELETENODESRESPONSE],
+ this, &requestId, m_asyncRequestTimeout);
+
+ QOpcUa::UaStatusCode resultStatus = static_cast<QOpcUa::UaStatusCode>(result);
+
+ if (result != QOpcUa::UaStatusCode::Good) {
qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to delete node" << nodeId << "with status code" << resultStatus;
+ emit deleteNodeFinished(nodeId, resultStatus);
+ return;
}
- emit deleteNodeFinished(nodeId, resultStatus);
+ m_asyncDeleteNodeContext[requestId] = { nodeId };
}
void Open62541AsyncBackend::addReference(const QOpcUaAddReferenceItem &referenceToAdd)
{
- UA_ExpandedNodeId target;
- UA_ExpandedNodeId_init(&target);
- UaDeleter<UA_ExpandedNodeId> nodeIdDeleter(&target, UA_ExpandedNodeId_deleteMembers);
-
- QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(
- referenceToAdd.targetNodeId(), &target);
-
- UA_String serverUri;
- UaDeleter<UA_String> serverUriDeleter(&serverUri, UA_String_deleteMembers);
- QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(
- referenceToAdd.targetServerUri(), &serverUri);
-
- UA_NodeClass nodeClass = static_cast<UA_NodeClass>(referenceToAdd.targetNodeClass());
-
- UA_StatusCode res = UA_Client_addReference(m_uaclient,
- Open62541Utils::nodeIdFromQString(referenceToAdd.sourceNodeId()),
- Open62541Utils::nodeIdFromQString(referenceToAdd.referenceTypeId()),
- referenceToAdd.isForwardReference(), serverUri, target, nodeClass);
-
- QOpcUa::UaStatusCode statusCode = static_cast<QOpcUa::UaStatusCode>(res);
- if (res != UA_STATUSCODE_GOOD)
+ UA_AddReferencesRequest request;
+ UA_AddReferencesRequest_init(&request);
+ UaDeleter<UA_AddReferencesRequest> requestDeleter(&request, UA_AddReferencesRequest_deleteMembers);
+
+ request.referencesToAddSize = 1;
+ request.referencesToAdd = UA_AddReferencesItem_new();
+
+ request.referencesToAdd->isForward = referenceToAdd.isForwardReference();
+ QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(referenceToAdd.sourceNodeId(),
+ &request.referencesToAdd->sourceNodeId);
+ QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(referenceToAdd.targetNodeId(),
+ &request.referencesToAdd->targetNodeId);
+ QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(referenceToAdd.referenceTypeId(),
+ &request.referencesToAdd->referenceTypeId);
+ request.referencesToAdd->targetNodeClass = static_cast<UA_NodeClass>(referenceToAdd.targetNodeClass());
+ QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(referenceToAdd.targetServerUri(),
+ &request.referencesToAdd->targetServerUri);
+
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &request, &UA_TYPES[UA_TYPES_ADDREFERENCESREQUEST],
+ &asyncAddReferenceCallback,
+ &UA_TYPES[UA_TYPES_ADDREFERENCESRESPONSE],
+ this, &requestId, m_asyncRequestTimeout);
+
+ QOpcUa::UaStatusCode statusCode = static_cast<QOpcUa::UaStatusCode>(result);
+ if (result != UA_STATUSCODE_GOOD) {
qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add reference from" << referenceToAdd.sourceNodeId() << "to"
<< referenceToAdd.targetNodeId().nodeId() << ":" << statusCode;
+ emit addReferenceFinished(referenceToAdd.sourceNodeId(), referenceToAdd.referenceTypeId(),
+ referenceToAdd.targetNodeId(), referenceToAdd.isForwardReference(), statusCode);
+ return;
+ }
- emit addReferenceFinished(referenceToAdd.sourceNodeId(), referenceToAdd.referenceTypeId(),
- referenceToAdd.targetNodeId(),
- referenceToAdd.isForwardReference(), statusCode);
+ m_asyncAddReferenceContext[requestId] = { referenceToAdd.sourceNodeId(), referenceToAdd.referenceTypeId(),
+ referenceToAdd.targetNodeId(), referenceToAdd.isForwardReference() };
}
void Open62541AsyncBackend::deleteReference(const QOpcUaDeleteReferenceItem &referenceToDelete)
{
- UA_ExpandedNodeId target;
- UA_ExpandedNodeId_init(&target);
- UaDeleter<UA_ExpandedNodeId> targetDeleter(&target, UA_ExpandedNodeId_deleteMembers);
- QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(
- referenceToDelete.targetNodeId(), &target);
-
- UA_StatusCode res = UA_Client_deleteReference(m_uaclient,
- Open62541Utils::nodeIdFromQString(referenceToDelete.sourceNodeId()),
- Open62541Utils::nodeIdFromQString(referenceToDelete.referenceTypeId()),
- referenceToDelete.isForwardReference(),
- target, referenceToDelete.deleteBidirectional());
-
- QOpcUa::UaStatusCode statusCode = static_cast<QOpcUa::UaStatusCode>(res);
- if (res != UA_STATUSCODE_GOOD)
+ UA_DeleteReferencesRequest request;
+ UA_DeleteReferencesRequest_init(&request);
+ UaDeleter<UA_DeleteReferencesRequest> requestDeleter(&request, UA_DeleteReferencesRequest_deleteMembers);
+
+ request.referencesToDeleteSize = 1;
+ request.referencesToDelete = UA_DeleteReferencesItem_new();
+ request.referencesToDelete->isForward = referenceToDelete.isForwardReference();
+ QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(referenceToDelete.sourceNodeId(),
+ &request.referencesToDelete->sourceNodeId);
+ QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(referenceToDelete.targetNodeId(),
+ &request.referencesToDelete->targetNodeId);
+ QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(referenceToDelete.referenceTypeId(),
+ &request.referencesToDelete->referenceTypeId);
+ request.referencesToDelete->deleteBidirectional = referenceToDelete.deleteBidirectional();
+
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &request, &UA_TYPES[UA_TYPES_DELETEREFERENCESREQUEST],
+ &asyncDeleteReferenceCallback,
+ &UA_TYPES[UA_TYPES_DELETEREFERENCESRESPONSE],
+ this, &requestId, m_asyncRequestTimeout);
+
+ QOpcUa::UaStatusCode statusCode = static_cast<QOpcUa::UaStatusCode>(result);
+ if (result != UA_STATUSCODE_GOOD) {
qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to delete reference from" << referenceToDelete.sourceNodeId() << "to"
<< referenceToDelete.targetNodeId().nodeId() << ":" << statusCode;
- emit deleteReferenceFinished(referenceToDelete.sourceNodeId(), referenceToDelete.referenceTypeId(),
- referenceToDelete.targetNodeId(),
- referenceToDelete.isForwardReference(), statusCode);
+ emit deleteReferenceFinished(referenceToDelete.sourceNodeId(), referenceToDelete.referenceTypeId(),
+ referenceToDelete.targetNodeId(),
+ referenceToDelete.isForwardReference(), statusCode);
+ return;
+ }
+
+ m_asyncDeleteReferenceContext[requestId] = { referenceToDelete.sourceNodeId(), referenceToDelete.referenceTypeId(),
+ referenceToDelete.targetNodeId(), referenceToDelete.isForwardReference()};
}
static void convertBrowseResult(UA_BrowseResult *src, quint32 referencesSize, QList<QOpcUaReferenceDescription> &dst)
@@ -761,7 +744,7 @@ void Open62541AsyncBackend::browse(quint64 handle, UA_NodeId id, const QOpcUaBro
{
UA_BrowseRequest uaRequest;
UA_BrowseRequest_init(&uaRequest);
- UaDeleter<UA_BrowseRequest> requestDeleter(&uaRequest, UA_BrowseRequest_deleteMembers);
+ UaDeleter<UA_BrowseRequest> requestDeleter(&uaRequest, UA_BrowseRequest_clear);
uaRequest.nodesToBrowse = UA_BrowseDescription_new();
uaRequest.nodesToBrowseSize = 1;
@@ -773,39 +756,16 @@ void Open62541AsyncBackend::browse(quint64 handle, UA_NodeId id, const QOpcUaBro
uaRequest.nodesToBrowse->referenceTypeId = Open62541Utils::nodeIdFromQString(request.referenceTypeId());
uaRequest.requestedMaxReferencesPerNode = 0; // Let the server choose a maximum value
- UA_BrowseResponse *response = UA_BrowseResponse_new();
- UaDeleter<UA_BrowseResponse> responseDeleter(response, UA_BrowseResponse_delete);
- *response = UA_Client_Service_browse(m_uaclient, uaRequest);
+ quint32 requestId = 0;
+ UA_StatusCode result = __UA_Client_AsyncServiceEx(m_uaclient, &uaRequest, &UA_TYPES[UA_TYPES_BROWSEREQUEST], &asyncBrowseCallback,
+ &UA_TYPES[UA_TYPES_BROWSERESPONSE], this, &requestId, m_asyncRequestTimeout);
- QList<QOpcUaReferenceDescription> ret;
-
- QOpcUa::UaStatusCode statusCode = QOpcUa::UaStatusCode::Good;
-
- while (response->resultsSize && statusCode == QOpcUa::UaStatusCode::Good) {
- UA_BrowseResponse *res = static_cast<UA_BrowseResponse *>(response);
-
- if (res->responseHeader.serviceResult != UA_STATUSCODE_GOOD || res->results->statusCode != UA_STATUSCODE_GOOD) {
- statusCode = static_cast<QOpcUa::UaStatusCode>(res->results->statusCode);
- break;
- }
-
- convertBrowseResult(res->results, res->results->referencesSize, ret);
-
- if (res->results->continuationPoint.length) {
- UA_BrowseNextRequest nextReq;
- UA_BrowseNextRequest_init(&nextReq);
- UaDeleter<UA_BrowseNextRequest> nextReqDeleter(&nextReq, UA_BrowseNextRequest_deleteMembers);
- nextReq.continuationPoints = UA_ByteString_new();
- UA_ByteString_copy(&(res->results->continuationPoint), nextReq.continuationPoints);
- nextReq.continuationPointsSize = 1;
- UA_BrowseResponse_deleteMembers(res); // Deallocate the pointer members before overwriting the response
- *reinterpret_cast<UA_BrowseNextResponse *>(response) = UA_Client_Service_browseNext(m_uaclient, nextReq);
- } else {
- break;
- }
+ if (result != UA_STATUSCODE_GOOD) {
+ emit browseFinished(handle, QList<QOpcUaReferenceDescription>(), static_cast<QOpcUa::UaStatusCode>(result));
+ return;
}
- emit browseFinished(handle, ret, statusCode);
+ m_asyncBrowseContext[requestId] = { handle, false, QList<QOpcUaReferenceDescription>() };
}
void Open62541AsyncBackend::clientStateCallback(UA_Client *client,
@@ -1012,9 +972,8 @@ void Open62541AsyncBackend::connectToEndpoint(const QOpcUaEndpointDescription &e
return;
}
- reevaluateClientIterateTimer();
-
m_useStateCallback = true;
+ m_clientIterateTimer.start(m_clientIterateInterval);
emit stateAndOrErrorChanged(QOpcUaClient::Connected, QOpcUaClient::NoError);
}
@@ -1107,25 +1066,13 @@ void Open62541AsyncBackend::iterateClient()
return;
// If BADSERVERNOTCONNECTED is returned, the subscriptions are gone and local information can be deleted.
- if (UA_Client_run_iterate(m_uaclient, 10) == UA_STATUSCODE_BADSERVERNOTCONNECTED) {
+ if (UA_Client_run_iterate(m_uaclient,
+ std::max<quint32>(1, m_clientIterateInterval / 2)) == UA_STATUSCODE_BADSERVERNOTCONNECTED) {
qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to send publish request";
cleanupSubscriptions();
}
}
-void Open62541AsyncBackend::reevaluateClientIterateTimer()
-{
- if (m_subscriptions.count() == 0)
- m_clientIterateTimer.start(m_maximumIterateInterval);
- else {// Derive an interval from the the lowest subscription and a lower limit.
- double minimum = (std::numeric_limits<double>::max)();
- for (const auto &subscription : qAsConst(m_subscriptions))
- minimum = subscription->interval() < minimum ? subscription->interval() : minimum;
- // Set an interval between configured minimum and maximum, depending on the fastest subscription
- m_clientIterateTimer.start((std::min)(m_maximumIterateInterval, (std::max)(m_minimumIterateInterval, minimum)));
- }
-}
-
void Open62541AsyncBackend::handleSubscriptionTimeout(QOpen62541Subscription *sub, QList<QPair<quint64, QOpcUa::NodeAttribute>> items)
{
for (auto it : qAsConst(items)) {
@@ -1136,7 +1083,6 @@ void Open62541AsyncBackend::handleSubscriptionTimeout(QOpen62541Subscription *su
}
m_subscriptions.remove(sub->subscriptionId());
delete sub;
- reevaluateClientIterateTimer();
}
QOpen62541Subscription *Open62541AsyncBackend::getSubscriptionForItem(quint64 handle, QOpcUa::NodeAttribute attr)
@@ -1177,8 +1123,310 @@ void Open62541AsyncBackend::cleanupSubscriptions()
m_subscriptions.clear();
m_attributeMapping.clear();
m_minPublishingInterval = 0;
+}
+
+void Open62541AsyncBackend::asyncMethodCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ const auto context = backend->m_asyncCallContext.take(requestId);
+
+ QVariant result;
+
+ const auto cr = static_cast<UA_CallResponse *>(response);
+
+ if (cr->resultsSize && cr->results->outputArgumentsSize > 1 && cr->results->statusCode == UA_STATUSCODE_GOOD) {
+ QVariantList temp;
+ for (size_t i = 0; i < cr->results->outputArgumentsSize; ++i)
+ temp.append(QOpen62541ValueConverter::toQVariant(cr->results->outputArguments[i]));
- reevaluateClientIterateTimer();
+ result = temp;
+ } else if (cr->resultsSize && cr->results->outputArgumentsSize == 1 && cr->results->statusCode == UA_STATUSCODE_GOOD) {
+ result = QOpen62541ValueConverter::toQVariant(cr->results->outputArguments[0]);
+ }
+
+ emit backend->methodCallFinished(context.handle, context.methodNodeId, result,
+ static_cast<QOpcUa::UaStatusCode>(cr->resultsSize ?
+ cr->results->statusCode :
+ cr->responseHeader.serviceResult));
+}
+
+void Open62541AsyncBackend::asyncTranslateBrowsePathCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ const auto context = backend->m_asyncTranslateContext.take(requestId);
+
+ const auto res = static_cast<UA_TranslateBrowsePathsToNodeIdsResponse *>(response);
+
+ if (res->responseHeader.serviceResult != UA_STATUSCODE_GOOD || res->resultsSize != 1) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Translate browse path failed:" << UA_StatusCode_name(res->responseHeader.serviceResult);
+ emit backend->resolveBrowsePathFinished(context.handle, QList<QOpcUaBrowsePathTarget>(), context.path,
+ static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult));
+ return;
+ }
+
+ QList<QOpcUaBrowsePathTarget> ret;
+ for (size_t i = 0; i < res->results->targetsSize ; ++i) {
+ QOpcUaBrowsePathTarget temp;
+ temp.setRemainingPathIndex(res->results->targets[i].remainingPathIndex);
+ temp.targetIdRef().setNamespaceUri(QString::fromUtf8(reinterpret_cast<char *>(res->results->targets[i].targetId.namespaceUri.data)));
+ temp.targetIdRef().setServerIndex(res->results->targets[i].targetId.serverIndex);
+ temp.targetIdRef().setNodeId(Open62541Utils::nodeIdToQString(res->results->targets[i].targetId.nodeId));
+ ret.append(temp);
+ }
+
+ emit backend->resolveBrowsePathFinished(context.handle, ret, context.path, static_cast<QOpcUa::UaStatusCode>(res->results->statusCode));
+}
+
+void Open62541AsyncBackend::asyncAddNodeCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ const auto context = backend->m_asyncAddNodeContext.take(requestId);
+
+ const auto res = static_cast<UA_AddNodesResponse *>(response);
+
+ QOpcUa::UaStatusCode status = QOpcUa::UaStatusCode::Good;
+ QString resultId;
+ if (res->responseHeader.serviceResult == UA_STATUSCODE_GOOD) {
+ if (res->results->statusCode == UA_STATUSCODE_GOOD)
+ resultId = Open62541Utils::nodeIdToQString(res->results->addedNodeId);
+ else {
+ status = static_cast<QOpcUa::UaStatusCode>(res->results->statusCode);
+ qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:" << status;
+ }
+ } else {
+ status = static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult);
+ qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:" << status;
+ }
+
+ emit backend->addNodeFinished(context.requestedNodeId, resultId, status);
+}
+
+void Open62541AsyncBackend::asyncDeleteNodeCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ const auto context = backend->m_asyncDeleteNodeContext.take(requestId);
+
+ const auto res = static_cast<UA_DeleteNodesResponse *>(response);
+
+ emit backend->deleteNodeFinished(context.nodeId,
+ static_cast<QOpcUa::UaStatusCode>(res->resultsSize ?
+ res->results[0] : res->responseHeader.serviceResult));
+}
+
+void Open62541AsyncBackend::asyncAddReferenceCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ const auto context = backend->m_asyncAddReferenceContext.take(requestId);
+
+ const auto res = static_cast<UA_AddReferencesResponse *>(response);
+
+ emit backend->addReferenceFinished(context.sourceNodeId, context.referenceTypeId, context.targetNodeId,
+ context.isForwardReference,
+ static_cast<QOpcUa::UaStatusCode>(res->resultsSize ?
+ res->results[0] : res->responseHeader.serviceResult));
+}
+
+void Open62541AsyncBackend::asyncDeleteReferenceCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ const auto context = backend->m_asyncDeleteReferenceContext.take(requestId);
+
+ const auto res = static_cast<UA_DeleteReferencesResponse *>(response);
+
+ emit backend->deleteReferenceFinished(context.sourceNodeId, context.referenceTypeId,
+ context.targetNodeId, context.isForwardReference,
+ static_cast<QOpcUa::UaStatusCode>(res->resultsSize ?
+ res->results[0] :
+ res->responseHeader.serviceResult));
+}
+
+void Open62541AsyncBackend::asyncReadCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ auto context = backend->m_asyncReadContext.take(requestId);
+
+ const auto res = static_cast<UA_ReadResponse *>(response);
+
+ for (int i = 0; i < context.results.size(); ++i) {
+ // Use the service result as status code if there is no specific result for the current value.
+ // This ensures a result for each attribute when UA_Client_Service_read is called for a disconnected client.
+ if (static_cast<size_t>(i) >= res->resultsSize) {
+ context.results[i].setStatusCode(static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult));
+ continue;
+ }
+ if (res->results[i].hasStatus)
+ context.results[i].setStatusCode(static_cast<QOpcUa::UaStatusCode>(res->results[i].status));
+ else
+ context.results[i].setStatusCode(QOpcUa::UaStatusCode::Good);
+ if (res->results[i].hasValue && res->results[i].value.data)
+ context.results[i].setValue(QOpen62541ValueConverter::toQVariant(res->results[i].value));
+ if (res->results[i].hasServerTimestamp)
+ context.results[i].setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(&res->results[i].sourceTimestamp));
+ if (res->results[i].hasSourceTimestamp)
+ context.results[i].setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(&res->results[i].serverTimestamp));
+ }
+
+ emit backend->attributesRead(context.handle, context.results,
+ static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult));
+}
+
+void Open62541AsyncBackend::asyncWriteAttributesCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ auto context = backend->m_asyncWriteAttributesContext.take(requestId);
+
+ const auto res = static_cast<UA_WriteResponse *>(response);
+
+ size_t index = 0;
+ for (auto it = context.toWrite.begin(); it != context.toWrite.end(); ++it, ++index) {
+ QOpcUa::UaStatusCode status = index < res->resultsSize ?
+ static_cast<QOpcUa::UaStatusCode>(res->results[index]) :
+ static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult);
+ emit backend->attributeWritten(context.handle, it.key(), it.value(), status);
+ }
+}
+
+void Open62541AsyncBackend::asyncBrowseCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ auto context = backend->m_asyncBrowseContext.take(requestId);
+
+ UA_StatusCode statusCode = UA_STATUSCODE_GOOD;
+ size_t referencesSize = 0;
+ UA_BrowseResult *references = nullptr;
+ UA_ByteString *continuationPoint = nullptr;
+
+ if (context.isBrowseNext) {
+ const auto res = static_cast<UA_BrowseNextResponse *>(response);
+ referencesSize = res->resultsSize ? res->results->referencesSize : 0;
+ references = res->results;
+ statusCode = referencesSize ? references->statusCode : res->responseHeader.serviceResult;
+ continuationPoint = res->resultsSize ? &res->results->continuationPoint : nullptr;
+ } else {
+ const auto res = static_cast<UA_BrowseResponse *>(response);
+ referencesSize = res->resultsSize ? res->results->referencesSize : 0;
+ references = res->results;
+ statusCode = referencesSize ? references->statusCode : res->responseHeader.serviceResult;
+ continuationPoint = res->resultsSize ? &res->results->continuationPoint : nullptr;
+ }
+
+ convertBrowseResult(references, referencesSize, context.results);
+
+ if (statusCode == UA_STATUSCODE_GOOD && continuationPoint->length) {
+ UA_BrowseNextRequest request;
+ UA_BrowseNextRequest_init(&request);
+ UaDeleter<UA_BrowseNextRequest> requestDeleter(&request, UA_BrowseNextRequest_clear);
+
+ request.continuationPointsSize = 1;
+ request.continuationPoints = UA_ByteString_new();
+ UA_ByteString_copy(continuationPoint, request.continuationPoints);
+
+ quint32 requestId = 0;
+ statusCode =__UA_Client_AsyncServiceEx(client, &request, &UA_TYPES[UA_TYPES_BROWSENEXTREQUEST], &asyncBrowseCallback,
+ &UA_TYPES[UA_TYPES_BROWSENEXTRESPONSE], backend, &requestId, backend->m_asyncRequestTimeout);
+
+ if (statusCode == UA_STATUSCODE_GOOD) {
+ context.isBrowseNext = true;
+ backend->m_asyncBrowseContext[requestId] = context;
+ return;
+ }
+ } else if (statusCode != UA_STATUSCODE_GOOD) {
+ emit backend->browseFinished(context.handle, QList<QOpcUaReferenceDescription>(),
+ static_cast<QOpcUa::UaStatusCode>(statusCode));
+ return;
+ }
+
+ emit backend->browseFinished(context.handle, context.results, static_cast<QOpcUa::UaStatusCode>(statusCode));
+}
+
+void Open62541AsyncBackend::asyncBatchReadCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ auto context = backend->m_asyncBatchReadContext.take(requestId);
+
+ const auto res = static_cast<UA_ReadResponse *>(response);
+
+ QOpcUa::UaStatusCode serviceResult = static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult);
+
+ if (serviceResult != QOpcUa::UaStatusCode::Good) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch read failed:" << serviceResult;
+ emit backend->readNodeAttributesFinished(QList<QOpcUaReadResult>(), serviceResult);
+ } else {
+ QList<QOpcUaReadResult> ret;
+
+ for (int i = 0; i < context.nodesToRead.size(); ++i) {
+ QOpcUaReadResult item;
+ item.setAttribute(context.nodesToRead.at(i).attribute());
+ item.setNodeId(context.nodesToRead.at(i).nodeId());
+ item.setIndexRange(context.nodesToRead.at(i).indexRange());
+ if (static_cast<size_t>(i) < res->resultsSize) {
+ if (res->results[i].hasServerTimestamp)
+ item.setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime>(&res->results[i].serverTimestamp));
+ if (res->results[i].hasSourceTimestamp)
+ item.setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime>(&res->results[i].sourceTimestamp));
+ if (res->results[i].hasValue)
+ item.setValue(QOpen62541ValueConverter::toQVariant(res->results[i].value));
+ if (res->results[i].hasStatus)
+ item.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res->results[i].status));
+ else
+ item.setStatusCode(serviceResult);
+ } else {
+ item.setStatusCode(serviceResult);
+ }
+ ret.push_back(item);
+ }
+ emit backend->readNodeAttributesFinished(ret, serviceResult);
+ }
+}
+
+void Open62541AsyncBackend::asyncBatchWriteCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response)
+{
+ Q_UNUSED(client)
+
+ Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata);
+ const auto context = backend->m_asyncBatchWriteContext.take(requestId);
+
+ const auto res = static_cast<UA_WriteResponse *>(response);
+
+ QOpcUa::UaStatusCode serviceResult = QOpcUa::UaStatusCode(res->responseHeader.serviceResult);
+
+ if (serviceResult != QOpcUa::UaStatusCode::Good) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch write failed:" << serviceResult;
+ emit backend->writeNodeAttributesFinished(QList<QOpcUaWriteResult>(), serviceResult);
+ } else {
+ QList<QOpcUaWriteResult> ret;
+
+ for (int i = 0; i < context.nodesToWrite.size(); ++i) {
+ QOpcUaWriteResult item;
+ item.setAttribute(context.nodesToWrite.at(i).attribute());
+ item.setNodeId(context.nodesToWrite.at(i).nodeId());
+ item.setIndexRange(context.nodesToWrite.at(i).indexRange());
+ if (static_cast<size_t>(i) < res->resultsSize)
+ item.setStatusCode(QOpcUa::UaStatusCode(res->results[i]));
+ else
+ item.setStatusCode(serviceResult);
+ ret.push_back(item);
+ }
+ emit backend->writeNodeAttributesFinished(ret, serviceResult);
+ }
}
bool Open62541AsyncBackend::loadFileToByteString(const QString &location, UA_ByteString *target) const
diff --git a/src/plugins/opcua/open62541/qopen62541backend.h b/src/plugins/opcua/open62541/qopen62541backend.h
index 89a9e0f..791fb44 100644
--- a/src/plugins/opcua/open62541/qopen62541backend.h
+++ b/src/plugins/opcua/open62541/qopen62541backend.h
@@ -1,4 +1,4 @@
-/****************************************************************************
+/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
@@ -82,16 +82,28 @@ public Q_SLOTS:
QOpen62541Subscription *getSubscription(const QOpcUaMonitoringParameters &settings);
bool removeSubscription(UA_UInt32 subscriptionId);
void iterateClient();
- void reevaluateClientIterateTimer();
void handleSubscriptionTimeout(QOpen62541Subscription *sub, QList<QPair<quint64, QOpcUa::NodeAttribute>> items);
void cleanupSubscriptions();
+ // Callbacks
+ static void asyncMethodCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncTranslateBrowsePathCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncAddNodeCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncDeleteNodeCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncAddReferenceCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncDeleteReferenceCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncReadCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncWriteAttributesCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncBrowseCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncBatchReadCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+ static void asyncBatchWriteCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response);
+
public:
UA_Client *m_uaclient;
QOpen62541Client *m_clientImpl;
bool m_useStateCallback;
- double m_minimumIterateInterval;
- const double m_maximumIterateInterval;
+ quint32 m_clientIterateInterval;
+ quint32 m_asyncRequestTimeout;
private:
static void clientStateCallback(UA_Client *client,
@@ -123,6 +135,75 @@ private:
double m_minPublishingInterval;
const UA_Logger m_open62541Logger {open62541LogHandler, nullptr, nullptr};
+
+ // Async contexts
+
+ struct AsyncCallContext {
+ quint64 handle;
+ QString methodNodeId;
+ };
+ QMap<quint32, AsyncCallContext> m_asyncCallContext;
+
+ struct AsyncTranslateContext {
+ quint64 handle;
+ QList<QOpcUaRelativePathElement> path;
+ };
+ QMap<quint32, AsyncTranslateContext> m_asyncTranslateContext;
+
+ struct AsyncAddNodeContext {
+ QOpcUaExpandedNodeId requestedNodeId;
+ };
+ QMap<quint32, AsyncAddNodeContext> m_asyncAddNodeContext;
+
+ struct AsyncDeleteNodeContext {
+ QString nodeId;
+ };
+ QMap<quint32, AsyncDeleteNodeContext> m_asyncDeleteNodeContext;
+
+ struct AsyncAddReferenceContext {
+ QString sourceNodeId;
+ QString referenceTypeId;
+ QOpcUaExpandedNodeId targetNodeId;
+ bool isForwardReference;
+ };
+ QMap<quint32, AsyncAddReferenceContext> m_asyncAddReferenceContext;
+
+ struct AsyncDeleteReferenceContext {
+ QString sourceNodeId;
+ QString referenceTypeId;
+ QOpcUaExpandedNodeId targetNodeId;
+ bool isForwardReference;
+ };
+ QMap<quint32, AsyncDeleteReferenceContext> m_asyncDeleteReferenceContext;
+
+ struct AsyncReadContext {
+ quint64 handle;
+ QList<QOpcUaReadResult> results;
+ };
+ QMap<quint32, AsyncReadContext> m_asyncReadContext;
+
+ struct AsyncWriteAttributesContext {
+ quint64 handle;
+ QOpcUaNode::AttributeMap toWrite;
+ };
+ QMap<quint32, AsyncWriteAttributesContext> m_asyncWriteAttributesContext;
+
+ struct AsyncBrowseContext {
+ quint64 handle;
+ bool isBrowseNext;
+ QList<QOpcUaReferenceDescription> results;
+ };
+ QMap<quint32, AsyncBrowseContext> m_asyncBrowseContext;
+
+ struct AsyncBatchReadContext {
+ QList<QOpcUaReadItem> nodesToRead;
+ };
+ QMap<quint32, AsyncBatchReadContext> m_asyncBatchReadContext;
+
+ struct AsyncBatchWriteContext {
+ QList<QOpcUaWriteItem> nodesToWrite;
+ };
+ QMap<quint32, AsyncBatchWriteContext> m_asyncBatchWriteContext;
};
QT_END_NAMESPACE
diff --git a/src/plugins/opcua/open62541/qopen62541client.cpp b/src/plugins/opcua/open62541/qopen62541client.cpp
index de1b6e8..9c620b8 100644
--- a/src/plugins/opcua/open62541/qopen62541client.cpp
+++ b/src/plugins/opcua/open62541/qopen62541client.cpp
@@ -56,11 +56,17 @@ QOpen62541Client::QOpen62541Client(const QVariantMap &backendProperties)
, m_backend(new Open62541AsyncBackend(this))
{
bool ok = false;
- const double minIterateInterval = backendProperties.value(QStringLiteral("minimumClientIterateIntervalMs"), 50.0)
- .toDouble(&ok);
+ const quint32 clientIterateInterval = backendProperties.value(QStringLiteral("clientIterateIntervalMs"), 50)
+ .toUInt(&ok);
if (ok)
- m_backend->m_minimumIterateInterval = minIterateInterval;
+ m_backend->m_clientIterateInterval = clientIterateInterval;
+
+ const quint32 asyncRequestTimeout = backendProperties.value(QStringLiteral("asyncRequestTimeoutMs"), 15000)
+ .toUInt(&ok);
+
+ if (ok)
+ m_backend->m_asyncRequestTimeout = asyncRequestTimeout;
m_thread = new QThread();
m_thread->setObjectName("QOpen62541Client");