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

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