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