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 "qopen62541client.h"
6#include "qopen62541node.h"
7#include "qopen62541subscription.h"
8#include "qopen62541utils.h"
9#include "qopen62541valueconverter.h"
10#include "qopen62541utils.h"
11#include <private/qopcuanode_p.h>
12
13#include "qopcuacontentfilterelementresult.h"
14
15#include <QtCore/qloggingcategory.h>
16
17QT_BEGIN_NAMESPACE
18
19Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_OPEN62541)
20
21static void monitoredValueHandler(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext, UA_DataValue *value)
22{
23 Q_UNUSED(client)
24 Q_UNUSED(subId)
25 Q_UNUSED(subContext)
26 QOpen62541Subscription *subscription = static_cast<QOpen62541Subscription *>(monContext);
27 subscription->monitoredValueUpdated(monId, value);
28}
29
30static void stateChangeHandler(UA_Client *client, UA_UInt32 subId, void *subContext, UA_StatusChangeNotification *notification)
31{
32 Q_UNUSED(client);
33 Q_UNUSED(subId);
34
35 if (notification->status != UA_STATUSCODE_BADTIMEOUT)
36 return;
37
38 QOpen62541Subscription *sub = static_cast<QOpen62541Subscription *>(subContext);
39 sub->sendTimeoutNotification();
40}
41
42static void eventHandler(UA_Client *client, UA_UInt32 subId, void *subContext, UA_UInt32 monId, void *monContext,
43 size_t numFields, UA_Variant *eventFields)
44{
45 Q_UNUSED(client);
46 Q_UNUSED(subId);
47 Q_UNUSED(subContext);
48
49 QOpen62541Subscription *subscription = static_cast<QOpen62541Subscription *>(monContext);
50
51 QVariantList list;
52 for (size_t i = 0; i < numFields; ++i)
53 list.append(t: QOpen62541ValueConverter::toQVariant(eventFields[i]));
54 subscription->eventReceived(monId, list);
55}
56
57QOpen62541Subscription::QOpen62541Subscription(Open62541AsyncBackend *backend, const QOpcUaMonitoringParameters &settings)
58 : m_backend(backend)
59 , m_interval(settings.publishingInterval())
60 , m_subscriptionId(0)
61 , m_lifetimeCount(settings.lifetimeCount() ? settings.lifetimeCount() : UA_CreateSubscriptionRequest_default().requestedLifetimeCount)
62 , m_maxKeepaliveCount(settings.maxKeepAliveCount() ? settings.maxKeepAliveCount() : UA_CreateSubscriptionRequest_default().requestedMaxKeepAliveCount)
63 , m_shared(settings.subscriptionType())
64 , m_priority(settings.priority())
65 , m_maxNotificationsPerPublish(settings.maxNotificationsPerPublish())
66 , m_clientHandle(0)
67 , m_timeout(false)
68{
69}
70
71QOpen62541Subscription::~QOpen62541Subscription()
72{
73 removeOnServer();
74}
75
76UA_UInt32 QOpen62541Subscription::createOnServer()
77{
78 UA_CreateSubscriptionRequest req = UA_CreateSubscriptionRequest_default();
79 req.requestedPublishingInterval = m_interval;
80 req.requestedLifetimeCount = m_lifetimeCount;
81 req.requestedMaxKeepAliveCount = m_maxKeepaliveCount;
82 req.priority = m_priority;
83 req.maxNotificationsPerPublish = m_maxNotificationsPerPublish;
84 UA_CreateSubscriptionResponse res = UA_Client_Subscriptions_create(client: m_backend->m_uaclient, request: req, subscriptionContext: this, statusChangeCallback: stateChangeHandler, deleteCallback: nullptr);
85
86 if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
87 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not create subscription with interval" << m_interval << UA_StatusCode_name(code: res.responseHeader.serviceResult);
88 return 0;
89 }
90
91 m_subscriptionId = res.subscriptionId;
92 m_maxKeepaliveCount = res.revisedMaxKeepAliveCount;
93 m_lifetimeCount = res.revisedLifetimeCount;
94 m_interval = res.revisedPublishingInterval;
95 return m_subscriptionId;
96}
97
98bool QOpen62541Subscription::removeOnServer()
99{
100 UA_StatusCode res = UA_STATUSCODE_GOOD;
101 if (m_subscriptionId) {
102 res = UA_Client_Subscriptions_deleteSingle(client: m_backend->m_uaclient, subscriptionId: m_subscriptionId);
103 m_subscriptionId = 0;
104 }
105
106 for (auto it : std::as_const(t&: m_itemIdToItemMapping)) {
107 QOpcUaMonitoringParameters s;
108 s.setStatusCode(m_timeout ? QOpcUa::UaStatusCode::BadTimeout : QOpcUa::UaStatusCode::BadDisconnect);
109 emit m_backend->monitoringEnableDisable(handle: it->handle, attr: it->attr, subscribe: false, status: s);
110 }
111
112 qDeleteAll(c: m_itemIdToItemMapping);
113
114 m_itemIdToItemMapping.clear();
115 m_nodeHandleToItemMapping.clear();
116
117 return (res == UA_STATUSCODE_GOOD) ? true : false;
118}
119
120void QOpen62541Subscription::modifyMonitoring(quint64 handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value)
121{
122 MonitoredItem *monItem = getItemForAttribute(nodeHandle: handle, attr);
123 if (!monItem) {
124 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify parameter" << item << "there are no monitored items";
125 QOpcUaMonitoringParameters p;
126 p.setStatusCode(QOpcUa::UaStatusCode::BadAttributeIdInvalid);
127 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
128 return;
129 }
130
131 QOpcUaMonitoringParameters p = monItem->parameters;
132 p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented);
133
134 // SetPublishingMode service
135 if (item == QOpcUaMonitoringParameters::Parameter::PublishingEnabled) {
136 if (value.metaType().id() != QMetaType::Bool) {
137 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "New value for PublishingEnabled is not a boolean";
138 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
139 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
140 return;
141 }
142
143 UA_SetPublishingModeRequest req;
144 UA_SetPublishingModeRequest_init(p: &req);
145 UaDeleter<UA_SetPublishingModeRequest> requestDeleter(&req, UA_SetPublishingModeRequest_clear);
146 req.publishingEnabled = value.toBool();
147 req.subscriptionIdsSize = 1;
148 req.subscriptionIds = UA_UInt32_new();
149 *req.subscriptionIds = m_subscriptionId;
150 UA_SetPublishingModeResponse res = UA_Client_Subscriptions_setPublishingMode(client: m_backend->m_uaclient, request: req);
151 UaDeleter<UA_SetPublishingModeResponse> responseDeleter(&res, UA_SetPublishingModeResponse_clear);
152
153 if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
154 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to set publishing mode:" << res.responseHeader.serviceResult;
155 p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult));
156 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
157 return;
158 }
159
160 if (res.resultsSize && res.results[0] == UA_STATUSCODE_GOOD)
161 p.setPublishingEnabled(value.toBool());
162
163 p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.results[0]));
164 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
165
166 return;
167 }
168
169 // SetMonitoringMode service
170 if (item == QOpcUaMonitoringParameters::Parameter::MonitoringMode) {
171 if (value.userType() != QMetaType::fromType<QOpcUaMonitoringParameters::MonitoringMode>().id()) {
172 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "New value for MonitoringMode is not a monitoring mode";
173 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
174 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
175 return;
176 }
177
178 UA_SetMonitoringModeRequest req;
179 UA_SetMonitoringModeRequest_init(p: &req);
180 UaDeleter<UA_SetMonitoringModeRequest> requestDeleter(&req, UA_SetMonitoringModeRequest_clear);
181 req.monitoringMode = static_cast<UA_MonitoringMode>(value.value<QOpcUaMonitoringParameters::MonitoringMode>());
182 req.monitoredItemIdsSize = 1;
183 req.monitoredItemIds = UA_UInt32_new();
184 *req.monitoredItemIds = monItem->monitoredItemId;
185 req.subscriptionId = m_subscriptionId;
186 UA_SetMonitoringModeResponse res = UA_Client_MonitoredItems_setMonitoringMode(client: m_backend->m_uaclient, request: req);
187 UaDeleter<UA_SetMonitoringModeResponse> responseDeleter(&res, UA_SetMonitoringModeResponse_clear);
188
189 if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
190 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to set monitoring mode:" << res.responseHeader.serviceResult;
191 p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult));
192 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
193 return;
194 }
195
196 if (res.resultsSize && res.results[0] == UA_STATUSCODE_GOOD)
197 p.setMonitoringMode(value.value<QOpcUaMonitoringParameters::MonitoringMode>());
198
199 p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.results[0]));
200 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
201 return;
202 }
203
204 // SetTriggering service
205 if (item == QOpcUaMonitoringParameters::Parameter::TriggeredItemIds) {
206 if (!value.canConvert<QSet<quint32>>()) {
207 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify triggering item id, value is not a set of quint32";
208 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
209 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
210 return;
211 }
212
213 auto triggeredItemIds = value.value<QSet<quint32>>();
214
215 UA_SetTriggeringRequest triggeringReq;
216 UA_SetTriggeringRequest_init(p: &triggeringReq);
217 triggeringReq.subscriptionId = m_subscriptionId;
218 triggeringReq.triggeringItemId = monItem->monitoredItemId;
219
220 QList<quint32> itemsToRemove;
221 QList<quint32> itemsToAdd;
222
223 if (triggeredItemIds.isEmpty() && !monItem->parameters.triggeredItemIds().isEmpty()) {
224 itemsToRemove = monItem->parameters.triggeredItemIds().values();
225 } else if (!triggeredItemIds.isEmpty()) {
226 itemsToAdd = triggeredItemIds.values();
227 itemsToRemove = monItem->parameters.triggeredItemIds().subtract(other: triggeredItemIds).values();
228 }
229
230 if (itemsToAdd.isEmpty() && itemsToRemove.isEmpty()) {
231 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Nothing to do for TriggeredItemIds";
232 p.setStatusCode(QOpcUa::UaStatusCode::Good);
233 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
234 return;
235 }
236
237 if (!itemsToAdd.isEmpty()) {
238 triggeringReq.linksToAddSize = itemsToAdd.size();
239 triggeringReq.linksToAdd = itemsToAdd.data();
240 }
241
242 if (!itemsToRemove.isEmpty()) {
243 triggeringReq.linksToRemoveSize = itemsToRemove.size();
244 triggeringReq.linksToRemove = itemsToRemove.data();
245 }
246
247 auto triggeringRes = UA_Client_MonitoredItems_setTriggering(client: m_backend->m_uaclient, request: triggeringReq);
248
249 QHash<quint32, QOpcUa::UaStatusCode> failedItems;
250
251 if (triggeringRes.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
252 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Modifying TriggeredItemIds failed with"
253 << UA_StatusCode_name(code: triggeringRes.responseHeader.serviceResult);
254
255 for (const auto &entry : itemsToAdd)
256 failedItems[entry] = QOpcUa::UaStatusCode(triggeringRes.responseHeader.serviceResult);
257 p.setFailedTriggeredItemsStatus(failedItems);
258 p.setStatusCode(QOpcUa::UaStatusCode(triggeringRes.responseHeader.serviceResult));
259 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
260 UA_SetTriggeringResponse_clear(p: &triggeringRes);
261 return;
262 }
263
264 for (size_t i = 0; i < triggeringRes.addResultsSize; ++i) {
265 if (triggeringRes.addResults[i] != UA_STATUSCODE_GOOD) {
266 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add trigger link" << triggeringReq.triggeringItemId
267 << "->" << itemsToAdd.at(i) << "on subscription" << m_subscriptionId
268 << "with status"
269 << UA_StatusCode_name(code: triggeringRes.addResults[i]);
270 failedItems.insert(key: itemsToAdd.at(i), value: QOpcUa::UaStatusCode(triggeringRes.addResults[i]));
271 triggeredItemIds.remove(value: itemsToAdd.at(i));
272 }
273 }
274
275 UA_SetTriggeringResponse_clear(p: &triggeringRes);
276
277 monItem->parameters.setTriggeredItemIds(triggeredItemIds);
278 p.setStatusCode(QOpcUa::UaStatusCode::Good);
279 p.setTriggeredItemIds(triggeredItemIds);
280 p.setFailedTriggeredItemsStatus(failedItems);
281 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
282
283 return;
284 }
285
286 if (modifySubscriptionParameters(nodeHandle: handle, attr, item, value))
287 return;
288 if (modifyMonitoredItemParameters(nodeHandle: handle, attr, item, value))
289 return;
290
291 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Modifying" << item << "is not implemented";
292 p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented);
293 emit m_backend->monitoringStatusChanged(handle, attr, items: item, param: p);
294}
295
296bool QOpen62541Subscription::addAttributeMonitoredItem(quint64 handle, QOpcUa::NodeAttribute attr, const UA_NodeId &id, QOpcUaMonitoringParameters settings)
297{
298 UA_MonitoredItemCreateRequest req;
299 UA_MonitoredItemCreateRequest_init(p: &req);
300 UaDeleter<UA_MonitoredItemCreateRequest> requestDeleter(&req, UA_MonitoredItemCreateRequest_clear);
301 req.itemToMonitor.attributeId = QOpen62541ValueConverter::toUaAttributeId(attr);
302 UA_NodeId_copy(src: &id, dst: &(req.itemToMonitor.nodeId));
303 if (settings.indexRange().size())
304 QOpen62541ValueConverter::scalarFromQt<UA_String, QString>(var: settings.indexRange(), ptr: &req.itemToMonitor.indexRange);
305 req.monitoringMode = static_cast<UA_MonitoringMode>(settings.monitoringMode());
306 req.requestedParameters.samplingInterval = qFuzzyCompare(p1: settings.samplingInterval(), p2: 0.0) ? m_interval : settings.samplingInterval();
307 req.requestedParameters.queueSize = settings.queueSize() == 0 ? 1 : settings.queueSize();
308 req.requestedParameters.discardOldest = settings.discardOldest();
309 req.requestedParameters.clientHandle = ++m_clientHandle;
310
311 if (settings.filter().isValid()) {
312 UA_ExtensionObject filter = createFilter(filterData: settings.filter());
313 if (filter.content.decoded.data)
314 req.requestedParameters.filter = filter;
315 else {
316 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not create monitored item, filter creation failed";
317 QOpcUaMonitoringParameters s;
318 s.setStatusCode(QOpcUa::UaStatusCode::BadInternalError);
319 emit m_backend->monitoringEnableDisable(handle, attr, subscribe: true, status: s);
320 return false;
321 }
322 }
323
324 UA_MonitoredItemCreateResult res;
325 UaDeleter<UA_MonitoredItemCreateResult> resultDeleter(&res, UA_MonitoredItemCreateResult_clear);
326
327 if (attr == QOpcUa::NodeAttribute::EventNotifier && settings.filter().canConvert<QOpcUaMonitoringParameters::EventFilter>())
328 res = UA_Client_MonitoredItems_createEvent(client: m_backend->m_uaclient, subscriptionId: m_subscriptionId,
329 timestampsToReturn: UA_TIMESTAMPSTORETURN_BOTH, item: req, context: this, callback: eventHandler, deleteCallback: nullptr);
330 else
331 res = UA_Client_MonitoredItems_createDataChange(client: m_backend->m_uaclient, subscriptionId: m_subscriptionId, timestampsToReturn: UA_TIMESTAMPSTORETURN_BOTH, item: req, context: this, callback: monitoredValueHandler, deleteCallback: nullptr);
332
333 if (res.statusCode != UA_STATUSCODE_GOOD) {
334 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not add monitored item for" << attr << "of node" << Open62541Utils::nodeIdToQString(id) << ":" << UA_StatusCode_name(code: res.statusCode);
335 QOpcUaMonitoringParameters s;
336 s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.statusCode));
337 emit m_backend->monitoringEnableDisable(handle, attr, subscribe: true, status: s);
338 return false;
339 }
340
341 QSet<quint32> successfulTriggerLinks;
342 QHash<quint32, QOpcUa::UaStatusCode> failedTriggerLinks;
343 if (!settings.triggeredItemIds().isEmpty()) {
344 auto triggeredItems = settings.triggeredItemIds().values();
345
346 UA_SetTriggeringRequest req;
347 UA_SetTriggeringRequest_init(p: &req);
348 req.subscriptionId = m_subscriptionId;
349 req.triggeringItemId = res.monitoredItemId;
350 req.linksToAddSize = triggeredItems.size();
351 req.linksToAdd = triggeredItems.data();
352
353 auto triggeringRes = UA_Client_MonitoredItems_setTriggering(client: m_backend->m_uaclient, request: req);
354
355 if (triggeringRes.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
356 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not set triggering item it for" << attr << "of node" << Open62541Utils::nodeIdToQString(id) << ":"
357 << UA_StatusCode_name(code: triggeringRes.responseHeader.serviceResult);
358
359 // Remove the new monitored item
360 UA_DeleteMonitoredItemsRequest deleteRequest;
361 UA_DeleteMonitoredItemsRequest_init(p: &deleteRequest);
362 deleteRequest.subscriptionId = m_subscriptionId;
363 deleteRequest.monitoredItemIdsSize = 1;
364 deleteRequest.monitoredItemIds = &res.monitoredItemId;
365 UA_Client_MonitoredItems_delete(client: m_backend->m_uaclient, deleteRequest);
366
367 for (const auto &entry : triggeredItems)
368 failedTriggerLinks.insert(key: entry, value: QOpcUa::UaStatusCode(triggeringRes.responseHeader.serviceResult));
369
370 QOpcUaMonitoringParameters s;
371 s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(triggeringRes.responseHeader.serviceResult));
372 s.setFailedTriggeredItemsStatus(failedTriggerLinks);
373 emit m_backend->monitoringEnableDisable(handle, attr, subscribe: true, status: s);
374
375 UA_SetTriggeringResponse_clear(p: &triggeringRes);
376
377 return false;
378 }
379
380 for (size_t i = 0; i < triggeringRes.addResultsSize; ++i) {
381 if (triggeringRes.addResults[i] == UA_STATUSCODE_GOOD) {
382 successfulTriggerLinks.insert(value: triggeredItems.at(i));
383 } else {
384 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add trigger link" << res.monitoredItemId
385 << "->" << triggeredItems.at(i) << "on subscription" << m_subscriptionId
386 << "with status"
387 << UA_StatusCode_name(code: triggeringRes.addResults[i]);
388 failedTriggerLinks.insert(key: triggeredItems.at(i), value: QOpcUa::UaStatusCode(triggeringRes.addResults[i]));
389 }
390 }
391
392 UA_SetTriggeringResponse_clear(p: &triggeringRes);
393 }
394
395 MonitoredItem *temp = new MonitoredItem(handle, attr, res.monitoredItemId);
396 m_nodeHandleToItemMapping[handle][attr] = temp;
397 m_itemIdToItemMapping[res.monitoredItemId] = temp;
398
399 QOpcUaMonitoringParameters s = settings;
400 s.setSubscriptionId(m_subscriptionId);
401 s.setPublishingInterval(m_interval);
402 s.setMaxKeepAliveCount(m_maxKeepaliveCount);
403 s.setLifetimeCount(m_lifetimeCount);
404 s.setStatusCode(QOpcUa::UaStatusCode::Good);
405 s.setSamplingInterval(res.revisedSamplingInterval);
406 s.setQueueSize(res.revisedQueueSize);
407 s.setMonitoredItemId(res.monitoredItemId);
408 s.setTriggeredItemIds(successfulTriggerLinks);
409 s.setFailedTriggeredItemsStatus(failedTriggerLinks);
410 temp->parameters = s;
411 temp->clientHandle = m_clientHandle;
412
413 if (res.filterResult.encoding >= UA_EXTENSIONOBJECT_DECODED &&
414 res.filterResult.content.decoded.type == &UA_TYPES[UA_TYPES_EVENTFILTERRESULT])
415 s.setFilterResult(convertEventFilterResult(obj: &res.filterResult));
416 else
417 s.clearFilterResult();
418
419 emit m_backend->monitoringEnableDisable(handle, attr, subscribe: true, status: s);
420
421 return true;
422}
423
424bool QOpen62541Subscription::removeAttributeMonitoredItem(quint64 handle, QOpcUa::NodeAttribute attr)
425{
426 MonitoredItem *item = getItemForAttribute(nodeHandle: handle, attr);
427 if (!item) {
428 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "There is no monitored item for this attribute";
429 QOpcUaMonitoringParameters s;
430 s.setStatusCode(QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid);
431 emit m_backend->monitoringEnableDisable(handle, attr, subscribe: false, status: s);
432 return false;
433 }
434
435 UA_StatusCode res = UA_Client_MonitoredItems_deleteSingle(client: m_backend->m_uaclient, subscriptionId: m_subscriptionId, monitoredItemId: item->monitoredItemId);
436 if (res != UA_STATUSCODE_GOOD)
437 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not remove monitored item" << item->monitoredItemId << "from subscription" << m_subscriptionId << ":" << UA_StatusCode_name(code: res);
438
439 m_itemIdToItemMapping.remove(key: item->monitoredItemId);
440 const auto it = m_nodeHandleToItemMapping.find(key: handle);
441 it->remove(key: attr);
442 if (it->empty())
443 m_nodeHandleToItemMapping.remove(key: it.key());
444
445 delete item;
446
447 QOpcUaMonitoringParameters s;
448 s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res));
449 emit m_backend->monitoringEnableDisable(handle, attr, subscribe: false, status: s);
450
451 return true;
452}
453
454void QOpen62541Subscription::monitoredValueUpdated(UA_UInt32 monId, UA_DataValue *value)
455{
456 auto item = m_itemIdToItemMapping.constFind(key: monId);
457 if (item == m_itemIdToItemMapping.constEnd())
458 return;
459 QOpcUaReadResult res;
460
461 if (!value || value == UA_EMPTY_ARRAY_SENTINEL) {
462 res.setStatusCode(QOpcUa::UaStatusCode::Good);
463 emit m_backend->dataChangeOccurred(handle: item.value()->handle, res);
464 return;
465 }
466
467 res.setValue(QOpen62541ValueConverter::toQVariant(value->value));
468 res.setAttribute(item.value()->attr);
469 if (value->hasServerTimestamp)
470 res.setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(data: &value->serverTimestamp));
471 if (value->hasSourceTimestamp)
472 res.setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(data: &value->sourceTimestamp));
473 res.setStatusCode(value->hasStatus ? QOpcUa::UaStatusCode(value->status) : QOpcUa::UaStatusCode::Good);
474 emit m_backend->dataChangeOccurred(handle: item.value()->handle, res);
475}
476
477void QOpen62541Subscription::sendTimeoutNotification()
478{
479 QList<QPair<quint64, QOpcUa::NodeAttribute>> items;
480 for (const auto &it : std::as_const(t&: m_nodeHandleToItemMapping)) {
481 for (auto item : it) {
482 items.push_back(t: {item->handle, item->attr});
483 }
484 }
485 emit timeout(sub: this, items);
486 m_timeout = true;
487}
488
489void QOpen62541Subscription::eventReceived(UA_UInt32 monId, QVariantList list)
490{
491 auto item = m_itemIdToItemMapping.constFind(key: monId);
492 if (item == m_itemIdToItemMapping.constEnd())
493 return;
494 emit m_backend->eventOccurred(handle: item.value()->handle, fields: list);
495}
496
497double QOpen62541Subscription::interval() const
498{
499 return m_interval;
500}
501
502UA_UInt32 QOpen62541Subscription::subscriptionId() const
503{
504 return m_subscriptionId;
505}
506
507int QOpen62541Subscription::monitoredItemsCount() const
508{
509 return m_itemIdToItemMapping.size();
510}
511
512QOpcUaMonitoringParameters::SubscriptionType QOpen62541Subscription::shared() const
513{
514 return m_shared;
515}
516
517QOpen62541Subscription::MonitoredItem *QOpen62541Subscription::getItemForAttribute(quint64 nodeHandle, QOpcUa::NodeAttribute attr)
518{
519 auto nodeEntry = m_nodeHandleToItemMapping.constFind(key: nodeHandle);
520
521 if (nodeEntry == m_nodeHandleToItemMapping.constEnd())
522 return nullptr;
523
524 auto item = nodeEntry->constFind(key: attr);
525 if (item == nodeEntry->constEnd())
526 return nullptr;
527
528 return item.value();
529}
530
531UA_ExtensionObject QOpen62541Subscription::createFilter(const QVariant &filterData)
532{
533 UA_ExtensionObject obj;
534 UA_ExtensionObject_init(p: &obj);
535
536 if (filterData.canConvert<QOpcUaMonitoringParameters::DataChangeFilter>()) {
537 createDataChangeFilter(filter: filterData.value<QOpcUaMonitoringParameters::DataChangeFilter>(), out: &obj);
538 return obj;
539 }
540
541 if (filterData.canConvert<QOpcUaMonitoringParameters::EventFilter>()) {
542 Open62541Utils::createEventFilter(filter: filterData.value<QOpcUaMonitoringParameters::EventFilter>(), out: &obj);
543 return obj;
544 }
545
546 if (filterData.isValid())
547 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not create filter, invalid input.";
548
549 return obj;
550}
551
552void QOpen62541Subscription::createDataChangeFilter(const QOpcUaMonitoringParameters::DataChangeFilter &filter, UA_ExtensionObject *out)
553{
554 UA_DataChangeFilter *uaFilter = UA_DataChangeFilter_new();
555 uaFilter->deadbandType = static_cast<UA_UInt32>(filter.deadbandType());
556 uaFilter->deadbandValue = filter.deadbandValue();
557 uaFilter->trigger = static_cast<UA_DataChangeTrigger>(filter.trigger());
558 out->encoding = UA_EXTENSIONOBJECT_DECODED;
559 out->content.decoded.type = &UA_TYPES[UA_TYPES_DATACHANGEFILTER];
560 out->content.decoded.data = uaFilter;
561}
562
563QOpcUaEventFilterResult QOpen62541Subscription::convertEventFilterResult(UA_ExtensionObject *obj)
564{
565 QOpcUaEventFilterResult result;
566
567 if (!obj)
568 return result;
569
570 if (obj->encoding == UA_EXTENSIONOBJECT_DECODED && obj->content.decoded.type == &UA_TYPES[UA_TYPES_EVENTFILTERRESULT]) {
571 UA_EventFilterResult *filterResult = static_cast<UA_EventFilterResult *>(obj->content.decoded.data);
572
573 for (size_t i = 0; i < filterResult->selectClauseResultsSize; ++i)
574 result.selectClauseResultsRef().append(t: static_cast<QOpcUa::UaStatusCode>(filterResult->selectClauseResults[i]));
575
576 for (size_t i = 0; i < filterResult->whereClauseResult.elementResultsSize; ++i) {
577 QOpcUaContentFilterElementResult temp;
578 temp.setStatusCode(static_cast<QOpcUa::UaStatusCode>(filterResult->whereClauseResult.elementResults[i].statusCode));
579 for (size_t j = 0; j < filterResult->whereClauseResult.elementResults[i].operandStatusCodesSize; ++j)
580 temp.operandStatusCodesRef().append(t: static_cast<QOpcUa::UaStatusCode>(
581 filterResult->whereClauseResult.elementResults[i].operandStatusCodes[j]));
582 result.whereClauseResultsRef().append(t: temp);
583 }
584 }
585
586 return result;
587}
588
589bool QOpen62541Subscription::modifySubscriptionParameters(quint64 nodeHandle, QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::Parameter &item, const QVariant &value)
590{
591 UA_ModifySubscriptionRequest req;
592 UA_ModifySubscriptionRequest_init(p: &req);
593 req.subscriptionId = m_subscriptionId;
594 req.requestedPublishingInterval = m_interval;
595 req.requestedLifetimeCount = m_lifetimeCount;
596 req.requestedMaxKeepAliveCount = m_maxKeepaliveCount;
597 req.maxNotificationsPerPublish = m_maxNotificationsPerPublish;
598
599 bool match = true;
600
601 switch (item) {
602 case QOpcUaMonitoringParameters::Parameter::PublishingInterval: {
603 bool ok;
604 req.requestedPublishingInterval = value.toDouble(ok: &ok);
605 if (!ok) {
606 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify PublishingInterval, value is not a double";
607 QOpcUaMonitoringParameters p;
608 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
609 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
610 return true;
611 }
612 break;
613 }
614 case QOpcUaMonitoringParameters::Parameter::LifetimeCount: {
615 bool ok;
616 req.requestedLifetimeCount = value.toUInt(ok: &ok);
617 if (!ok) {
618 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify LifetimeCount, value is not an integer";
619 QOpcUaMonitoringParameters p;
620 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
621 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
622 return true;
623 }
624 break;
625 }
626 case QOpcUaMonitoringParameters::Parameter::MaxKeepAliveCount: {
627 bool ok;
628 req.requestedMaxKeepAliveCount = value.toUInt(ok: &ok);
629 if (!ok) {
630 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify MaxKeepAliveCount, value is not an integer";
631 QOpcUaMonitoringParameters p;
632 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
633 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
634 return true;
635 }
636 break;
637 }
638 case QOpcUaMonitoringParameters::Parameter::Priority: {
639 bool ok;
640 req.priority = value.toUInt(ok: &ok);
641 if (!ok) {
642 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify Priority, value is not an integer";
643 QOpcUaMonitoringParameters p;
644 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
645 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
646 return true;
647 }
648 break;
649 }
650 case QOpcUaMonitoringParameters::Parameter::MaxNotificationsPerPublish: {
651 bool ok;
652 req.maxNotificationsPerPublish = value.toUInt(ok: &ok);
653 if (!ok) {
654 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify MaxNotificationsPerPublish, value is not an integer";
655 QOpcUaMonitoringParameters p;
656 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
657 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
658 return true;
659 }
660 break;
661 }
662 default:
663 match = false;
664 break;
665 }
666
667 if (match) {
668 UA_ModifySubscriptionResponse res = UA_Client_Subscriptions_modify(client: m_backend->m_uaclient, request: req);
669
670 if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
671 QOpcUaMonitoringParameters p;
672 p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult));
673 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
674 } else {
675 QOpcUaMonitoringParameters::Parameters changed = item;
676 if (!qFuzzyCompare(p1: res.revisedPublishingInterval, p2: m_interval))
677 changed |= QOpcUaMonitoringParameters::Parameter::PublishingInterval;
678 if (res.revisedLifetimeCount != m_lifetimeCount)
679 changed |= QOpcUaMonitoringParameters::Parameter::LifetimeCount;
680 if (res.revisedMaxKeepAliveCount != m_maxKeepaliveCount)
681 changed |= QOpcUaMonitoringParameters::Parameter::MaxKeepAliveCount;
682
683 m_lifetimeCount = res.revisedLifetimeCount;
684 m_maxKeepaliveCount = res.revisedMaxKeepAliveCount;
685 m_interval = res.revisedPublishingInterval;
686 if (item == QOpcUaMonitoringParameters::Parameter::Priority)
687 m_priority = value.toUInt();
688 if (item == QOpcUaMonitoringParameters::Parameter::MaxNotificationsPerPublish)
689 m_maxNotificationsPerPublish = value.toUInt();
690
691 QOpcUaMonitoringParameters p;
692 p.setStatusCode(QOpcUa::UaStatusCode::Good);
693 p.setPublishingInterval(m_interval);
694 p.setLifetimeCount(m_lifetimeCount);
695 p.setMaxKeepAliveCount(m_maxKeepaliveCount);
696 p.setPriority(m_priority);
697 p.setMaxNotificationsPerPublish(m_maxNotificationsPerPublish);
698
699 for (auto it : std::as_const(t&: m_itemIdToItemMapping))
700 emit m_backend->monitoringStatusChanged(handle: it->handle, attr: it->attr, items: changed, param: p);
701 }
702 return true;
703 }
704 return false;
705}
706
707bool QOpen62541Subscription::modifyMonitoredItemParameters(quint64 nodeHandle, QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::Parameter &item, const QVariant &value)
708{
709 MonitoredItem *monItem = getItemForAttribute(nodeHandle, attr);
710 QOpcUaMonitoringParameters p = monItem->parameters;
711
712 UA_ModifyMonitoredItemsRequest req;
713 UA_ModifyMonitoredItemsRequest_init(p: &req);
714 UaDeleter<UA_ModifyMonitoredItemsRequest> requestDeleter(&req, UA_ModifyMonitoredItemsRequest_clear);
715 req.subscriptionId = m_subscriptionId;
716 req.itemsToModifySize = 1;
717 req.itemsToModify = UA_MonitoredItemModifyRequest_new();
718 UA_MonitoredItemModifyRequest_init(p: req.itemsToModify);
719 req.itemsToModify->monitoredItemId = monItem->monitoredItemId;
720 req.itemsToModify->requestedParameters.discardOldest = monItem->parameters.discardOldest();
721 req.itemsToModify->requestedParameters.queueSize = monItem->parameters.queueSize();
722 req.itemsToModify->requestedParameters.samplingInterval = monItem->parameters.samplingInterval();
723 req.itemsToModify->monitoredItemId = monItem->monitoredItemId;
724 req.itemsToModify->requestedParameters.clientHandle = monItem->clientHandle;
725
726 bool match = true;
727
728 switch (item) {
729 case QOpcUaMonitoringParameters::Parameter::DiscardOldest: {
730 if (value.metaType().id() != QMetaType::Bool) {
731 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify DiscardOldest, value is not a bool";
732 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
733 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
734 return true;
735 }
736 req.itemsToModify->requestedParameters.discardOldest = value.toBool();
737 break;
738 }
739 case QOpcUaMonitoringParameters::Parameter::QueueSize: {
740 if (value.metaType().id() != QMetaType::UInt) {
741 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify QueueSize, value is not an integer";
742 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
743 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
744 return true;
745 }
746 req.itemsToModify->requestedParameters.queueSize = value.toUInt();
747 break;
748 }
749 case QOpcUaMonitoringParameters::Parameter::SamplingInterval: {
750 if (value.metaType().id() != QMetaType::Double) {
751 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify SamplingInterval, value is not a double";
752 p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
753 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
754 return true;
755 }
756 req.itemsToModify->requestedParameters.samplingInterval = value.toDouble();
757 break;
758 }
759 case QOpcUaMonitoringParameters::Parameter::Filter: {
760 UA_ExtensionObject filter = createFilter(filterData: value);
761 if (filter.content.decoded.data)
762 req.itemsToModify->requestedParameters.filter = filter;
763 else {
764 qCDebug(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to modify filter, filter creation failed";
765 p.setStatusCode(QOpcUa::UaStatusCode::BadInternalError);
766 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
767 return true;
768 }
769 break;
770 }
771 default:
772 match = false;
773 break;
774 }
775
776 if (match) {
777 if (item != QOpcUaMonitoringParameters::Parameter::Filter && p.filter().isValid()) {
778 UA_ExtensionObject filter = createFilter(filterData: monItem->parameters.filter());
779 if (filter.content.decoded.data)
780 req.itemsToModify->requestedParameters.filter = filter;
781 else {
782 qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify monitored item, filter creation failed";
783 p.setStatusCode(QOpcUa::UaStatusCode::BadInternalError);
784 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
785 return true;
786 }
787 }
788
789 UA_ModifyMonitoredItemsResponse res = UA_Client_MonitoredItems_modify(client: m_backend->m_uaclient, request: req);
790 UaDeleter<UA_ModifyMonitoredItemsResponse> responseDeleter(
791 &res, UA_ModifyMonitoredItemsResponse_clear);
792
793 if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD || res.results[0].statusCode != UA_STATUSCODE_GOOD) {
794 p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult == UA_STATUSCODE_GOOD ? res.results[0].statusCode : res.responseHeader.serviceResult));
795 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: item, param: p);
796 return true;
797 } else {
798 p.setStatusCode(QOpcUa::UaStatusCode::Good);
799 QOpcUaMonitoringParameters::Parameters changed = item;
800 if (!qFuzzyCompare(p1: p.samplingInterval(), p2: res.results[0].revisedSamplingInterval)) {
801 p.setSamplingInterval(res.results[0].revisedSamplingInterval);
802 changed |= QOpcUaMonitoringParameters::Parameter::SamplingInterval;
803 }
804 if (p.queueSize() != res.results[0].revisedQueueSize) {
805 p.setQueueSize(res.results[0].revisedQueueSize);
806 changed |= QOpcUaMonitoringParameters::Parameter::QueueSize;
807 }
808
809 if (item == QOpcUaMonitoringParameters::Parameter::DiscardOldest) {
810 p.setDiscardOldest(value.toBool());
811 changed |= QOpcUaMonitoringParameters::Parameter::DiscardOldest;
812 }
813
814 if (item == QOpcUaMonitoringParameters::Parameter::Filter) {
815 changed |= QOpcUaMonitoringParameters::Parameter::Filter;
816 if (value.canConvert<QOpcUaMonitoringParameters::DataChangeFilter>())
817 p.setFilter(value.value<QOpcUaMonitoringParameters::DataChangeFilter>());
818 else if (value.canConvert<QOpcUaMonitoringParameters::EventFilter>())
819 p.setFilter(value.value<QOpcUaMonitoringParameters::EventFilter>());
820 if (res.results[0].filterResult.content.decoded.type == &UA_TYPES[UA_TYPES_EVENTFILTERRESULT])
821 p.setFilterResult(convertEventFilterResult(obj: &res.results[0].filterResult));
822 }
823
824 emit m_backend->monitoringStatusChanged(handle: nodeHandle, attr, items: changed, param: p);
825
826 monItem->parameters = p;
827 }
828 return true;
829 }
830 return false;
831}
832
833QT_END_NAMESPACE
834

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtopcua/src/plugins/opcua/open62541/qopen62541subscription.cpp