1 | // Copyright (C) 2017 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qopen62541backend.h" |
5 | #include "qopen62541node.h" |
6 | #include "qopen62541utils.h" |
7 | #include "qopen62541valueconverter.h" |
8 | #include <private/qopcuaclient_p.h> |
9 | |
10 | #include "qopcuaauthenticationinformation.h" |
11 | #include <qopcuaerrorstate.h> |
12 | |
13 | #include <QtCore/QDir> |
14 | #include <QtCore/QFile> |
15 | #include <QtCore/qloggingcategory.h> |
16 | #include <QtCore/qstringlist.h> |
17 | #include <QtCore/qurl.h> |
18 | #include <QtCore/private/qnumeric_p.h> // for qt_saturate |
19 | |
20 | #include <algorithm> |
21 | #include <limits> |
22 | |
23 | QT_BEGIN_NAMESPACE |
24 | |
25 | Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_OPEN62541) |
26 | |
27 | Open62541AsyncBackend::Open62541AsyncBackend(QOpen62541Client *parent) |
28 | : QOpcUaBackend() |
29 | , m_uaclient(nullptr) |
30 | , m_clientImpl(parent) |
31 | , m_useStateCallback(false) |
32 | , m_clientIterateInterval(50) |
33 | , m_asyncRequestTimeout(15000) |
34 | , m_clientIterateTimer(this) |
35 | , m_disconnectAfterStateChangeTimer(this) |
36 | , m_minPublishingInterval(0) |
37 | { |
38 | QObject::connect(sender: &m_clientIterateTimer, signal: &QTimer::timeout, |
39 | context: this, slot: &Open62541AsyncBackend::iterateClient); |
40 | |
41 | m_disconnectAfterStateChangeTimer.setSingleShot(true); |
42 | m_disconnectAfterStateChangeTimer.setInterval(0); |
43 | |
44 | QObject::connect(sender: &m_disconnectAfterStateChangeTimer, signal: &QTimer::timeout, |
45 | context: this, slot: [this]() { |
46 | disconnectInternal(error: QOpcUaClient::ConnectionError); |
47 | }); |
48 | } |
49 | |
50 | Open62541AsyncBackend::~Open62541AsyncBackend() |
51 | { |
52 | cleanupSubscriptions(); |
53 | if (m_uaclient) |
54 | UA_Client_delete(client: m_uaclient); |
55 | } |
56 | |
57 | void Open62541AsyncBackend::readAttributes(quint64 handle, UA_NodeId id, QOpcUa::NodeAttributes attr, QString indexRange) |
58 | { |
59 | UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_clear); |
60 | |
61 | UA_ReadRequest req; |
62 | UA_ReadRequest_init(p: &req); |
63 | UaDeleter<UA_ReadRequest> requestDeleter(&req, UA_ReadRequest_clear); |
64 | req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; |
65 | |
66 | qt_forEachAttribute(attributes: attr, f: [&req](QOpcUa::NodeAttribute attr) { |
67 | Q_UNUSED(attr); |
68 | ++req.nodesToReadSize; |
69 | }); |
70 | |
71 | if (req.nodesToReadSize) |
72 | req.nodesToRead = static_cast<UA_ReadValueId *>(UA_Array_new(size: req.nodesToReadSize, type: &UA_TYPES[UA_TYPES_READVALUEID])); |
73 | |
74 | QList<QOpcUaReadResult> resultMetadata; |
75 | size_t index = 0; |
76 | qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ |
77 | auto ¤t = req.nodesToRead[index++]; |
78 | |
79 | current.attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: attribute); |
80 | UA_NodeId_copy(src: &id, dst: ¤t.nodeId); |
81 | if (indexRange.size()) |
82 | QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: indexRange, ptr: ¤t.indexRange); |
83 | |
84 | QOpcUaReadResult temp; |
85 | temp.setAttribute(attribute); |
86 | resultMetadata.push_back(t: temp); |
87 | }); |
88 | |
89 | quint32 requestId = 0; |
90 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_READREQUEST], |
91 | callback: &asyncReadCallback, responseType: &UA_TYPES[UA_TYPES_READRESPONSE], userdata: this, |
92 | requestId: &requestId, timeout: m_asyncRequestTimeout); |
93 | |
94 | if (result != UA_STATUSCODE_GOOD) { |
95 | const auto statusCode = static_cast<QOpcUa::UaStatusCode>(result); |
96 | for (auto &entry : resultMetadata) { |
97 | entry.setStatusCode(statusCode); |
98 | } |
99 | emit attributesRead(handle, attributes: resultMetadata, serviceResult: statusCode); |
100 | return; |
101 | } |
102 | |
103 | m_asyncReadContext[requestId] = { .handle: handle, .results: resultMetadata }; |
104 | } |
105 | |
106 | void Open62541AsyncBackend::writeAttribute(quint64 handle, UA_NodeId id, QOpcUa::NodeAttribute attrId, QVariant value, QOpcUa::Types type, QString indexRange) |
107 | { |
108 | if (type == QOpcUa::Types::Undefined && attrId != QOpcUa::NodeAttribute::Value) |
109 | type = attributeIdToTypeId(attr: attrId); |
110 | |
111 | UA_WriteRequest req; |
112 | UA_WriteRequest_init(p: &req); |
113 | UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_clear); |
114 | req.nodesToWriteSize = 1; |
115 | req.nodesToWrite = UA_WriteValue_new(); |
116 | |
117 | UA_WriteValue_init(p: req.nodesToWrite); |
118 | req.nodesToWrite->attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: attrId); |
119 | req.nodesToWrite->nodeId = id; |
120 | req.nodesToWrite->value.value = QOpen62541ValueConverter::toOpen62541Variant(value, type); |
121 | req.nodesToWrite->value.hasValue = true; |
122 | if (indexRange.size()) |
123 | QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: indexRange, ptr: &req.nodesToWrite->indexRange); |
124 | |
125 | quint32 requestId = 0; |
126 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_WRITEREQUEST], |
127 | callback: &asyncWriteAttributesCallback, responseType: &UA_TYPES[UA_TYPES_WRITERESPONSE], userdata: this, |
128 | requestId: &requestId, timeout: m_asyncRequestTimeout); |
129 | |
130 | if (result != UA_STATUSCODE_GOOD) { |
131 | emit attributeWritten(handle, attribute: attrId, value, statusCode: static_cast<QOpcUa::UaStatusCode>(result)); |
132 | return; |
133 | } |
134 | |
135 | m_asyncWriteAttributesContext[requestId] = { .handle: handle, .toWrite: {{attrId, value}} }; |
136 | } |
137 | |
138 | void Open62541AsyncBackend::writeAttributes(quint64 handle, UA_NodeId id, QOpcUaNode::AttributeMap toWrite, QOpcUa::Types valueAttributeType) |
139 | { |
140 | UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_clear); |
141 | |
142 | if (toWrite.size() == 0) { |
143 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "No values to be written" ; |
144 | emit attributeWritten(handle, attribute: QOpcUa::NodeAttribute::None, value: QVariant(), statusCode: QOpcUa::UaStatusCode::BadNothingToDo); |
145 | return; |
146 | } |
147 | |
148 | UA_WriteRequest req; |
149 | UA_WriteRequest_init(p: &req); |
150 | UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_clear); |
151 | req.nodesToWriteSize = toWrite.size(); |
152 | req.nodesToWrite = static_cast<UA_WriteValue *>(UA_Array_new(size: req.nodesToWriteSize, type: &UA_TYPES[UA_TYPES_WRITEVALUE])); |
153 | size_t index = 0; |
154 | for (auto it = toWrite.begin(); it != toWrite.end(); ++it, ++index) { |
155 | UA_WriteValue_init(p: &(req.nodesToWrite[index])); |
156 | req.nodesToWrite[index].attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: it.key()); |
157 | UA_NodeId_copy(src: &id, dst: &(req.nodesToWrite[index].nodeId)); |
158 | QOpcUa::Types type = it.key() == QOpcUa::NodeAttribute::Value ? valueAttributeType : attributeIdToTypeId(attr: it.key()); |
159 | req.nodesToWrite[index].value.value = QOpen62541ValueConverter::toOpen62541Variant(it.value(), type); |
160 | } |
161 | |
162 | quint32 requestId = 0; |
163 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_WRITEREQUEST], |
164 | callback: &asyncWriteAttributesCallback, responseType: &UA_TYPES[UA_TYPES_WRITERESPONSE], userdata: this, |
165 | requestId: &requestId, timeout: m_asyncRequestTimeout); |
166 | |
167 | if (result != UA_STATUSCODE_GOOD) { |
168 | index = 0; |
169 | for (auto it = toWrite.begin(); it != toWrite.end(); ++it) { |
170 | emit attributeWritten(handle, attribute: it.key(), value: it.value(), statusCode: static_cast<QOpcUa::UaStatusCode>(result)); |
171 | } |
172 | return; |
173 | } |
174 | |
175 | m_asyncWriteAttributesContext[requestId] = { .handle: handle, .toWrite: toWrite }; |
176 | } |
177 | |
178 | void Open62541AsyncBackend::enableMonitoring(quint64 handle, UA_NodeId id, QOpcUa::NodeAttributes attr, const QOpcUaMonitoringParameters &settings) |
179 | { |
180 | UaDeleter<UA_NodeId> nodeIdDeleter(&id, UA_NodeId_clear); |
181 | |
182 | QOpen62541Subscription *usedSubscription = nullptr; |
183 | |
184 | // Create a new subscription if necessary |
185 | if (settings.subscriptionId()) { |
186 | auto sub = m_subscriptions.find(key: settings.subscriptionId()); |
187 | if (sub == m_subscriptions.end()) { |
188 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "There is no subscription with id" << settings.subscriptionId(); |
189 | |
190 | qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ |
191 | QOpcUaMonitoringParameters s; |
192 | s.setStatusCode(QOpcUa::UaStatusCode::BadSubscriptionIdInvalid); |
193 | emit monitoringEnableDisable(handle, attr: attribute, subscribe: true, status: s); |
194 | }); |
195 | return; |
196 | } |
197 | usedSubscription = sub.value(); // Ignore interval != subscription.interval |
198 | } else { |
199 | usedSubscription = getSubscription(settings); |
200 | } |
201 | |
202 | if (!usedSubscription) { |
203 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not create subscription with interval" << settings.publishingInterval(); |
204 | qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ |
205 | QOpcUaMonitoringParameters s; |
206 | s.setStatusCode(QOpcUa::UaStatusCode::BadSubscriptionIdInvalid); |
207 | emit monitoringEnableDisable(handle, attr: attribute, subscribe: true, status: s); |
208 | }); |
209 | return; |
210 | } |
211 | |
212 | qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ |
213 | if (getSubscriptionForItem(handle, attr: attribute)) { |
214 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Monitored item for" << attribute << "has already been created" ; |
215 | QOpcUaMonitoringParameters s; |
216 | s.setStatusCode(QOpcUa::UaStatusCode::BadEntryExists); |
217 | emit monitoringEnableDisable(handle, attr: attribute, subscribe: true, status: s); |
218 | } else { |
219 | bool success = usedSubscription->addAttributeMonitoredItem(handle, attr: attribute, id, settings); |
220 | if (success) |
221 | m_attributeMapping[handle][attribute] = usedSubscription; |
222 | } |
223 | }); |
224 | |
225 | if (usedSubscription->monitoredItemsCount() == 0) |
226 | removeSubscription(subscriptionId: usedSubscription->subscriptionId()); // No items were added |
227 | } |
228 | |
229 | void Open62541AsyncBackend::disableMonitoring(quint64 handle, QOpcUa::NodeAttributes attr) |
230 | { |
231 | qt_forEachAttribute(attributes: attr, f: [&](QOpcUa::NodeAttribute attribute){ |
232 | QOpen62541Subscription *sub = getSubscriptionForItem(handle, attr: attribute); |
233 | if (sub) { |
234 | sub->removeAttributeMonitoredItem(handle, attr: attribute); |
235 | m_attributeMapping[handle].remove(key: attribute); |
236 | if (sub->monitoredItemsCount() == 0) |
237 | removeSubscription(subscriptionId: sub->subscriptionId()); |
238 | } |
239 | }); |
240 | } |
241 | |
242 | void Open62541AsyncBackend::modifyMonitoring(quint64 handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value) |
243 | { |
244 | QOpen62541Subscription *subscription = getSubscriptionForItem(handle, attr); |
245 | if (!subscription) { |
246 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify" << item << ", the monitored item does not exist" ; |
247 | QOpcUaMonitoringParameters p; |
248 | p.setStatusCode(QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid); |
249 | emit monitoringStatusChanged(handle, attr, items: item, param: p); |
250 | return; |
251 | } |
252 | |
253 | subscription->modifyMonitoring(handle, attr, item, value); |
254 | } |
255 | |
256 | QOpen62541Subscription *Open62541AsyncBackend::getSubscription(const QOpcUaMonitoringParameters &settings) |
257 | { |
258 | if (settings.subscriptionType() == QOpcUaMonitoringParameters::SubscriptionType::Shared) { |
259 | // Requesting multiple subscriptions with publishing interval < minimum publishing interval breaks subscription sharing |
260 | double interval = revisePublishingInterval(requestedValue: settings.publishingInterval(), minimumValue: m_minPublishingInterval); |
261 | for (auto entry : std::as_const(t&: m_subscriptions)) { |
262 | if (qFuzzyCompare(p1: entry->interval(), p2: interval) && entry->shared() == QOpcUaMonitoringParameters::SubscriptionType::Shared) |
263 | return entry; |
264 | } |
265 | } |
266 | |
267 | QOpen62541Subscription *sub = new QOpen62541Subscription(this, settings); |
268 | UA_UInt32 id = sub->createOnServer(); |
269 | if (!id) { |
270 | delete sub; |
271 | return nullptr; |
272 | } |
273 | m_subscriptions[id] = sub; |
274 | if (sub->interval() > settings.publishingInterval()) // The publishing interval has been revised by the server. |
275 | m_minPublishingInterval = sub->interval(); |
276 | // This must be a queued connection to prevent the slot from being called while the client is inside UA_Client_run_iterate(). |
277 | QObject::connect(sender: sub, signal: &QOpen62541Subscription::timeout, context: this, slot: &Open62541AsyncBackend::handleSubscriptionTimeout, type: Qt::QueuedConnection); |
278 | return sub; |
279 | } |
280 | |
281 | bool Open62541AsyncBackend::removeSubscription(UA_UInt32 subscriptionId) |
282 | { |
283 | auto sub = m_subscriptions.find(key: subscriptionId); |
284 | if (sub != m_subscriptions.end()) { |
285 | sub.value()->removeOnServer(); |
286 | delete sub.value(); |
287 | m_subscriptions.remove(key: subscriptionId); |
288 | return true; |
289 | } |
290 | return false; |
291 | } |
292 | |
293 | void Open62541AsyncBackend::callMethod(quint64 handle, UA_NodeId objectId, UA_NodeId methodId, QList<QOpcUa::TypedVariant> args) |
294 | { |
295 | UA_Variant *inputArgs = nullptr; |
296 | |
297 | if (args.size()) { |
298 | inputArgs = static_cast<UA_Variant *>(UA_Array_new(size: args.size(), type: &UA_TYPES[UA_TYPES_VARIANT])); |
299 | for (qsizetype i = 0; i < args.size(); ++i) |
300 | inputArgs[i] = QOpen62541ValueConverter::toOpen62541Variant(args[i].first, args[i].second); |
301 | } |
302 | |
303 | quint32 requestId = 0; |
304 | |
305 | UA_CallRequest request; |
306 | UA_CallRequest_init(p: &request); |
307 | UaDeleter<UA_CallRequest> requestDeleter(&request, UA_CallRequest_clear); |
308 | |
309 | request.methodsToCallSize = 1; |
310 | request.methodsToCall = UA_CallMethodRequest_new(); |
311 | request.methodsToCall->objectId = objectId; |
312 | request.methodsToCall->methodId = methodId; |
313 | request.methodsToCall->inputArguments = inputArgs; |
314 | request.methodsToCall->inputArgumentsSize = args.size(); |
315 | |
316 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &request, requestType: &UA_TYPES[UA_TYPES_CALLREQUEST], |
317 | callback: &asyncMethodCallback, |
318 | responseType: &UA_TYPES[UA_TYPES_CALLRESPONSE], |
319 | userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
320 | if (result != UA_STATUSCODE_GOOD) |
321 | emit methodCallFinished(handle, methodNodeId: Open62541Utils::nodeIdToQString(id: methodId), result: QVariant(), |
322 | statusCode: static_cast<QOpcUa::UaStatusCode>(result)); |
323 | |
324 | m_asyncCallContext[requestId] = { .handle: handle, .methodNodeId: Open62541Utils::nodeIdToQString(id: methodId) }; |
325 | } |
326 | |
327 | void Open62541AsyncBackend::resolveBrowsePath(quint64 handle, UA_NodeId startNode, const QList<QOpcUaRelativePathElement> &path) |
328 | { |
329 | UA_TranslateBrowsePathsToNodeIdsRequest req; |
330 | UA_TranslateBrowsePathsToNodeIdsRequest_init(p: &req); |
331 | UaDeleter<UA_TranslateBrowsePathsToNodeIdsRequest> requestDeleter( |
332 | &req,UA_TranslateBrowsePathsToNodeIdsRequest_clear); |
333 | |
334 | req.browsePathsSize = 1; |
335 | req.browsePaths = UA_BrowsePath_new(); |
336 | UA_BrowsePath_init(p: req.browsePaths); |
337 | req.browsePaths->startingNode = startNode; |
338 | req.browsePaths->relativePath.elementsSize = path.size(); |
339 | req.browsePaths->relativePath.elements = static_cast<UA_RelativePathElement *>(UA_Array_new(size: path.size(), type: &UA_TYPES[UA_TYPES_RELATIVEPATHELEMENT])); |
340 | |
341 | for (qsizetype i = 0 ; i < path.size(); ++i) { |
342 | req.browsePaths->relativePath.elements[i].includeSubtypes = path[i].includeSubtypes(); |
343 | req.browsePaths->relativePath.elements[i].isInverse = path[i].isInverse(); |
344 | req.browsePaths->relativePath.elements[i].referenceTypeId = Open62541Utils::nodeIdFromQString(name: path[i].referenceTypeId()); |
345 | req.browsePaths->relativePath.elements[i].targetName = UA_QUALIFIEDNAME_ALLOC(nsIndex: path[i].targetName().namespaceIndex(), |
346 | chars: path[i].targetName().name().toUtf8().constData()); |
347 | } |
348 | |
349 | quint32 requestId = 0; |
350 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSREQUEST], |
351 | callback: &asyncTranslateBrowsePathCallback, |
352 | responseType: &UA_TYPES[UA_TYPES_TRANSLATEBROWSEPATHSTONODEIDSRESPONSE], |
353 | userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
354 | |
355 | if (result != UA_STATUSCODE_GOOD) { |
356 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Translate browse path failed:" << UA_StatusCode_name(code: result); |
357 | emit resolveBrowsePathFinished(handle, targets: QList<QOpcUaBrowsePathTarget>(), path, |
358 | statusCode: static_cast<QOpcUa::UaStatusCode>(result)); |
359 | return; |
360 | } |
361 | |
362 | m_asyncTranslateContext[requestId] = { .handle: handle, .path: path }; |
363 | } |
364 | |
365 | void Open62541AsyncBackend::open62541LogHandler (void *logContext, UA_LogLevel level, UA_LogCategory category, |
366 | const char *msg, va_list args) { |
367 | |
368 | Q_UNUSED(logContext) |
369 | |
370 | Q_STATIC_ASSERT(UA_LOGCATEGORY_NETWORK == 0); |
371 | Q_STATIC_ASSERT(UA_LOGCATEGORY_SECURECHANNEL == 1); |
372 | Q_STATIC_ASSERT(UA_LOGCATEGORY_SESSION == 2); |
373 | Q_STATIC_ASSERT(UA_LOGCATEGORY_SERVER == 3); |
374 | Q_STATIC_ASSERT(UA_LOGCATEGORY_CLIENT == 4); |
375 | Q_STATIC_ASSERT(UA_LOGCATEGORY_USERLAND == 5); |
376 | Q_STATIC_ASSERT(UA_LOGCATEGORY_SECURITYPOLICY == 6); |
377 | |
378 | Q_ASSERT(category <= UA_LOGCATEGORY_SECURITYPOLICY); |
379 | |
380 | const auto logMessage = QString::vasprintf(format: msg, ap: args); |
381 | |
382 | static const QLoggingCategory loggingCategories[] { |
383 | QLoggingCategory("qt.opcua.plugins.open62541.sdk.network" ), |
384 | QLoggingCategory("qt.opcua.plugins.open62541.sdk.securechannel" ), |
385 | QLoggingCategory("qt.opcua.plugins.open62541.sdk.session" ), |
386 | QLoggingCategory("qt.opcua.plugins.open62541.sdk.server" ), |
387 | QLoggingCategory("qt.opcua.plugins.open62541.sdk.client" ), |
388 | QLoggingCategory("qt.opcua.plugins.open62541.sdk.userland" ), |
389 | QLoggingCategory("qt.opcua.plugins.open62541.sdk.securitypolicy" ) |
390 | }; |
391 | |
392 | switch (level) { |
393 | case UA_LOGLEVEL_TRACE: |
394 | case UA_LOGLEVEL_DEBUG: |
395 | qCDebug(loggingCategories[category]) << logMessage; |
396 | break; |
397 | case UA_LOGLEVEL_INFO: |
398 | qCInfo(loggingCategories[category]) << logMessage; |
399 | break; |
400 | case UA_LOGLEVEL_WARNING: |
401 | qCWarning(loggingCategories[category]) << logMessage; |
402 | break; |
403 | case UA_LOGLEVEL_ERROR: |
404 | case UA_LOGLEVEL_FATAL: |
405 | qCCritical(loggingCategories[category]) << logMessage; |
406 | break; |
407 | default: |
408 | qCCritical(loggingCategories[category]) << "Unknown UA_LOGLEVEL" << logMessage; |
409 | break; |
410 | } |
411 | } |
412 | |
413 | void Open62541AsyncBackend::findServers(const QUrl &url, const QStringList &localeIds, const QStringList &serverUris) |
414 | { |
415 | UA_Client *tmpClient = UA_Client_new(); |
416 | auto conf = UA_Client_getConfig(client: tmpClient); |
417 | |
418 | conf->logger = m_open62541Logger; |
419 | |
420 | UA_ClientConfig_setDefault(config: UA_Client_getConfig(client: tmpClient)); |
421 | |
422 | UaDeleter<UA_Client> clientDeleter(tmpClient, UA_Client_delete); |
423 | |
424 | UA_String *uaServerUris = nullptr; |
425 | if (!serverUris.isEmpty()) { |
426 | uaServerUris = static_cast<UA_String *>(UA_Array_new(size: serverUris.size(), type: &UA_TYPES[UA_TYPES_STRING])); |
427 | for (qsizetype i = 0; i < serverUris.size(); ++i) |
428 | QOpen62541ValueConverter::scalarFromQt(var: serverUris.at(i), ptr: &uaServerUris[i]); |
429 | } |
430 | UaArrayDeleter<UA_TYPES_STRING> serverUrisDeleter(uaServerUris, serverUris.size()); |
431 | |
432 | UA_String *uaLocaleIds = nullptr; |
433 | if (!localeIds.isEmpty()) { |
434 | uaLocaleIds = static_cast<UA_String *>(UA_Array_new(size: localeIds.size(), type: &UA_TYPES[UA_TYPES_STRING])); |
435 | for (qsizetype i = 0; i < localeIds.size(); ++i) |
436 | QOpen62541ValueConverter::scalarFromQt(var: localeIds.at(i), ptr: &uaLocaleIds[i]); |
437 | } |
438 | UaArrayDeleter<UA_TYPES_STRING> localeIdsDeleter(uaLocaleIds, localeIds.size()); |
439 | |
440 | size_t = 0; |
441 | UA_ApplicationDescription *servers = nullptr; |
442 | |
443 | UA_StatusCode result = UA_Client_findServers(client: tmpClient, serverUrl: url.toString(options: QUrl::RemoveUserInfo).toUtf8().constData(), |
444 | serverUrisSize: serverUris.size(), serverUris: uaServerUris, localeIdsSize: localeIds.size(), localeIds: uaLocaleIds, |
445 | registeredServersSize: &serversSize, registeredServers: &servers); |
446 | |
447 | UaArrayDeleter<UA_TYPES_APPLICATIONDESCRIPTION> serversDeleter(servers, serversSize); |
448 | |
449 | QList<QOpcUaApplicationDescription> ret; |
450 | |
451 | for (size_t i = 0; i < serversSize; ++i) |
452 | ret.append(t: convertApplicationDescription(desc&: servers[i])); |
453 | |
454 | if (result != UA_STATUSCODE_GOOD) { |
455 | qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to get servers:" << static_cast<QOpcUa::UaStatusCode>(result); |
456 | } |
457 | |
458 | emit findServersFinished(servers: ret, statusCode: static_cast<QOpcUa::UaStatusCode>(result), requestUrl: url); |
459 | } |
460 | |
461 | void Open62541AsyncBackend::readNodeAttributes(const QList<QOpcUaReadItem> &nodesToRead) |
462 | { |
463 | if (nodesToRead.size() == 0) { |
464 | emit readNodeAttributesFinished(results: QList<QOpcUaReadResult>(), serviceResult: QOpcUa::UaStatusCode::BadNothingToDo); |
465 | return; |
466 | } |
467 | |
468 | UA_ReadRequest req; |
469 | UA_ReadRequest_init(p: &req); |
470 | UaDeleter<UA_ReadRequest> requestDeleter(&req, UA_ReadRequest_clear); |
471 | |
472 | req.nodesToReadSize = nodesToRead.size(); |
473 | req.nodesToRead = static_cast<UA_ReadValueId *>(UA_Array_new(size: nodesToRead.size(), type: &UA_TYPES[UA_TYPES_READVALUEID])); |
474 | req.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; |
475 | |
476 | for (qsizetype i = 0; i < nodesToRead.size(); ++i) { |
477 | UA_ReadValueId_init(p: &req.nodesToRead[i]); |
478 | req.nodesToRead[i].attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: nodesToRead.at(i).attribute()); |
479 | req.nodesToRead[i].nodeId = Open62541Utils::nodeIdFromQString(name: nodesToRead.at(i).nodeId()); |
480 | if (!nodesToRead[i].indexRange().isEmpty()) |
481 | QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: nodesToRead.at(i).indexRange(), |
482 | ptr: &req.nodesToRead[i].indexRange); |
483 | } |
484 | |
485 | quint32 requestId = 0; |
486 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_READREQUEST], callback: &asyncBatchReadCallback, |
487 | responseType: &UA_TYPES[UA_TYPES_READRESPONSE], userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
488 | |
489 | if (result != UA_STATUSCODE_GOOD) { |
490 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch read failed:" << result; |
491 | emit readNodeAttributesFinished(results: QList<QOpcUaReadResult>(), serviceResult: static_cast<QOpcUa::UaStatusCode>(result)); |
492 | return; |
493 | } |
494 | |
495 | m_asyncBatchReadContext[requestId] = { .nodesToRead: nodesToRead }; |
496 | } |
497 | |
498 | void Open62541AsyncBackend::writeNodeAttributes(const QList<QOpcUaWriteItem> &nodesToWrite) |
499 | { |
500 | if (nodesToWrite.isEmpty()) { |
501 | emit writeNodeAttributesFinished(results: QList<QOpcUaWriteResult>(), serviceResult: QOpcUa::UaStatusCode::BadNothingToDo); |
502 | return; |
503 | } |
504 | |
505 | UA_WriteRequest req; |
506 | UA_WriteRequest_init(p: &req); |
507 | UaDeleter<UA_WriteRequest> requestDeleter(&req, UA_WriteRequest_clear); |
508 | |
509 | req.nodesToWriteSize = nodesToWrite.size(); |
510 | req.nodesToWrite = static_cast<UA_WriteValue *>(UA_Array_new(size: nodesToWrite.size(), type: &UA_TYPES[UA_TYPES_WRITEVALUE])); |
511 | |
512 | for (qsizetype i = 0; i < nodesToWrite.size(); ++i) { |
513 | const auto ¤tItem = nodesToWrite.at(i); |
514 | auto ¤tUaItem = req.nodesToWrite[i]; |
515 | currentUaItem.attributeId = QOpen62541ValueConverter::toUaAttributeId(attr: currentItem.attribute()); |
516 | currentUaItem.nodeId = Open62541Utils::nodeIdFromQString(name: currentItem.nodeId()); |
517 | if (currentItem.hasStatusCode()) { |
518 | currentUaItem.value.status = currentItem.statusCode(); |
519 | currentUaItem.value.hasStatus = UA_TRUE; |
520 | } |
521 | if (!currentItem.indexRange().isEmpty()) |
522 | QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: currentItem.indexRange(), ptr: ¤tUaItem.indexRange); |
523 | if (!currentItem.value().isNull()) { |
524 | currentUaItem.value.hasValue = true; |
525 | currentUaItem.value.value = QOpen62541ValueConverter::toOpen62541Variant(currentItem.value(), currentItem.type()); |
526 | } |
527 | if (currentItem.sourceTimestamp().isValid()) { |
528 | QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: currentItem.sourceTimestamp(), |
529 | ptr: ¤tUaItem.value.sourceTimestamp); |
530 | currentUaItem.value.hasSourceTimestamp = UA_TRUE; |
531 | } |
532 | if (currentItem.serverTimestamp().isValid()) { |
533 | QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: currentItem.serverTimestamp(), |
534 | ptr: ¤tUaItem.value.serverTimestamp); |
535 | currentUaItem.value.hasServerTimestamp = UA_TRUE; |
536 | } |
537 | } |
538 | |
539 | quint32 requestId = 0; |
540 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_WRITEREQUEST], callback: &asyncBatchWriteCallback, |
541 | responseType: &UA_TYPES[UA_TYPES_WRITERESPONSE], userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
542 | |
543 | if (result != UA_STATUSCODE_GOOD) { |
544 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch read failed:" << result; |
545 | emit readNodeAttributesFinished(results: QList<QOpcUaReadResult>(), serviceResult: static_cast<QOpcUa::UaStatusCode>(result)); |
546 | return; |
547 | } |
548 | |
549 | m_asyncBatchWriteContext[requestId] = { .nodesToWrite: nodesToWrite }; |
550 | } |
551 | |
552 | void Open62541AsyncBackend::readHistoryRaw(QOpcUaHistoryReadRawRequest request, QList<QByteArray> continuationPoints, bool releaseContinuationPoints, quint64 handle) |
553 | { |
554 | if (!continuationPoints.empty() && continuationPoints.size() != request.nodesToRead().size()) { |
555 | emit historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode::BadInternalError, handle); |
556 | return; |
557 | } |
558 | |
559 | UA_HistoryReadRequest uarequest; |
560 | UA_HistoryReadRequest_init(p: &uarequest); |
561 | uarequest.nodesToReadSize = request.nodesToRead().size(); |
562 | uarequest.nodesToRead = static_cast<UA_HistoryReadValueId*>(UA_Array_new(size: uarequest.nodesToReadSize, type: &UA_TYPES[UA_TYPES_HISTORYREADVALUEID])); |
563 | for (size_t i = 0; i < uarequest.nodesToReadSize; ++i) { |
564 | uarequest.nodesToRead[i].nodeId = Open62541Utils::nodeIdFromQString(name: request.nodesToRead().at(i).nodeId()); |
565 | QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: request.nodesToRead().at(i).indexRange(), ptr: &uarequest.nodesToRead[i].indexRange); |
566 | uarequest.nodesToRead[i].dataEncoding = UA_QUALIFIEDNAME_ALLOC(nsIndex: 0, chars: "Default Binary" ); |
567 | if (!continuationPoints.isEmpty()) |
568 | QOpen62541ValueConverter::scalarFromQt<UA_ByteString, QByteArray>(var: continuationPoints.at(i), ptr: &uarequest.nodesToRead[i].continuationPoint); |
569 | } |
570 | uarequest.timestampsToReturn = UA_TIMESTAMPSTORETURN_BOTH; |
571 | |
572 | if (releaseContinuationPoints) |
573 | uarequest.releaseContinuationPoints = releaseContinuationPoints; |
574 | |
575 | uarequest.historyReadDetails.encoding = UA_EXTENSIONOBJECT_DECODED; |
576 | uarequest.historyReadDetails.content.decoded.type = &UA_TYPES[UA_TYPES_READRAWMODIFIEDDETAILS]; |
577 | UA_ReadRawModifiedDetails *details = UA_ReadRawModifiedDetails_new(); |
578 | uarequest.historyReadDetails.content.decoded.data = details; |
579 | QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: request.startTimestamp(), ptr: &details->startTime); |
580 | QOpen62541ValueConverter::scalarFromQt<UA_DateTime, QDateTime>(var: request.endTimestamp(), ptr: &details->endTime); |
581 | details->isReadModified = UA_FALSE; |
582 | details->returnBounds = request.returnBounds(); |
583 | details->numValuesPerNode = request.numValuesPerNode(); |
584 | |
585 | quint32 requestId = 0; |
586 | UA_StatusCode resultCode = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &uarequest, requestType: &UA_TYPES[UA_TYPES_HISTORYREADREQUEST], callback: &asyncReadHistoryDataCallBack, |
587 | responseType: &UA_TYPES[UA_TYPES_HISTORYREADRESPONSE], userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
588 | |
589 | UA_HistoryReadRequest_clear(p: &uarequest); |
590 | |
591 | if (resultCode != UA_STATUSCODE_GOOD) { |
592 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Read history data failed:" << resultCode; |
593 | emit historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode(resultCode), handle); |
594 | return; |
595 | } |
596 | |
597 | m_asyncReadHistoryDataContext[requestId] = {.handle: handle, .historyReadRawRequest: request}; |
598 | } |
599 | |
600 | void Open62541AsyncBackend::addNode(const QOpcUaAddNodeItem &nodeToAdd) |
601 | { |
602 | UA_AddNodesRequest req; |
603 | UA_AddNodesRequest_init(p: &req); |
604 | UaDeleter<UA_AddNodesRequest> requestDeleter(&req, UA_AddNodesRequest_clear); |
605 | req.nodesToAddSize = 1; |
606 | req.nodesToAdd = UA_AddNodesItem_new(); |
607 | UA_AddNodesItem_init(p: req.nodesToAdd); |
608 | |
609 | QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>( |
610 | var: nodeToAdd.parentNodeId(), ptr: &req.nodesToAdd->parentNodeId); |
611 | |
612 | req.nodesToAdd->referenceTypeId = Open62541Utils::nodeIdFromQString(name: nodeToAdd.referenceTypeId()); |
613 | |
614 | QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>( |
615 | var: nodeToAdd.requestedNewNodeId(), ptr: &req.nodesToAdd->requestedNewNodeId); |
616 | |
617 | QOpen62541ValueConverter::scalarFromQt<UA_QualifiedName, QOpcUaQualifiedName>( |
618 | var: nodeToAdd.browseName(), ptr: &req.nodesToAdd->browseName); |
619 | |
620 | req.nodesToAdd->nodeClass = static_cast<UA_NodeClass>(nodeToAdd.nodeClass()); |
621 | |
622 | req.nodesToAdd->nodeAttributes = assembleNodeAttributes(nodeAttributes: nodeToAdd.nodeAttributes(), |
623 | nodeClass: nodeToAdd.nodeClass()); |
624 | |
625 | if (!nodeToAdd.typeDefinition().nodeId().isEmpty()) |
626 | QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>( |
627 | var: nodeToAdd.typeDefinition(), ptr: &req.nodesToAdd->typeDefinition); |
628 | |
629 | quint32 requestId = 0; |
630 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &req, requestType: &UA_TYPES[UA_TYPES_ADDNODESREQUEST], |
631 | callback: &asyncAddNodeCallback, |
632 | responseType: &UA_TYPES[UA_TYPES_ADDNODESRESPONSE], |
633 | userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
634 | |
635 | if (result != UA_STATUSCODE_GOOD) { |
636 | qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:" << result; |
637 | emit addNodeFinished(requestedNodeId: nodeToAdd.requestedNewNodeId(), assignedNodeId: QString(), statusCode: static_cast<QOpcUa::UaStatusCode>(result)); |
638 | return; |
639 | } |
640 | |
641 | m_asyncAddNodeContext[requestId] = { .requestedNodeId: nodeToAdd.requestedNewNodeId() }; |
642 | } |
643 | |
644 | void Open62541AsyncBackend::deleteNode(const QString &nodeId, bool deleteTargetReferences) |
645 | { |
646 | UA_DeleteNodesRequest request; |
647 | UA_DeleteNodesRequest_init(p: &request); |
648 | UaDeleter<UA_DeleteNodesRequest> requestDeleter(&request, UA_DeleteNodesRequest_clear); |
649 | |
650 | request.nodesToDeleteSize = 1; |
651 | request.nodesToDelete = UA_DeleteNodesItem_new(); |
652 | |
653 | request.nodesToDelete->nodeId = Open62541Utils::nodeIdFromQString(name: nodeId); |
654 | request.nodesToDelete->deleteTargetReferences = deleteTargetReferences; |
655 | |
656 | quint32 requestId = 0; |
657 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &request, requestType: &UA_TYPES[UA_TYPES_DELETENODESREQUEST], |
658 | callback: &asyncDeleteNodeCallback, |
659 | responseType: &UA_TYPES[UA_TYPES_DELETENODESRESPONSE], |
660 | userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
661 | |
662 | QOpcUa::UaStatusCode resultStatus = static_cast<QOpcUa::UaStatusCode>(result); |
663 | |
664 | if (result != QOpcUa::UaStatusCode::Good) { |
665 | qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to delete node" << nodeId << "with status code" << resultStatus; |
666 | emit deleteNodeFinished(nodeId, statusCode: resultStatus); |
667 | return; |
668 | } |
669 | |
670 | m_asyncDeleteNodeContext[requestId] = { .nodeId: nodeId }; |
671 | } |
672 | |
673 | void Open62541AsyncBackend::addReference(const QOpcUaAddReferenceItem &referenceToAdd) |
674 | { |
675 | UA_AddReferencesRequest request; |
676 | UA_AddReferencesRequest_init(p: &request); |
677 | UaDeleter<UA_AddReferencesRequest> requestDeleter(&request, UA_AddReferencesRequest_clear); |
678 | |
679 | request.referencesToAddSize = 1; |
680 | request.referencesToAdd = UA_AddReferencesItem_new(); |
681 | |
682 | request.referencesToAdd->isForward = referenceToAdd.isForwardReference(); |
683 | QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: referenceToAdd.sourceNodeId(), |
684 | ptr: &request.referencesToAdd->sourceNodeId); |
685 | QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(var: referenceToAdd.targetNodeId(), |
686 | ptr: &request.referencesToAdd->targetNodeId); |
687 | QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: referenceToAdd.referenceTypeId(), |
688 | ptr: &request.referencesToAdd->referenceTypeId); |
689 | request.referencesToAdd->targetNodeClass = static_cast<UA_NodeClass>(referenceToAdd.targetNodeClass()); |
690 | QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: referenceToAdd.targetServerUri(), |
691 | ptr: &request.referencesToAdd->targetServerUri); |
692 | |
693 | quint32 requestId = 0; |
694 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &request, requestType: &UA_TYPES[UA_TYPES_ADDREFERENCESREQUEST], |
695 | callback: &asyncAddReferenceCallback, |
696 | responseType: &UA_TYPES[UA_TYPES_ADDREFERENCESRESPONSE], |
697 | userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
698 | |
699 | QOpcUa::UaStatusCode statusCode = static_cast<QOpcUa::UaStatusCode>(result); |
700 | if (result != UA_STATUSCODE_GOOD) { |
701 | qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add reference from" << referenceToAdd.sourceNodeId() << "to" |
702 | << referenceToAdd.targetNodeId().nodeId() << ":" << statusCode; |
703 | emit addReferenceFinished(sourceNodeId: referenceToAdd.sourceNodeId(), referenceTypeId: referenceToAdd.referenceTypeId(), |
704 | targetNodeId: referenceToAdd.targetNodeId(), isForwardReference: referenceToAdd.isForwardReference(), statusCode); |
705 | return; |
706 | } |
707 | |
708 | m_asyncAddReferenceContext[requestId] = { .sourceNodeId: referenceToAdd.sourceNodeId(), .referenceTypeId: referenceToAdd.referenceTypeId(), |
709 | .targetNodeId: referenceToAdd.targetNodeId(), .isForwardReference: referenceToAdd.isForwardReference() }; |
710 | } |
711 | |
712 | void Open62541AsyncBackend::deleteReference(const QOpcUaDeleteReferenceItem &referenceToDelete) |
713 | { |
714 | UA_DeleteReferencesRequest request; |
715 | UA_DeleteReferencesRequest_init(p: &request); |
716 | UaDeleter<UA_DeleteReferencesRequest> requestDeleter(&request, UA_DeleteReferencesRequest_clear); |
717 | |
718 | request.referencesToDeleteSize = 1; |
719 | request.referencesToDelete = UA_DeleteReferencesItem_new(); |
720 | request.referencesToDelete->isForward = referenceToDelete.isForwardReference(); |
721 | QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: referenceToDelete.sourceNodeId(), |
722 | ptr: &request.referencesToDelete->sourceNodeId); |
723 | QOpen62541ValueConverter::scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(var: referenceToDelete.targetNodeId(), |
724 | ptr: &request.referencesToDelete->targetNodeId); |
725 | QOpen62541ValueConverter::scalarFromQt<UA_NodeId, QString>(var: referenceToDelete.referenceTypeId(), |
726 | ptr: &request.referencesToDelete->referenceTypeId); |
727 | request.referencesToDelete->deleteBidirectional = referenceToDelete.deleteBidirectional(); |
728 | |
729 | quint32 requestId = 0; |
730 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &request, requestType: &UA_TYPES[UA_TYPES_DELETEREFERENCESREQUEST], |
731 | callback: &asyncDeleteReferenceCallback, |
732 | responseType: &UA_TYPES[UA_TYPES_DELETEREFERENCESRESPONSE], |
733 | userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
734 | |
735 | QOpcUa::UaStatusCode statusCode = static_cast<QOpcUa::UaStatusCode>(result); |
736 | if (result != UA_STATUSCODE_GOOD) { |
737 | qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to delete reference from" << referenceToDelete.sourceNodeId() << "to" |
738 | << referenceToDelete.targetNodeId().nodeId() << ":" << statusCode; |
739 | |
740 | emit deleteReferenceFinished(sourceNodeId: referenceToDelete.sourceNodeId(), referenceTypeId: referenceToDelete.referenceTypeId(), |
741 | targetNodeId: referenceToDelete.targetNodeId(), |
742 | isForwardReference: referenceToDelete.isForwardReference(), statusCode); |
743 | return; |
744 | } |
745 | |
746 | m_asyncDeleteReferenceContext[requestId] = { .sourceNodeId: referenceToDelete.sourceNodeId(), .referenceTypeId: referenceToDelete.referenceTypeId(), |
747 | .targetNodeId: referenceToDelete.targetNodeId(), .isForwardReference: referenceToDelete.isForwardReference()}; |
748 | } |
749 | |
750 | static void convertBrowseResult(UA_BrowseResult *src, size_t referencesSize, QList<QOpcUaReferenceDescription> &dst) |
751 | { |
752 | if (!src) |
753 | return; |
754 | |
755 | for (size_t i = 0; i < referencesSize; ++i) { |
756 | QOpcUaReferenceDescription temp; |
757 | temp.setTargetNodeId(QOpen62541ValueConverter::scalarToQt<QOpcUaExpandedNodeId>(data: &src->references[i].nodeId)); |
758 | temp.setTypeDefinition(QOpen62541ValueConverter::scalarToQt<QOpcUaExpandedNodeId>(data: &src->references[i].typeDefinition)); |
759 | temp.setRefTypeId(Open62541Utils::nodeIdToQString(id: src->references[i].referenceTypeId)); |
760 | temp.setNodeClass(static_cast<QOpcUa::NodeClass>(src->references[i].nodeClass)); |
761 | temp.setBrowseName(QOpen62541ValueConverter::scalarToQt<QOpcUaQualifiedName, UA_QualifiedName>(data: &src->references[i].browseName)); |
762 | temp.setDisplayName(QOpen62541ValueConverter::scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &src->references[i].displayName)); |
763 | temp.setIsForwardReference(src->references[i].isForward); |
764 | dst.push_back(t: temp); |
765 | } |
766 | } |
767 | |
768 | void Open62541AsyncBackend::browse(quint64 handle, UA_NodeId id, const QOpcUaBrowseRequest &request) |
769 | { |
770 | UA_BrowseRequest uaRequest; |
771 | UA_BrowseRequest_init(p: &uaRequest); |
772 | UaDeleter<UA_BrowseRequest> requestDeleter(&uaRequest, UA_BrowseRequest_clear); |
773 | |
774 | uaRequest.nodesToBrowse = UA_BrowseDescription_new(); |
775 | uaRequest.nodesToBrowseSize = 1; |
776 | uaRequest.nodesToBrowse->browseDirection = static_cast<UA_BrowseDirection>(request.browseDirection()); |
777 | uaRequest.nodesToBrowse->includeSubtypes = request.includeSubtypes(); |
778 | uaRequest.nodesToBrowse->nodeClassMask = static_cast<quint32>(request.nodeClassMask()); |
779 | uaRequest.nodesToBrowse->nodeId = id; |
780 | uaRequest.nodesToBrowse->resultMask = UA_BROWSERESULTMASK_ALL; |
781 | uaRequest.nodesToBrowse->referenceTypeId = Open62541Utils::nodeIdFromQString(name: request.referenceTypeId()); |
782 | uaRequest.requestedMaxReferencesPerNode = 0; // Let the server choose a maximum value |
783 | |
784 | quint32 requestId = 0; |
785 | UA_StatusCode result = __UA_Client_AsyncServiceEx(client: m_uaclient, request: &uaRequest, requestType: &UA_TYPES[UA_TYPES_BROWSEREQUEST], callback: &asyncBrowseCallback, |
786 | responseType: &UA_TYPES[UA_TYPES_BROWSERESPONSE], userdata: this, requestId: &requestId, timeout: m_asyncRequestTimeout); |
787 | |
788 | if (result != UA_STATUSCODE_GOOD) { |
789 | emit browseFinished(handle, children: QList<QOpcUaReferenceDescription>(), statusCode: static_cast<QOpcUa::UaStatusCode>(result)); |
790 | return; |
791 | } |
792 | |
793 | m_asyncBrowseContext[requestId] = { .handle: handle, .isBrowseNext: false, .results: QList<QOpcUaReferenceDescription>() }; |
794 | } |
795 | |
796 | void Open62541AsyncBackend::clientStateCallback(UA_Client *client, |
797 | UA_SecureChannelState channelState, |
798 | UA_SessionState sessionState, |
799 | UA_StatusCode connectStatus) |
800 | { |
801 | Q_UNUSED(channelState) |
802 | Q_UNUSED(sessionState) |
803 | Q_UNUSED(connectStatus) |
804 | |
805 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(UA_Client_getContext(client)); |
806 | if (!backend || !backend->m_useStateCallback) |
807 | return; |
808 | |
809 | backend->m_useStateCallback = false; |
810 | backend->m_clientIterateTimer.stop(); |
811 | |
812 | // UA_Client_disconnect() must be called from outside this callback or open62541 will crash |
813 | backend->m_disconnectAfterStateChangeTimer.start(); |
814 | } |
815 | |
816 | void Open62541AsyncBackend::inactivityCallback(UA_Client *client) |
817 | { |
818 | // The client state callback is not called if the background check encounters a timeout. |
819 | // This call triggers the disconnect and the appropriate state change |
820 | clientStateCallback(client, channelState: UA_SECURECHANNELSTATE_CLOSED, sessionState: UA_SESSIONSTATE_CLOSED, UA_STATUSCODE_BADTIMEOUT); |
821 | } |
822 | |
823 | void Open62541AsyncBackend::connectToEndpoint(const QOpcUaEndpointDescription &endpoint) |
824 | { |
825 | disconnectInternal(); |
826 | |
827 | QString errorMessage; |
828 | if (!verifyEndpointDescription(endpoint, message: &errorMessage)) { |
829 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << errorMessage; |
830 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::ClientError::InvalidUrl); |
831 | return; |
832 | } |
833 | |
834 | if (!m_clientImpl->supportedSecurityPolicies().contains(str: endpoint.securityPolicy())) { |
835 | #ifndef UA_ENABLE_ENCRYPTION |
836 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "The open62541 plugin has been built without encryption support" ; |
837 | #endif |
838 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unsupported security policy:" << endpoint.securityPolicy(); |
839 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::ClientError::InvalidUrl); |
840 | return; |
841 | } |
842 | |
843 | emit stateAndOrErrorChanged(state: QOpcUaClient::Connecting, error: QOpcUaClient::NoError); |
844 | |
845 | m_uaclient = UA_Client_new(); |
846 | auto conf = UA_Client_getConfig(client: m_uaclient); |
847 | |
848 | conf->logger = m_open62541Logger; |
849 | |
850 | const auto identity = m_clientImpl->m_client->applicationIdentity(); |
851 | const auto authInfo = m_clientImpl->m_client->authenticationInformation(); |
852 | const auto connectionSettings = m_clientImpl->m_client->connectionSettings(); |
853 | #ifdef UA_ENABLE_ENCRYPTION |
854 | const auto pkiConfig = m_clientImpl->m_client->pkiConfiguration(); |
855 | #endif |
856 | |
857 | #ifdef UA_ENABLE_ENCRYPTION |
858 | if (pkiConfig.isPkiValid()) { |
859 | UA_ByteString localCertificate; |
860 | UA_ByteString privateKey; |
861 | UA_ByteString *trustList = nullptr; |
862 | qsizetype trustListSize = 0; |
863 | UA_ByteString *revocationList = nullptr; |
864 | qsizetype revocationListSize = 0; |
865 | |
866 | bool success = loadFileToByteString(location: pkiConfig.clientCertificateFile(), target: &localCertificate); |
867 | |
868 | if (!success) { |
869 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to load client certificate" ; |
870 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); |
871 | UA_Client_delete(client: m_uaclient); |
872 | m_uaclient = nullptr; |
873 | return; |
874 | } |
875 | |
876 | UaDeleter<UA_ByteString> clientCertDeleter(&localCertificate, &UA_ByteString_clear); |
877 | |
878 | success = loadFileToByteString(location: pkiConfig.privateKeyFile(), target: &privateKey); |
879 | |
880 | if (!success) { |
881 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to load private key" ; |
882 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); |
883 | UA_Client_delete(client: m_uaclient); |
884 | m_uaclient = nullptr; |
885 | return; |
886 | } |
887 | |
888 | UaDeleter<UA_ByteString> privateKeyDeleter(&privateKey, &UA_ByteString_clear); |
889 | |
890 | success = loadAllFilesInDirectory(location: pkiConfig.trustListDirectory(), target: &trustList, size: &trustListSize); |
891 | |
892 | if (!success) { |
893 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to load trust list" ; |
894 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); |
895 | UA_Client_delete(client: m_uaclient); |
896 | m_uaclient = nullptr; |
897 | return; |
898 | } |
899 | |
900 | UaArrayDeleter<UA_TYPES_BYTESTRING> trustListDeleter(trustList, trustListSize); |
901 | |
902 | success = loadAllFilesInDirectory(location: pkiConfig.revocationListDirectory(), target: &revocationList, size: &revocationListSize); |
903 | |
904 | if (!success) { |
905 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to load revocation list" ; |
906 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); |
907 | UA_Client_delete(client: m_uaclient); |
908 | m_uaclient = nullptr; |
909 | return; |
910 | } |
911 | |
912 | UaArrayDeleter<UA_TYPES_BYTESTRING> revocationListDeleter(revocationList, revocationListSize); |
913 | |
914 | UA_StatusCode result = UA_ClientConfig_setDefaultEncryption(config: conf, localCertificate, privateKey, trustList, |
915 | trustListSize, revocationList, revocationListSize); |
916 | |
917 | if (result != UA_STATUSCODE_GOOD) { |
918 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to initialize PKI:" << static_cast<QOpcUa::UaStatusCode>(result); |
919 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::AccessDenied); |
920 | UA_Client_delete(client: m_uaclient); |
921 | m_uaclient = nullptr; |
922 | return; |
923 | } |
924 | } else { |
925 | #else |
926 | { |
927 | #endif |
928 | UA_ClientConfig_setDefault(config: conf); |
929 | } |
930 | |
931 | using Timeout_t = decltype(conf->timeout); |
932 | conf->timeout = qt_saturate<Timeout_t>(x: connectionSettings.connectTimeout().count()); |
933 | conf->secureChannelLifeTime = qt_saturate<Timeout_t>(x: connectionSettings.secureChannelLifeTime().count()); |
934 | conf->requestedSessionTimeout = qt_saturate<Timeout_t>(x: connectionSettings.sessionTimeout().count()); |
935 | |
936 | const auto sessionLocaleIds = connectionSettings.sessionLocaleIds(); |
937 | if (!sessionLocaleIds.isEmpty()) { |
938 | conf->sessionLocaleIds = static_cast<UA_String *>(UA_Array_new(size: sessionLocaleIds.size(), type: &UA_TYPES[UA_TYPES_STRING])); |
939 | for (qsizetype i = 0; i < sessionLocaleIds.size(); ++i) |
940 | conf->sessionLocaleIds[i] = UA_STRING_ALLOC(sessionLocaleIds[i].toUtf8().constData()); |
941 | conf->sessionLocaleIdsSize = sessionLocaleIds.size(); |
942 | } |
943 | |
944 | UA_LocalizedText_clear(p: &conf->clientDescription.applicationName); |
945 | UA_String_clear(p: &conf->clientDescription.applicationUri); |
946 | UA_String_clear(p: &conf->clientDescription.productUri); |
947 | |
948 | conf->clientContext = this; |
949 | conf->stateCallback = clientStateCallback; |
950 | |
951 | // Send periodic read requests as keepalive |
952 | conf->connectivityCheckInterval = 60000; |
953 | conf->inactivityCallback = inactivityCallback; |
954 | |
955 | conf->clientDescription.applicationName = UA_LOCALIZEDTEXT_ALLOC(locale: "" , text: identity.applicationName().toUtf8().constData()); |
956 | conf->clientDescription.applicationUri = UA_STRING_ALLOC(identity.applicationUri().toUtf8().constData()); |
957 | conf->clientDescription.productUri = UA_STRING_ALLOC(identity.productUri().toUtf8().constData()); |
958 | conf->clientDescription.applicationType = UA_APPLICATIONTYPE_CLIENT; |
959 | |
960 | conf->securityPolicyUri = UA_STRING_ALLOC(endpoint.securityPolicy().toUtf8().constData()); |
961 | conf->securityMode = static_cast<UA_MessageSecurityMode>(endpoint.securityMode()); |
962 | |
963 | UA_StatusCode ret; |
964 | |
965 | if (authInfo.authenticationType() == QOpcUaUserTokenPolicy::TokenType::Anonymous) { |
966 | ret = UA_Client_connect(client: m_uaclient, endpointUrl: endpoint.endpointUrl().toUtf8().constData()); |
967 | } else if (authInfo.authenticationType() == QOpcUaUserTokenPolicy::TokenType::Username) { |
968 | |
969 | bool suitableTokenFound = false; |
970 | const auto userIdentityTokens = endpoint.userIdentityTokens(); |
971 | for (const auto &token : userIdentityTokens) { |
972 | if (token.tokenType() == QOpcUaUserTokenPolicy::Username && |
973 | m_clientImpl->supportedSecurityPolicies().contains(str: token.securityPolicy())) { |
974 | suitableTokenFound = true; |
975 | break; |
976 | } |
977 | } |
978 | |
979 | if (!suitableTokenFound) { |
980 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "No suitable user token policy found" ; |
981 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::ClientError::NoError); |
982 | UA_Client_delete(client: m_uaclient); |
983 | m_uaclient = nullptr; |
984 | return; |
985 | } |
986 | |
987 | const auto credentials = authInfo.authenticationData().value<QPair<QString, QString>>(); |
988 | ret = UA_Client_connectUsername(client: m_uaclient, endpointUrl: endpoint.endpointUrl().toUtf8().constData(), |
989 | username: credentials.first.toUtf8().constData(), password: credentials.second.toUtf8().constData()); |
990 | } else { |
991 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error: QOpcUaClient::UnsupportedAuthenticationInformation); |
992 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to connect: Selected authentication type" |
993 | << authInfo.authenticationType() << "is not supported." ; |
994 | UA_Client_delete(client: m_uaclient); |
995 | m_uaclient = nullptr; |
996 | return; |
997 | } |
998 | |
999 | if (ret != UA_STATUSCODE_GOOD) { |
1000 | UA_Client_delete(client: m_uaclient); |
1001 | m_uaclient = nullptr; |
1002 | QOpcUaClient::ClientError error = ret == UA_STATUSCODE_BADUSERACCESSDENIED ? QOpcUaClient::AccessDenied : QOpcUaClient::UnknownError; |
1003 | |
1004 | QOpcUaErrorState errorState; |
1005 | errorState.setConnectionStep(QOpcUaErrorState::ConnectionStep::Unknown); |
1006 | errorState.setErrorCode(static_cast<QOpcUa::UaStatusCode>(ret)); |
1007 | errorState.setClientSideError(false); |
1008 | errorState.setIgnoreError(false); |
1009 | |
1010 | // This signal is connected using Qt::BlockingQueuedConnection. It will place a metacall to a different thread and waits |
1011 | // until this metacall is fully handled before returning. |
1012 | emit QOpcUaBackend::connectError(errorState: &errorState); |
1013 | |
1014 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error); |
1015 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Open62541: Failed to connect" ; |
1016 | return; |
1017 | } |
1018 | |
1019 | conf->timeout = qt_saturate<Timeout_t>(x: connectionSettings.requestTimeout().count()); |
1020 | |
1021 | m_useStateCallback = true; |
1022 | m_clientIterateTimer.start(msec: m_clientIterateInterval); |
1023 | emit stateAndOrErrorChanged(state: QOpcUaClient::Connected, error: QOpcUaClient::NoError); |
1024 | } |
1025 | |
1026 | void Open62541AsyncBackend::disconnectFromEndpoint() |
1027 | { |
1028 | disconnectInternal(); |
1029 | } |
1030 | |
1031 | void Open62541AsyncBackend::requestEndpoints(const QUrl &url) |
1032 | { |
1033 | UA_Client *tmpClient = UA_Client_new(); |
1034 | auto conf = UA_Client_getConfig(client: tmpClient); |
1035 | |
1036 | conf->logger = m_open62541Logger; |
1037 | |
1038 | UA_ClientConfig_setDefault(config: conf); |
1039 | |
1040 | size_t numEndpoints = 0; |
1041 | UA_EndpointDescription *endpoints = nullptr; |
1042 | UA_StatusCode res = UA_Client_getEndpoints(client: tmpClient, serverUrl: url.toString(options: QUrl::RemoveUserInfo).toUtf8().constData(), endpointDescriptionsSize: &numEndpoints, endpointDescriptions: &endpoints); |
1043 | UaArrayDeleter<UA_TYPES_ENDPOINTDESCRIPTION> endpointDescriptionDeleter(endpoints, numEndpoints); |
1044 | QList<QOpcUaEndpointDescription> ret; |
1045 | |
1046 | namespace vc = QOpen62541ValueConverter; |
1047 | using namespace QOpcUa; |
1048 | if (res == UA_STATUSCODE_GOOD && numEndpoints) { |
1049 | for (size_t i = 0; i < numEndpoints ; ++i) { |
1050 | QOpcUaEndpointDescription epd; |
1051 | QOpcUaApplicationDescription &apd = epd.serverRef(); |
1052 | |
1053 | apd.setApplicationUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.applicationUri)); |
1054 | apd.setProductUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.productUri)); |
1055 | apd.setApplicationName(vc::scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &endpoints[i].server.applicationName)); |
1056 | apd.setApplicationType(static_cast<QOpcUaApplicationDescription::ApplicationType>(endpoints[i].server.applicationType)); |
1057 | apd.setGatewayServerUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.gatewayServerUri)); |
1058 | apd.setDiscoveryProfileUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.discoveryProfileUri)); |
1059 | for (size_t j = 0; j < endpoints[i].server.discoveryUrlsSize; ++j) |
1060 | apd.discoveryUrlsRef().append(t: vc::scalarToQt<QString, UA_String>(data: &endpoints[i].server.discoveryUrls[j])); |
1061 | |
1062 | epd.setEndpointUrl(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].endpointUrl)); |
1063 | epd.setServerCertificate(vc::scalarToQt<QByteArray, UA_ByteString>(data: &endpoints[i].serverCertificate)); |
1064 | epd.setSecurityMode(static_cast<QOpcUaEndpointDescription::MessageSecurityMode>(endpoints[i].securityMode)); |
1065 | epd.setSecurityPolicy(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].securityPolicyUri)); |
1066 | for (size_t j = 0; j < endpoints[i].userIdentityTokensSize; ++j) { |
1067 | QOpcUaUserTokenPolicy policy; |
1068 | UA_UserTokenPolicy *policySrc = &endpoints[i].userIdentityTokens[j]; |
1069 | policy.setPolicyId(vc::scalarToQt<QString, UA_String>(data: &policySrc->policyId)); |
1070 | policy.setTokenType(static_cast<QOpcUaUserTokenPolicy::TokenType>(endpoints[i].userIdentityTokens[j].tokenType)); |
1071 | policy.setIssuedTokenType(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].userIdentityTokens[j].issuedTokenType)); |
1072 | policy.setIssuerEndpointUrl(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].userIdentityTokens[j].issuerEndpointUrl)); |
1073 | policy.setSecurityPolicy(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].userIdentityTokens[j].securityPolicyUri)); |
1074 | epd.userIdentityTokensRef().append(t: policy); |
1075 | } |
1076 | |
1077 | epd.setTransportProfileUri(vc::scalarToQt<QString, UA_String>(data: &endpoints[i].transportProfileUri)); |
1078 | epd.setSecurityLevel(endpoints[i].securityLevel); |
1079 | ret.append(t: epd); |
1080 | } |
1081 | } else { |
1082 | if (res == UA_STATUSCODE_GOOD) |
1083 | qWarning() << "Server returned an empty endpoint list" ; |
1084 | else |
1085 | qWarning() << "Failed to retrieve endpoints from " << url.toString(options: QUrl::RemoveUserInfo).toUtf8().constData() |
1086 | << "with status" << UA_StatusCode_name(code: res); |
1087 | } |
1088 | |
1089 | emit endpointsRequestFinished(endpoints: ret, statusCode: static_cast<QOpcUa::UaStatusCode>(res), requestUrl: url); |
1090 | |
1091 | UA_Client_delete(client: tmpClient); |
1092 | } |
1093 | |
1094 | void Open62541AsyncBackend::iterateClient() |
1095 | { |
1096 | if (!m_uaclient) |
1097 | return; |
1098 | |
1099 | // If BADSERVERNOTCONNECTED is returned, the subscriptions are gone and local information can be deleted. |
1100 | if (UA_Client_run_iterate(client: m_uaclient, |
1101 | timeout: std::max<quint32>(a: 1, b: m_clientIterateInterval / 2)) == UA_STATUSCODE_BADSERVERNOTCONNECTED) { |
1102 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to send publish request" ; |
1103 | cleanupSubscriptions(); |
1104 | } |
1105 | } |
1106 | |
1107 | void Open62541AsyncBackend::handleSubscriptionTimeout(QOpen62541Subscription *sub, QList<QPair<quint64, QOpcUa::NodeAttribute>> items) |
1108 | { |
1109 | for (auto it : std::as_const(t&: items)) { |
1110 | auto item = m_attributeMapping.find(key: it.first); |
1111 | if (item == m_attributeMapping.end()) |
1112 | continue; |
1113 | item->remove(key: it.second); |
1114 | } |
1115 | m_subscriptions.remove(key: sub->subscriptionId()); |
1116 | delete sub; |
1117 | } |
1118 | |
1119 | QOpen62541Subscription *Open62541AsyncBackend::getSubscriptionForItem(quint64 handle, QOpcUa::NodeAttribute attr) |
1120 | { |
1121 | auto nodeEntry = m_attributeMapping.find(key: handle); |
1122 | if (nodeEntry == m_attributeMapping.end()) |
1123 | return nullptr; |
1124 | |
1125 | auto subscription = nodeEntry->find(key: attr); |
1126 | if (subscription == nodeEntry->end()) { |
1127 | return nullptr; |
1128 | } |
1129 | |
1130 | return subscription.value(); |
1131 | } |
1132 | |
1133 | QOpcUaApplicationDescription Open62541AsyncBackend::convertApplicationDescription(UA_ApplicationDescription &desc) |
1134 | { |
1135 | QOpcUaApplicationDescription temp; |
1136 | |
1137 | temp.setApplicationUri(QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.applicationUri)); |
1138 | temp.setProductUri(QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.productUri)); |
1139 | temp.setApplicationName(QOpen62541ValueConverter::scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &desc.applicationName)); |
1140 | temp.setApplicationType(static_cast<QOpcUaApplicationDescription::ApplicationType>(desc.applicationType)); |
1141 | temp.setGatewayServerUri(QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.gatewayServerUri)); |
1142 | temp.setDiscoveryProfileUri(QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.discoveryProfileUri)); |
1143 | |
1144 | |
1145 | for (size_t i = 0; i < desc.discoveryUrlsSize; ++i) |
1146 | temp.discoveryUrlsRef().append(t: QOpen62541ValueConverter::scalarToQt<QString, UA_String>(data: &desc.discoveryUrls[i])); |
1147 | |
1148 | return temp; |
1149 | } |
1150 | |
1151 | void Open62541AsyncBackend::cleanupSubscriptions() |
1152 | { |
1153 | qDeleteAll(c: m_subscriptions); |
1154 | m_subscriptions.clear(); |
1155 | m_attributeMapping.clear(); |
1156 | m_minPublishingInterval = 0; |
1157 | } |
1158 | |
1159 | void Open62541AsyncBackend::asyncMethodCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1160 | { |
1161 | Q_UNUSED(client) |
1162 | |
1163 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1164 | const auto context = backend->m_asyncCallContext.take(key: requestId); |
1165 | |
1166 | QVariant result; |
1167 | |
1168 | const auto cr = static_cast<UA_CallResponse *>(response); |
1169 | |
1170 | if (cr->resultsSize && cr->results->outputArgumentsSize > 1 && cr->results->statusCode == UA_STATUSCODE_GOOD) { |
1171 | QVariantList temp; |
1172 | for (size_t i = 0; i < cr->results->outputArgumentsSize; ++i) |
1173 | temp.append(t: QOpen62541ValueConverter::toQVariant(cr->results->outputArguments[i])); |
1174 | |
1175 | result = temp; |
1176 | } else if (cr->resultsSize && cr->results->outputArgumentsSize == 1 && cr->results->statusCode == UA_STATUSCODE_GOOD) { |
1177 | result = QOpen62541ValueConverter::toQVariant(cr->results->outputArguments[0]); |
1178 | } |
1179 | |
1180 | emit backend->methodCallFinished(handle: context.handle, methodNodeId: context.methodNodeId, result, |
1181 | statusCode: static_cast<QOpcUa::UaStatusCode>(cr->resultsSize ? |
1182 | cr->results->statusCode : |
1183 | cr->responseHeader.serviceResult)); |
1184 | } |
1185 | |
1186 | void Open62541AsyncBackend::asyncTranslateBrowsePathCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1187 | { |
1188 | Q_UNUSED(client) |
1189 | |
1190 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1191 | const auto context = backend->m_asyncTranslateContext.take(key: requestId); |
1192 | |
1193 | const auto res = static_cast<UA_TranslateBrowsePathsToNodeIdsResponse *>(response); |
1194 | |
1195 | if (res->responseHeader.serviceResult != UA_STATUSCODE_GOOD || res->resultsSize != 1) { |
1196 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Translate browse path failed:" << UA_StatusCode_name(code: res->responseHeader.serviceResult); |
1197 | emit backend->resolveBrowsePathFinished(handle: context.handle, targets: QList<QOpcUaBrowsePathTarget>(), path: context.path, |
1198 | statusCode: static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult)); |
1199 | return; |
1200 | } |
1201 | |
1202 | QList<QOpcUaBrowsePathTarget> ret; |
1203 | for (size_t i = 0; i < res->results->targetsSize ; ++i) { |
1204 | QOpcUaBrowsePathTarget temp; |
1205 | temp.setRemainingPathIndex(res->results->targets[i].remainingPathIndex); |
1206 | temp.targetIdRef().setNamespaceUri(QString::fromUtf8(utf8: reinterpret_cast<char *>(res->results->targets[i].targetId.namespaceUri.data))); |
1207 | temp.targetIdRef().setServerIndex(res->results->targets[i].targetId.serverIndex); |
1208 | temp.targetIdRef().setNodeId(Open62541Utils::nodeIdToQString(id: res->results->targets[i].targetId.nodeId)); |
1209 | ret.append(t: temp); |
1210 | } |
1211 | |
1212 | emit backend->resolveBrowsePathFinished(handle: context.handle, targets: ret, path: context.path, statusCode: static_cast<QOpcUa::UaStatusCode>(res->results->statusCode)); |
1213 | } |
1214 | |
1215 | void Open62541AsyncBackend::asyncAddNodeCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1216 | { |
1217 | Q_UNUSED(client) |
1218 | |
1219 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1220 | const auto context = backend->m_asyncAddNodeContext.take(key: requestId); |
1221 | |
1222 | const auto res = static_cast<UA_AddNodesResponse *>(response); |
1223 | |
1224 | QOpcUa::UaStatusCode status = QOpcUa::UaStatusCode::Good; |
1225 | QString resultId; |
1226 | if (res->responseHeader.serviceResult == UA_STATUSCODE_GOOD) { |
1227 | if (res->results->statusCode == UA_STATUSCODE_GOOD) |
1228 | resultId = Open62541Utils::nodeIdToQString(id: res->results->addedNodeId); |
1229 | else { |
1230 | status = static_cast<QOpcUa::UaStatusCode>(res->results->statusCode); |
1231 | qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:" << status; |
1232 | } |
1233 | } else { |
1234 | status = static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult); |
1235 | qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add node:" << status; |
1236 | } |
1237 | |
1238 | emit backend->addNodeFinished(requestedNodeId: context.requestedNodeId, assignedNodeId: resultId, statusCode: status); |
1239 | } |
1240 | |
1241 | void Open62541AsyncBackend::asyncDeleteNodeCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1242 | { |
1243 | Q_UNUSED(client) |
1244 | |
1245 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1246 | const auto context = backend->m_asyncDeleteNodeContext.take(key: requestId); |
1247 | |
1248 | const auto res = static_cast<UA_DeleteNodesResponse *>(response); |
1249 | |
1250 | emit backend->deleteNodeFinished(nodeId: context.nodeId, |
1251 | statusCode: static_cast<QOpcUa::UaStatusCode>(res->resultsSize ? |
1252 | res->results[0] : res->responseHeader.serviceResult)); |
1253 | } |
1254 | |
1255 | void Open62541AsyncBackend::asyncAddReferenceCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1256 | { |
1257 | Q_UNUSED(client) |
1258 | |
1259 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1260 | const auto context = backend->m_asyncAddReferenceContext.take(key: requestId); |
1261 | |
1262 | const auto res = static_cast<UA_AddReferencesResponse *>(response); |
1263 | |
1264 | emit backend->addReferenceFinished(sourceNodeId: context.sourceNodeId, referenceTypeId: context.referenceTypeId, targetNodeId: context.targetNodeId, |
1265 | isForwardReference: context.isForwardReference, |
1266 | statusCode: static_cast<QOpcUa::UaStatusCode>(res->resultsSize ? |
1267 | res->results[0] : res->responseHeader.serviceResult)); |
1268 | } |
1269 | |
1270 | void Open62541AsyncBackend::asyncDeleteReferenceCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1271 | { |
1272 | Q_UNUSED(client) |
1273 | |
1274 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1275 | const auto context = backend->m_asyncDeleteReferenceContext.take(key: requestId); |
1276 | |
1277 | const auto res = static_cast<UA_DeleteReferencesResponse *>(response); |
1278 | |
1279 | emit backend->deleteReferenceFinished(sourceNodeId: context.sourceNodeId, referenceTypeId: context.referenceTypeId, |
1280 | targetNodeId: context.targetNodeId, isForwardReference: context.isForwardReference, |
1281 | statusCode: static_cast<QOpcUa::UaStatusCode>(res->resultsSize ? |
1282 | res->results[0] : |
1283 | res->responseHeader.serviceResult)); |
1284 | } |
1285 | |
1286 | void Open62541AsyncBackend::asyncReadCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1287 | { |
1288 | Q_UNUSED(client) |
1289 | |
1290 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1291 | auto context = backend->m_asyncReadContext.take(key: requestId); |
1292 | |
1293 | const auto res = static_cast<UA_ReadResponse *>(response); |
1294 | |
1295 | for (qsizetype i = 0; i < context.results.size(); ++i) { |
1296 | // Use the service result as status code if there is no specific result for the current value. |
1297 | // This ensures a result for each attribute when UA_Client_Service_read is called for a disconnected client. |
1298 | if (static_cast<size_t>(i) >= res->resultsSize) { |
1299 | context.results[i].setStatusCode(static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult)); |
1300 | continue; |
1301 | } |
1302 | if (res->results[i].hasStatus) |
1303 | context.results[i].setStatusCode(static_cast<QOpcUa::UaStatusCode>(res->results[i].status)); |
1304 | else |
1305 | context.results[i].setStatusCode(QOpcUa::UaStatusCode::Good); |
1306 | if (res->results[i].hasValue && res->results[i].value.data) |
1307 | context.results[i].setValue(QOpen62541ValueConverter::toQVariant(res->results[i].value)); |
1308 | if (res->results[i].hasSourceTimestamp) |
1309 | context.results[i].setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(data: &res->results[i].sourceTimestamp)); |
1310 | if (res->results[i].hasServerTimestamp) |
1311 | context.results[i].setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(data: &res->results[i].serverTimestamp)); |
1312 | } |
1313 | |
1314 | emit backend->attributesRead(handle: context.handle, attributes: context.results, |
1315 | serviceResult: static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult)); |
1316 | } |
1317 | |
1318 | void Open62541AsyncBackend::asyncWriteAttributesCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1319 | { |
1320 | Q_UNUSED(client) |
1321 | |
1322 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1323 | auto context = backend->m_asyncWriteAttributesContext.take(key: requestId); |
1324 | |
1325 | const auto res = static_cast<UA_WriteResponse *>(response); |
1326 | |
1327 | size_t index = 0; |
1328 | for (auto it = context.toWrite.begin(); it != context.toWrite.end(); ++it, ++index) { |
1329 | QOpcUa::UaStatusCode status = index < res->resultsSize ? |
1330 | static_cast<QOpcUa::UaStatusCode>(res->results[index]) : |
1331 | static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult); |
1332 | emit backend->attributeWritten(handle: context.handle, attribute: it.key(), value: it.value(), statusCode: status); |
1333 | } |
1334 | } |
1335 | |
1336 | void Open62541AsyncBackend::asyncBrowseCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1337 | { |
1338 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1339 | auto context = backend->m_asyncBrowseContext.take(key: requestId); |
1340 | |
1341 | UA_StatusCode statusCode = UA_STATUSCODE_GOOD; |
1342 | size_t referencesSize = 0; |
1343 | UA_BrowseResult *references = nullptr; |
1344 | UA_ByteString *continuationPoint = nullptr; |
1345 | |
1346 | if (context.isBrowseNext) { |
1347 | const auto res = static_cast<UA_BrowseNextResponse *>(response); |
1348 | referencesSize = res->resultsSize ? res->results->referencesSize : 0; |
1349 | references = res->results; |
1350 | statusCode = referencesSize ? references->statusCode : res->responseHeader.serviceResult; |
1351 | continuationPoint = res->resultsSize ? &res->results->continuationPoint : nullptr; |
1352 | } else { |
1353 | const auto res = static_cast<UA_BrowseResponse *>(response); |
1354 | referencesSize = res->resultsSize ? res->results->referencesSize : 0; |
1355 | references = res->results; |
1356 | statusCode = referencesSize ? references->statusCode : res->responseHeader.serviceResult; |
1357 | continuationPoint = res->resultsSize ? &res->results->continuationPoint : nullptr; |
1358 | } |
1359 | |
1360 | convertBrowseResult(src: references, referencesSize, dst&: context.results); |
1361 | |
1362 | if (statusCode == UA_STATUSCODE_GOOD && continuationPoint->length) { |
1363 | UA_BrowseNextRequest request; |
1364 | UA_BrowseNextRequest_init(p: &request); |
1365 | UaDeleter<UA_BrowseNextRequest> requestDeleter(&request, UA_BrowseNextRequest_clear); |
1366 | |
1367 | request.continuationPointsSize = 1; |
1368 | request.continuationPoints = UA_ByteString_new(); |
1369 | UA_ByteString_copy(src: continuationPoint, dst: request.continuationPoints); |
1370 | |
1371 | quint32 requestId = 0; |
1372 | statusCode =__UA_Client_AsyncServiceEx(client, request: &request, requestType: &UA_TYPES[UA_TYPES_BROWSENEXTREQUEST], callback: &asyncBrowseCallback, |
1373 | responseType: &UA_TYPES[UA_TYPES_BROWSENEXTRESPONSE], userdata: backend, requestId: &requestId, timeout: backend->m_asyncRequestTimeout); |
1374 | |
1375 | if (statusCode == UA_STATUSCODE_GOOD) { |
1376 | context.isBrowseNext = true; |
1377 | backend->m_asyncBrowseContext[requestId] = context; |
1378 | return; |
1379 | } |
1380 | } else if (statusCode != UA_STATUSCODE_GOOD) { |
1381 | emit backend->browseFinished(handle: context.handle, children: QList<QOpcUaReferenceDescription>(), |
1382 | statusCode: static_cast<QOpcUa::UaStatusCode>(statusCode)); |
1383 | return; |
1384 | } |
1385 | |
1386 | emit backend->browseFinished(handle: context.handle, children: context.results, statusCode: static_cast<QOpcUa::UaStatusCode>(statusCode)); |
1387 | } |
1388 | |
1389 | void Open62541AsyncBackend::asyncBatchReadCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1390 | { |
1391 | Q_UNUSED(client) |
1392 | |
1393 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1394 | auto context = backend->m_asyncBatchReadContext.take(key: requestId); |
1395 | |
1396 | const auto res = static_cast<UA_ReadResponse *>(response); |
1397 | |
1398 | QOpcUa::UaStatusCode serviceResult = static_cast<QOpcUa::UaStatusCode>(res->responseHeader.serviceResult); |
1399 | |
1400 | if (serviceResult != QOpcUa::UaStatusCode::Good) { |
1401 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch read failed:" << serviceResult; |
1402 | emit backend->readNodeAttributesFinished(results: QList<QOpcUaReadResult>(), serviceResult); |
1403 | } else { |
1404 | QList<QOpcUaReadResult> ret; |
1405 | |
1406 | for (qsizetype i = 0; i < context.nodesToRead.size(); ++i) { |
1407 | QOpcUaReadResult item; |
1408 | item.setAttribute(context.nodesToRead.at(i).attribute()); |
1409 | item.setNodeId(context.nodesToRead.at(i).nodeId()); |
1410 | item.setIndexRange(context.nodesToRead.at(i).indexRange()); |
1411 | if (static_cast<size_t>(i) < res->resultsSize) { |
1412 | if (res->results[i].hasServerTimestamp) |
1413 | item.setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime>(data: &res->results[i].serverTimestamp)); |
1414 | if (res->results[i].hasSourceTimestamp) |
1415 | item.setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime>(data: &res->results[i].sourceTimestamp)); |
1416 | if (res->results[i].hasValue) |
1417 | item.setValue(QOpen62541ValueConverter::toQVariant(res->results[i].value)); |
1418 | if (res->results[i].hasStatus) |
1419 | item.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res->results[i].status)); |
1420 | else |
1421 | item.setStatusCode(serviceResult); |
1422 | } else { |
1423 | item.setStatusCode(serviceResult); |
1424 | } |
1425 | ret.push_back(t: item); |
1426 | } |
1427 | emit backend->readNodeAttributesFinished(results: ret, serviceResult); |
1428 | } |
1429 | } |
1430 | |
1431 | void Open62541AsyncBackend::asyncBatchWriteCallback(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1432 | { |
1433 | Q_UNUSED(client) |
1434 | |
1435 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1436 | const auto context = backend->m_asyncBatchWriteContext.take(key: requestId); |
1437 | |
1438 | const auto res = static_cast<UA_WriteResponse *>(response); |
1439 | |
1440 | QOpcUa::UaStatusCode serviceResult = QOpcUa::UaStatusCode(res->responseHeader.serviceResult); |
1441 | |
1442 | if (serviceResult != QOpcUa::UaStatusCode::Good) { |
1443 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Batch write failed:" << serviceResult; |
1444 | emit backend->writeNodeAttributesFinished(results: QList<QOpcUaWriteResult>(), serviceResult); |
1445 | } else { |
1446 | QList<QOpcUaWriteResult> ret; |
1447 | |
1448 | for (qsizetype i = 0; i < context.nodesToWrite.size(); ++i) { |
1449 | QOpcUaWriteResult item; |
1450 | item.setAttribute(context.nodesToWrite.at(i).attribute()); |
1451 | item.setNodeId(context.nodesToWrite.at(i).nodeId()); |
1452 | item.setIndexRange(context.nodesToWrite.at(i).indexRange()); |
1453 | if (static_cast<size_t>(i) < res->resultsSize) |
1454 | item.setStatusCode(QOpcUa::UaStatusCode(res->results[i])); |
1455 | else |
1456 | item.setStatusCode(serviceResult); |
1457 | ret.push_back(t: item); |
1458 | } |
1459 | emit backend->writeNodeAttributesFinished(results: ret, serviceResult); |
1460 | } |
1461 | } |
1462 | |
1463 | void Open62541AsyncBackend::asyncReadHistoryDataCallBack(UA_Client *client, void *userdata, UA_UInt32 requestId, void *response) |
1464 | { |
1465 | Q_UNUSED(client); |
1466 | |
1467 | Open62541AsyncBackend *backend = static_cast<Open62541AsyncBackend *>(userdata); |
1468 | AsyncReadHistoryDataContext context = backend->m_asyncReadHistoryDataContext.take(key: requestId); |
1469 | |
1470 | UA_HistoryReadResponse* res = static_cast<UA_HistoryReadResponse*>(response); |
1471 | |
1472 | QList<QByteArray> continuationPoints; |
1473 | |
1474 | QList<QOpcUaHistoryData> historyData; |
1475 | |
1476 | for (size_t i = 0; i < res->resultsSize; ++i) { |
1477 | if (res->results[i].historyData.encoding != UA_EXTENSIONOBJECT_DECODED) { |
1478 | emit backend->historyDataAvailable(data: {}, continuationPoints: {}, serviceResult: QOpcUa::UaStatusCode(res->responseHeader.serviceResult), handle: context.handle); |
1479 | return; |
1480 | } |
1481 | |
1482 | historyData.push_back(t: QOpcUaHistoryData(context.historyReadRawRequest.nodesToRead().at(i).nodeId())); |
1483 | |
1484 | historyData[i].setStatusCode(QOpcUa::UaStatusCode(res->results[i].statusCode)); |
1485 | |
1486 | if (res->results[i].statusCode != UA_STATUSCODE_GOOD) |
1487 | continue; |
1488 | |
1489 | if (res->results[i].historyData.content.decoded.type != &UA_TYPES[UA_TYPES_HISTORYDATA]) { |
1490 | historyData[i].setStatusCode(QOpcUa::UaStatusCode::BadInternalError); |
1491 | continue; |
1492 | } |
1493 | |
1494 | UA_HistoryData *data = static_cast<UA_HistoryData *>(res->results[i].historyData.content.decoded.data); |
1495 | for (size_t j = 0; j < data->dataValuesSize; ++j) { |
1496 | const QOpcUaDataValue value = QOpen62541ValueConverter::scalarToQt<QOpcUaDataValue, UA_DataValue>(data: &data->dataValues[j]); |
1497 | historyData[i].addValue(value); |
1498 | } |
1499 | |
1500 | continuationPoints.push_back(t: QOpen62541ValueConverter::scalarToQt<QByteArray, UA_ByteString>(data: &res->results[i].continuationPoint)); |
1501 | } |
1502 | |
1503 | emit backend->historyDataAvailable(data: historyData, continuationPoints, serviceResult: QOpcUa::UaStatusCode(res->responseHeader.serviceResult), handle: context.handle); |
1504 | } |
1505 | |
1506 | bool Open62541AsyncBackend::loadFileToByteString(const QString &location, UA_ByteString *target) const |
1507 | { |
1508 | if (location.isEmpty()) { |
1509 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to read from empty file path" ; |
1510 | return false; |
1511 | } |
1512 | |
1513 | if (!target) { |
1514 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "No target ByteString given" ; |
1515 | return false; |
1516 | } |
1517 | |
1518 | UA_ByteString_init(p: target); |
1519 | |
1520 | QFile file(location); |
1521 | |
1522 | if (!file.open(flags: QFile::ReadOnly)) { |
1523 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to open file" << location << file.errorString(); |
1524 | return false; |
1525 | } |
1526 | |
1527 | QByteArray data = file.readAll(); |
1528 | |
1529 | UA_ByteString temp; |
1530 | temp.length = data.size(); |
1531 | if (data.isEmpty()) |
1532 | temp.data = nullptr; |
1533 | else { |
1534 | if (data.startsWith(c: '-')) { // PEM file |
1535 | // mbedTLS expects PEM encoded data to be null terminated |
1536 | data = data.append(c: '\0'); |
1537 | temp.length = data.size(); |
1538 | } |
1539 | temp.data = reinterpret_cast<unsigned char *>(data.data()); |
1540 | } |
1541 | |
1542 | return UA_ByteString_copy(src: &temp, dst: target) == UA_STATUSCODE_GOOD; |
1543 | } |
1544 | |
1545 | bool Open62541AsyncBackend::loadAllFilesInDirectory(const QString &location, UA_ByteString **target, qsizetype *size) const |
1546 | { |
1547 | if (location.isEmpty()) { |
1548 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to read from empty file path" ; |
1549 | return false; |
1550 | } |
1551 | |
1552 | if (!target) { |
1553 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "No target ByteString given" ; |
1554 | return false; |
1555 | } |
1556 | |
1557 | *target = nullptr; |
1558 | *size = 0; |
1559 | |
1560 | QDir dir(location); |
1561 | |
1562 | auto entries = dir.entryList(filters: QDir::Files); |
1563 | |
1564 | if (entries.isEmpty()) { |
1565 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Directory is empty" ; |
1566 | return true; |
1567 | } |
1568 | |
1569 | const qsizetype tempSize = entries.size(); |
1570 | UA_ByteString *list = static_cast<UA_ByteString *>(UA_Array_new(size: tempSize, type: &UA_TYPES[UA_TYPES_BYTESTRING])); |
1571 | |
1572 | if (!list) { |
1573 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to allocate memory for loading files in" << location; |
1574 | return false; |
1575 | } |
1576 | |
1577 | for (qsizetype i = 0; i < entries.size(); ++i) { |
1578 | if (!loadFileToByteString(location: dir.filePath(fileName: entries.at(i)), target: &list[i])) { |
1579 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to open file" << entries.at(i); |
1580 | UA_Array_delete(p: list, size: tempSize, type: &UA_TYPES[UA_TYPES_BYTESTRING]); |
1581 | size = 0; |
1582 | *target = nullptr; |
1583 | return false; |
1584 | } |
1585 | } |
1586 | |
1587 | *target = list; |
1588 | *size = tempSize; |
1589 | |
1590 | return true; |
1591 | } |
1592 | |
1593 | void Open62541AsyncBackend::disconnectInternal(QOpcUaClient::ClientError error) |
1594 | { |
1595 | m_useStateCallback = false; |
1596 | m_clientIterateTimer.stop(); |
1597 | cleanupSubscriptions(); |
1598 | |
1599 | if (m_uaclient) { |
1600 | UA_Client_disconnect(client: m_uaclient); |
1601 | UA_Client_delete(client: m_uaclient); |
1602 | m_uaclient = nullptr; |
1603 | emit stateAndOrErrorChanged(state: QOpcUaClient::Disconnected, error); |
1604 | } |
1605 | } |
1606 | |
1607 | UA_ExtensionObject Open62541AsyncBackend::assembleNodeAttributes(const QOpcUaNodeCreationAttributes &nodeAttributes, |
1608 | QOpcUa::NodeClass nodeClass) |
1609 | { |
1610 | UA_ExtensionObject obj; |
1611 | UA_ExtensionObject_init(p: &obj); |
1612 | obj.encoding = UA_EXTENSIONOBJECT_DECODED; |
1613 | |
1614 | switch (nodeClass) { |
1615 | case QOpcUa::NodeClass::Object: { |
1616 | UA_ObjectAttributes *attr = UA_ObjectAttributes_new(); |
1617 | *attr = UA_ObjectAttributes_default; |
1618 | obj.content.decoded.data = attr; |
1619 | obj.content.decoded.type = &UA_TYPES[UA_TYPES_OBJECTATTRIBUTES]; |
1620 | |
1621 | if (nodeAttributes.hasEventNotifier()) { |
1622 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_EVENTNOTIFIER; |
1623 | attr->eventNotifier = nodeAttributes.eventNotifier(); |
1624 | } |
1625 | break; |
1626 | } |
1627 | case QOpcUa::NodeClass::Variable: { |
1628 | UA_VariableAttributes *attr = UA_VariableAttributes_new(); |
1629 | *attr = UA_VariableAttributes_default; |
1630 | obj.content.decoded.data = attr; |
1631 | obj.content.decoded.type = &UA_TYPES[UA_TYPES_VARIABLEATTRIBUTES]; |
1632 | |
1633 | if (nodeAttributes.hasValue()) { |
1634 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_VALUE; |
1635 | attr->value = QOpen62541ValueConverter::toOpen62541Variant(nodeAttributes.value(), |
1636 | nodeAttributes.valueType()); |
1637 | } |
1638 | if (nodeAttributes.hasDataTypeId()) { |
1639 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_DATATYPE; |
1640 | attr->dataType = Open62541Utils::nodeIdFromQString(name: nodeAttributes.dataTypeId()); |
1641 | } |
1642 | if (nodeAttributes.hasValueRank()) { |
1643 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_VALUERANK; |
1644 | attr->valueRank = nodeAttributes.valueRank(); |
1645 | } |
1646 | if (nodeAttributes.hasArrayDimensions()) { |
1647 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ARRAYDIMENSIONS; |
1648 | attr->arrayDimensions = copyArrayDimensions(arrayDimensions: nodeAttributes.arrayDimensions(), outputSize: &attr->arrayDimensionsSize); |
1649 | } |
1650 | if (nodeAttributes.hasAccessLevel()) { |
1651 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ACCESSLEVEL; |
1652 | attr->accessLevel = nodeAttributes.accessLevel(); |
1653 | } |
1654 | if (nodeAttributes.hasUserAccessLevel()) { |
1655 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_USERACCESSLEVEL; |
1656 | attr->userAccessLevel = nodeAttributes.userAccessLevel(); |
1657 | } |
1658 | if (nodeAttributes.hasMinimumSamplingInterval()) { |
1659 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_MINIMUMSAMPLINGINTERVAL; |
1660 | attr->minimumSamplingInterval = nodeAttributes.minimumSamplingInterval(); |
1661 | } |
1662 | if (nodeAttributes.hasHistorizing()) { |
1663 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_HISTORIZING; |
1664 | attr->historizing = nodeAttributes.historizing(); |
1665 | } |
1666 | break; |
1667 | } |
1668 | case QOpcUa::NodeClass::Method: { |
1669 | UA_MethodAttributes *attr = UA_MethodAttributes_new(); |
1670 | *attr = UA_MethodAttributes_default; |
1671 | obj.content.decoded.data = attr; |
1672 | obj.content.decoded.type = &UA_TYPES[UA_TYPES_METHODATTRIBUTES]; |
1673 | |
1674 | if (nodeAttributes.hasExecutable()) { |
1675 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_EXECUTABLE; |
1676 | attr->executable = nodeAttributes.executable(); |
1677 | } |
1678 | if (nodeAttributes.hasUserExecutable()) { |
1679 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_USEREXECUTABLE; |
1680 | attr->userExecutable = nodeAttributes.userExecutable(); |
1681 | } |
1682 | break; |
1683 | } |
1684 | case QOpcUa::NodeClass::ObjectType: { |
1685 | UA_ObjectTypeAttributes *attr = UA_ObjectTypeAttributes_new(); |
1686 | *attr = UA_ObjectTypeAttributes_default; |
1687 | obj.content.decoded.data = attr; |
1688 | obj.content.decoded.type = &UA_TYPES[UA_TYPES_OBJECTTYPEATTRIBUTES]; |
1689 | |
1690 | if (nodeAttributes.hasIsAbstract()) { |
1691 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ISABSTRACT; |
1692 | attr->isAbstract = nodeAttributes.isAbstract(); |
1693 | } |
1694 | break; |
1695 | } |
1696 | case QOpcUa::NodeClass::VariableType: { |
1697 | UA_VariableTypeAttributes *attr = UA_VariableTypeAttributes_new(); |
1698 | *attr = UA_VariableTypeAttributes_default; |
1699 | obj.content.decoded.data = attr; |
1700 | obj.content.decoded.type = &UA_TYPES[UA_TYPES_VARIABLETYPEATTRIBUTES]; |
1701 | |
1702 | if (nodeAttributes.hasValue()) { |
1703 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_VALUE; |
1704 | attr->value = QOpen62541ValueConverter::toOpen62541Variant(nodeAttributes.value(), |
1705 | nodeAttributes.valueType()); |
1706 | } |
1707 | if (nodeAttributes.hasDataTypeId()) { |
1708 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_DATATYPE; |
1709 | attr->dataType = Open62541Utils::nodeIdFromQString(name: nodeAttributes.dataTypeId()); |
1710 | } |
1711 | if (nodeAttributes.hasValueRank()) { |
1712 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_VALUERANK; |
1713 | attr->valueRank = nodeAttributes.valueRank(); |
1714 | } |
1715 | if (nodeAttributes.hasArrayDimensions()) { |
1716 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ARRAYDIMENSIONS; |
1717 | attr->arrayDimensions = copyArrayDimensions(arrayDimensions: nodeAttributes.arrayDimensions(), outputSize: &attr->arrayDimensionsSize); |
1718 | } |
1719 | if (nodeAttributes.hasIsAbstract()) { |
1720 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ISABSTRACT; |
1721 | attr->isAbstract = nodeAttributes.isAbstract(); |
1722 | } |
1723 | break; |
1724 | } |
1725 | case QOpcUa::NodeClass::ReferenceType: { |
1726 | UA_ReferenceTypeAttributes *attr = UA_ReferenceTypeAttributes_new(); |
1727 | *attr = UA_ReferenceTypeAttributes_default; |
1728 | obj.content.decoded.data = attr; |
1729 | obj.content.decoded.type = &UA_TYPES[UA_TYPES_REFERENCETYPEATTRIBUTES]; |
1730 | |
1731 | if (nodeAttributes.hasIsAbstract()) { |
1732 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ISABSTRACT; |
1733 | attr->isAbstract = nodeAttributes.isAbstract(); |
1734 | } |
1735 | if (nodeAttributes.hasSymmetric()) { |
1736 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_SYMMETRIC; |
1737 | attr->symmetric = nodeAttributes.symmetric(); |
1738 | } |
1739 | if (nodeAttributes.hasInverseName()) { |
1740 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_INVERSENAME; |
1741 | QOpen62541ValueConverter::scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>( |
1742 | var: nodeAttributes.inverseName(), ptr: &attr->inverseName); |
1743 | } |
1744 | break; |
1745 | } |
1746 | case QOpcUa::NodeClass::DataType: { |
1747 | UA_DataTypeAttributes *attr = UA_DataTypeAttributes_new(); |
1748 | *attr = UA_DataTypeAttributes_default; |
1749 | obj.content.decoded.data = attr; |
1750 | obj.content.decoded.type = &UA_TYPES[UA_TYPES_DATATYPEATTRIBUTES]; |
1751 | |
1752 | if (nodeAttributes.hasIsAbstract()) { |
1753 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_ISABSTRACT; |
1754 | attr->isAbstract = nodeAttributes.isAbstract(); |
1755 | } |
1756 | break; |
1757 | } |
1758 | case QOpcUa::NodeClass::View: { |
1759 | UA_ViewAttributes *attr = UA_ViewAttributes_new(); |
1760 | *attr = UA_ViewAttributes_default; |
1761 | obj.content.decoded.data = attr; |
1762 | obj.content.decoded.type = &UA_TYPES[UA_TYPES_VIEWATTRIBUTES]; |
1763 | |
1764 | if (nodeAttributes.hasContainsNoLoops()) { |
1765 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_CONTAINSNOLOOPS; |
1766 | attr->containsNoLoops = nodeAttributes.containsNoLoops(); |
1767 | } |
1768 | if (nodeAttributes.hasEventNotifier()) { |
1769 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_EVENTNOTIFIER; |
1770 | attr->eventNotifier = nodeAttributes.eventNotifier(); |
1771 | } |
1772 | break; |
1773 | } |
1774 | default: |
1775 | qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Could not convert node attributes, unknown node class" ; |
1776 | UA_ExtensionObject_init(p: &obj); |
1777 | return obj; |
1778 | } |
1779 | |
1780 | UA_ObjectAttributes *attr = reinterpret_cast<UA_ObjectAttributes *>(obj.content.decoded.data); |
1781 | if (nodeAttributes.hasDisplayName()) { |
1782 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_DISPLAYNAME; |
1783 | QOpen62541ValueConverter::scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>( |
1784 | var: nodeAttributes.displayName(), ptr: &attr->displayName); |
1785 | } |
1786 | if (nodeAttributes.hasDescription()) { |
1787 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_DESCRIPTION; |
1788 | QOpen62541ValueConverter::scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>( |
1789 | var: nodeAttributes.description(), ptr: &attr->description); |
1790 | } |
1791 | if (nodeAttributes.hasWriteMask()) { |
1792 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_WRITEMASK; |
1793 | attr->writeMask = nodeAttributes.writeMask(); |
1794 | } |
1795 | if (nodeAttributes.hasUserWriteMask()) { |
1796 | attr->specifiedAttributes |= UA_NODEATTRIBUTESMASK_USERWRITEMASK; |
1797 | attr->userWriteMask = nodeAttributes.userWriteMask(); |
1798 | } |
1799 | |
1800 | return obj; |
1801 | } |
1802 | |
1803 | UA_UInt32 *Open62541AsyncBackend::copyArrayDimensions(const QList<quint32> &arrayDimensions, size_t *outputSize) |
1804 | { |
1805 | if (outputSize) |
1806 | *outputSize = arrayDimensions.size(); |
1807 | |
1808 | if (!outputSize) |
1809 | return nullptr; |
1810 | |
1811 | UA_UInt32 *data = nullptr; |
1812 | UA_StatusCode res = UA_Array_copy(src: arrayDimensions.constData(), size: arrayDimensions.size(), |
1813 | dst: reinterpret_cast<void **>(&data), type: &UA_TYPES[UA_TYPES_UINT32]); |
1814 | return res == UA_STATUSCODE_GOOD ? data : nullptr; |
1815 | } |
1816 | |
1817 | QT_END_NAMESPACE |
1818 | |