summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJannis Voelker <jannis.voelker@basyskom.com>2023-10-24 15:32:47 +0200
committerJannis Voelker <jannis.voelker@basyskom.com>2023-12-07 14:42:21 +0100
commitc23cccdd17e0529946f0e923a85378e070817c93 (patch)
tree924577b16ef9f79d9cf76862322216b1151fdc18
parentbba934aebc0cc37f012cea446258ec9b2cc106c9 (diff)
Implement triggering for monitored items
This change adds an API to create and remove a triggering link between two monitored items on the same subscription. [ChangeLog][Qt OPC UA] QOpcUaNode now supports SetTriggering to set up a triggering link between two monitored items. Change-Id: Ieea1ccf78d8f71d7bde4cd1487d5926ec6ae988c Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Frank Meerkoetter <frank.meerkoetter@basyskom.com>
-rw-r--r--src/opcua/client/qopcuamonitoringparameters.cpp54
-rw-r--r--src/opcua/client/qopcuamonitoringparameters.h8
-rw-r--r--src/opcua/client/qopcuamonitoringparameters_p.h2
-rw-r--r--src/opcua/client/qopcuanode_p.h4
-rw-r--r--src/plugins/opcua/open62541/qopen62541subscription.cpp138
-rw-r--r--tests/auto/qopcuaclient/tst_client.cpp168
6 files changed, 373 insertions, 1 deletions
diff --git a/src/opcua/client/qopcuamonitoringparameters.cpp b/src/opcua/client/qopcuamonitoringparameters.cpp
index 3480a6f..2c071b3 100644
--- a/src/opcua/client/qopcuamonitoringparameters.cpp
+++ b/src/opcua/client/qopcuamonitoringparameters.cpp
@@ -86,6 +86,8 @@ QT_BEGIN_NAMESPACE
\row
\li MaxNotificationsPerPublish
\li X
+ \li TriggeringItemId
+ \li X
\endtable
*/
@@ -127,6 +129,7 @@ QT_BEGIN_NAMESPACE
\value QueueSize
\value DiscardOldest
\value MonitoringMode
+ \value TriggeredItemIds
*/
/*!
@@ -205,6 +208,57 @@ void QOpcUaMonitoringParameters::setIndexRange(const QString &indexRange)
}
/*!
+ \since 6.7
+
+ Returns the ids of the monitored items triggerd by this monitored item.
+*/
+QSet<quint32> QOpcUaMonitoringParameters::triggeredItemIds() const
+{
+ return d_ptr->triggeredItemIds;
+}
+
+/*!
+ \since 6.7
+
+ Adds triggering links to all monitored items in \a ids as described in OPC UA 1.05, 5.12.1.6.
+
+ The values in \a ids must be the monitored item ids of other monitored item on the same subscription.
+ If the monitoring mode of these items is set to Sampling, their data change notifications will be
+ delivered to the client whenever this monitoring detects a data change.
+
+ Any ids that could not be added will not be included in the monitoring status but will instead show
+ up in \l failedTriggeredItemsStatus().
+
+ Modifying this setting to an empty set will remove all triggering links.
+*/
+void QOpcUaMonitoringParameters::setTriggeredItemIds(const QSet<quint32> &ids)
+{
+ d_ptr->triggeredItemIds = ids;
+}
+
+/*!
+ \since 6.7
+
+ Returns the status codes for all triggered items from \l setTriggeredItemIds() that could not
+ be successfully added.
+*/
+QHash<quint32, QOpcUa::UaStatusCode> QOpcUaMonitoringParameters::failedTriggeredItemsStatus() const
+{
+ return d_ptr->addTriggeredItemStatus;
+}
+
+/*!
+ \since 6.7
+
+ Sets the status codes for all triggered items that could not be successfully added to \a status.
+ Setting this value as a client has no effect.
+*/
+void QOpcUaMonitoringParameters::setFailedTriggeredItemsStatus(const QHash<quint32, QOpcUa::UaStatusCode> &status)
+{
+ d_ptr->addTriggeredItemStatus = status;
+}
+
+/*!
Returns the status code of the monitored item creation.
*/
QOpcUa::UaStatusCode QOpcUaMonitoringParameters::statusCode() const
diff --git a/src/opcua/client/qopcuamonitoringparameters.h b/src/opcua/client/qopcuamonitoringparameters.h
index 0aed98b..ead74a8 100644
--- a/src/opcua/client/qopcuamonitoringparameters.h
+++ b/src/opcua/client/qopcuamonitoringparameters.h
@@ -7,6 +7,7 @@
#include <QtOpcUa/qopcuacontentfilterelement.h>
#include <QtOpcUa/qopcuasimpleattributeoperand.h>
+#include <QtCore/qset.h>
#include <QtCore/qshareddata.h>
QT_BEGIN_NAMESPACE
@@ -43,7 +44,8 @@ public:
Filter = (1 << 7),
QueueSize = (1 << 8),
DiscardOldest = (1 << 9),
- MonitoringMode = (1 << 10)
+ MonitoringMode = (1 << 10),
+ TriggeredItemIds = (1 << 11)
};
Q_ENUM(Parameter)
Q_DECLARE_FLAGS(Parameters, Parameter)
@@ -154,6 +156,10 @@ public:
void setSubscriptionType(SubscriptionType subscriptionType);
QString indexRange() const;
void setIndexRange(const QString &indexRange);
+ QSet<quint32> triggeredItemIds() const;
+ void setTriggeredItemIds(const QSet<quint32> &id);
+ QHash<quint32, QOpcUa::UaStatusCode> failedTriggeredItemsStatus() const;
+ void setFailedTriggeredItemsStatus(const QHash<quint32, QOpcUa::UaStatusCode> &status);
private:
QSharedDataPointer<QOpcUaMonitoringParametersPrivate> d_ptr;
diff --git a/src/opcua/client/qopcuamonitoringparameters_p.h b/src/opcua/client/qopcuamonitoringparameters_p.h
index 358ef23..20d4ee8 100644
--- a/src/opcua/client/qopcuamonitoringparameters_p.h
+++ b/src/opcua/client/qopcuamonitoringparameters_p.h
@@ -51,6 +51,8 @@ public:
bool discardOldest;
QOpcUaMonitoringParameters::MonitoringMode monitoringMode;
QString indexRange;
+ QSet<quint32> triggeredItemIds;
+ QHash<quint32, QOpcUa::UaStatusCode> addTriggeredItemStatus;
// Subscription
quint32 subscriptionId;
diff --git a/src/opcua/client/qopcuanode_p.h b/src/opcua/client/qopcuanode_p.h
index 135594b..81c88ee 100644
--- a/src/opcua/client/qopcuanode_p.h
+++ b/src/opcua/client/qopcuanode_p.h
@@ -211,6 +211,10 @@ public:
it->setDiscardOldest(param.discardOldest());
if (items & QOpcUaMonitoringParameters::Parameter::MonitoringMode)
it->setMonitoringMode(param.monitoringMode());
+ if (items & QOpcUaMonitoringParameters::Parameter::TriggeredItemIds) {
+ it->setTriggeredItemIds(param.triggeredItemIds());
+ it->setFailedTriggeredItemsStatus(param.failedTriggeredItemsStatus());
+ }
}
Q_Q(QOpcUaNode);
diff --git a/src/plugins/opcua/open62541/qopen62541subscription.cpp b/src/plugins/opcua/open62541/qopen62541subscription.cpp
index 24a1580..0128de3 100644
--- a/src/plugins/opcua/open62541/qopen62541subscription.cpp
+++ b/src/plugins/opcua/open62541/qopen62541subscription.cpp
@@ -201,6 +201,88 @@ void QOpen62541Subscription::modifyMonitoring(quint64 handle, QOpcUa::NodeAttrib
return;
}
+ // SetTriggering service
+ if (item == QOpcUaMonitoringParameters::Parameter::TriggeredItemIds) {
+ if (!value.canConvert<QSet<quint32>>()) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify triggering item id, value is not a set of quint32";
+ p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch);
+ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
+ return;
+ }
+
+ auto triggeredItemIds = value.value<QSet<quint32>>();
+
+ UA_SetTriggeringRequest triggeringReq;
+ UA_SetTriggeringRequest_init(&triggeringReq);
+ triggeringReq.subscriptionId = m_subscriptionId;
+ triggeringReq.triggeringItemId = monItem->monitoredItemId;
+
+ QList<quint32> itemsToRemove;
+ QList<quint32> itemsToAdd;
+
+ if (triggeredItemIds.isEmpty() && !monItem->parameters.triggeredItemIds().isEmpty()) {
+ itemsToRemove = monItem->parameters.triggeredItemIds().values();
+ } else if (!triggeredItemIds.isEmpty()) {
+ itemsToAdd = triggeredItemIds.values();
+ itemsToRemove = monItem->parameters.triggeredItemIds().subtract(triggeredItemIds).values();
+ }
+
+ if (itemsToAdd.isEmpty() && itemsToRemove.isEmpty()) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Nothing to do for TriggeredItemIds";
+ p.setStatusCode(QOpcUa::UaStatusCode::Good);
+ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
+ return;
+ }
+
+ if (!itemsToAdd.isEmpty()) {
+ triggeringReq.linksToAddSize = itemsToAdd.size();
+ triggeringReq.linksToAdd = itemsToAdd.data();
+ }
+
+ if (!itemsToRemove.isEmpty()) {
+ triggeringReq.linksToRemoveSize = itemsToRemove.size();
+ triggeringReq.linksToRemove = itemsToRemove.data();
+ }
+
+ auto triggeringRes = UA_Client_MonitoredItems_setTriggering(m_backend->m_uaclient, triggeringReq);
+
+ QHash<quint32, QOpcUa::UaStatusCode> failedItems;
+
+ if (triggeringRes.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Modifying TriggeredItemIds failed with"
+ << UA_StatusCode_name(triggeringRes.responseHeader.serviceResult);
+
+ for (const auto &entry : itemsToAdd)
+ failedItems[entry] = QOpcUa::UaStatusCode(triggeringRes.responseHeader.serviceResult);
+ p.setFailedTriggeredItemsStatus(failedItems);
+ p.setStatusCode(QOpcUa::UaStatusCode(triggeringRes.responseHeader.serviceResult));
+ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
+ UA_SetTriggeringResponse_clear(&triggeringRes);
+ return;
+ }
+
+ for (size_t i = 0; i < triggeringRes.addResultsSize; ++i) {
+ if (triggeringRes.addResults[i] != UA_STATUSCODE_GOOD) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add trigger link" << triggeringReq.triggeringItemId
+ << "->" << itemsToAdd.at(i) << "on subscription" << m_subscriptionId
+ << "with status"
+ << UA_StatusCode_name(triggeringRes.addResults[i]);
+ failedItems.insert(itemsToAdd.at(i), QOpcUa::UaStatusCode(triggeringRes.addResults[i]));
+ triggeredItemIds.remove(itemsToAdd.at(i));
+ }
+ }
+
+ UA_SetTriggeringResponse_clear(&triggeringRes);
+
+ monItem->parameters.setTriggeredItemIds(triggeredItemIds);
+ p.setStatusCode(QOpcUa::UaStatusCode::Good);
+ p.setTriggeredItemIds(triggeredItemIds);
+ p.setFailedTriggeredItemsStatus(failedItems);
+ emit m_backend->monitoringStatusChanged(handle, attr, item, p);
+
+ return;
+ }
+
if (modifySubscriptionParameters(handle, attr, item, value))
return;
if (modifyMonitoredItemParameters(handle, attr, item, value))
@@ -256,6 +338,60 @@ bool QOpen62541Subscription::addAttributeMonitoredItem(quint64 handle, QOpcUa::N
return false;
}
+ QSet<quint32> successfulTriggerLinks;
+ QHash<quint32, QOpcUa::UaStatusCode> failedTriggerLinks;
+ if (!settings.triggeredItemIds().isEmpty()) {
+ auto triggeredItems = settings.triggeredItemIds().values();
+
+ UA_SetTriggeringRequest req;
+ UA_SetTriggeringRequest_init(&req);
+ req.subscriptionId = m_subscriptionId;
+ req.triggeringItemId = res.monitoredItemId;
+ req.linksToAddSize = triggeredItems.size();
+ req.linksToAdd = triggeredItems.data();
+
+ auto triggeringRes = UA_Client_MonitoredItems_setTriggering(m_backend->m_uaclient, req);
+
+ if (triggeringRes.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not set triggering item it for" << attr << "of node" << Open62541Utils::nodeIdToQString(id) << ":"
+ << UA_StatusCode_name(triggeringRes.responseHeader.serviceResult);
+
+ // Remove the new monitored item
+ UA_DeleteMonitoredItemsRequest deleteRequest;
+ UA_DeleteMonitoredItemsRequest_init(&deleteRequest);
+ deleteRequest.subscriptionId = m_subscriptionId;
+ deleteRequest.monitoredItemIdsSize = 1;
+ deleteRequest.monitoredItemIds = &res.monitoredItemId;
+ UA_Client_MonitoredItems_delete(m_backend->m_uaclient, deleteRequest);
+
+ for (const auto &entry : triggeredItems)
+ failedTriggerLinks.insert(entry, QOpcUa::UaStatusCode(triggeringRes.responseHeader.serviceResult));
+
+ QOpcUaMonitoringParameters s;
+ s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(triggeringRes.responseHeader.serviceResult));
+ s.setFailedTriggeredItemsStatus(failedTriggerLinks);
+ emit m_backend->monitoringEnableDisable(handle, attr, true, s);
+
+ UA_SetTriggeringResponse_clear(&triggeringRes);
+
+ return false;
+ }
+
+ for (size_t i = 0; i < triggeringRes.addResultsSize; ++i) {
+ if (triggeringRes.addResults[i] == UA_STATUSCODE_GOOD) {
+ successfulTriggerLinks.insert(triggeredItems.at(i));
+ } else {
+ qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to add trigger link" << res.monitoredItemId
+ << "->" << triggeredItems.at(i) << "on subscription" << m_subscriptionId
+ << "with status"
+ << UA_StatusCode_name(triggeringRes.addResults[i]);
+ failedTriggerLinks.insert(triggeredItems.at(i), QOpcUa::UaStatusCode(triggeringRes.addResults[i]));
+ }
+ }
+
+ UA_SetTriggeringResponse_clear(&triggeringRes);
+ }
+
MonitoredItem *temp = new MonitoredItem(handle, attr, res.monitoredItemId);
m_nodeHandleToItemMapping[handle][attr] = temp;
m_itemIdToItemMapping[res.monitoredItemId] = temp;
@@ -269,6 +405,8 @@ bool QOpen62541Subscription::addAttributeMonitoredItem(quint64 handle, QOpcUa::N
s.setSamplingInterval(res.revisedSamplingInterval);
s.setQueueSize(res.revisedQueueSize);
s.setMonitoredItemId(res.monitoredItemId);
+ s.setTriggeredItemIds(successfulTriggerLinks);
+ s.setFailedTriggeredItemsStatus(failedTriggerLinks);
temp->parameters = s;
temp->clientHandle = m_clientHandle;
diff --git a/tests/auto/qopcuaclient/tst_client.cpp b/tests/auto/qopcuaclient/tst_client.cpp
index b7fe080..c025f68 100644
--- a/tests/auto/qopcuaclient/tst_client.cpp
+++ b/tests/auto/qopcuaclient/tst_client.cpp
@@ -554,6 +554,10 @@ private slots:
defineDataMethod(dataChangeSubscriptionSharing_data)
void dataChangeSubscriptionSharing();
defineDataMethod(methodCall_data)
+ void dataChangeSubscriptionTriggering();
+ defineDataMethod(dataChangeSubscriptionTriggering_data);
+ void dataChangeSubscriptionModifyTriggering();
+ defineDataMethod(dataChangeSubscriptionModifyTriggering_data);
void methodCall();
defineDataMethod(methodCallInvalid_data)
void methodCallInvalid();
@@ -2512,6 +2516,170 @@ void Tst_QOpcUaClient::dataChangeSubscriptionSharing()
QCOMPARE(attrs.size(), 0);
}
+void Tst_QOpcUaClient::dataChangeSubscriptionTriggering()
+{
+ QFETCH(QOpcUaClient *, opcuaClient);
+ OpcuaConnector connector(opcuaClient, m_endpoint);
+
+ // Setup triggered node and write node
+ QScopedPointer<QOpcUaNode> writeNode(opcuaClient->node("ns=2;s=Demo.Static.Scalar.Int32"));
+ QScopedPointer<QOpcUaNode> triggeredNode(opcuaClient->node("ns=2;s=Demo.Static.Scalar.Int32"));
+
+ WRITE_VALUE_ATTRIBUTE(writeNode, 1, QOpcUa::Types::Int32);
+
+ QOpcUaMonitoringParameters triggeredParams(50);
+ triggeredParams.setMonitoringMode(QOpcUaMonitoringParameters::MonitoringMode::Sampling);
+ triggeredParams.setQueueSize(10);
+
+ triggeredNode->enableMonitoring(QOpcUa::NodeAttribute::Value, triggeredParams);
+ QSignalSpy monitoringEnabledSpy2(triggeredNode.data(), &QOpcUaNode::enableMonitoringFinished);
+ QSignalSpy valueChangedSpy(triggeredNode.data(), &QOpcUaNode::attributeUpdated);
+ monitoringEnabledSpy2.wait(signalSpyTimeout);
+
+ QCOMPARE(monitoringEnabledSpy2.size(), 1);
+ QCOMPARE(monitoringEnabledSpy2.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value);
+ const auto triggeredStatus = triggeredNode->monitoringStatus(QOpcUa::NodeAttribute::Value);
+ QCOMPARE(triggeredStatus.statusCode(), QOpcUa::UaStatusCode::Good);
+
+ // Setup trigger
+ QScopedPointer<QOpcUaNode> triggeringNode(opcuaClient->node(readWriteNode));
+ QVERIFY(triggeringNode != nullptr);
+ QSignalSpy monitoringEnabledSpy(triggeringNode.data(), &QOpcUaNode::enableMonitoringFinished);
+
+ QOpcUaMonitoringParameters triggerParameters(50);
+ triggerParameters.setSubscriptionId(triggeredStatus.subscriptionId());
+ triggerParameters.setTriggeredItemIds({ triggeredStatus.monitoredItemId(), 10, 11 });
+ triggeringNode->enableMonitoring(QOpcUa::NodeAttribute::Value, triggerParameters);
+ monitoringEnabledSpy.wait(signalSpyTimeout);
+
+ QCOMPARE(monitoringEnabledSpy.size(), 1);
+ QCOMPARE(monitoringEnabledSpy.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value);
+ QCOMPARE(triggeringNode->monitoringStatus(QOpcUa::NodeAttribute::Value).statusCode(), QOpcUa::UaStatusCode::Good);
+
+ const auto triggerStatus = triggeringNode->monitoringStatus(QOpcUa::NodeAttribute::Value);
+ QVERIFY(triggerStatus.subscriptionId() != 0);
+ QCOMPARE(triggerStatus.statusCode(), QOpcUa::UaStatusCode::Good);
+ QCOMPARE(triggerStatus.subscriptionId(), triggeredStatus.subscriptionId());
+ QCOMPARE(triggerStatus.triggeredItemIds(), QSet<quint32>{ triggeredStatus.monitoredItemId() });
+ QCOMPARE(triggerStatus.failedTriggeredItemsStatus(),
+ (QHash<quint32, QOpcUa::UaStatusCode>{
+ { 10, QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid },
+ { 11, QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid }
+ }));
+
+ // Create value change, trigger and check results
+
+ WRITE_VALUE_ATTRIBUTE(writeNode, 2, QOpcUa::Types::Int32);
+
+ valueChangedSpy.wait(1000);
+ QCOMPARE(valueChangedSpy.size(), 0);
+
+ WRITE_VALUE_ATTRIBUTE(triggeringNode, 1234.0, QOpcUa::Types::Double);
+
+ valueChangedSpy.wait(signalSpyTimeout);
+ QCOMPARE(valueChangedSpy.size(), 1);
+ QCOMPARE(valueChangedSpy.at(0).at(1).value<quint32>(), 2);
+}
+
+void Tst_QOpcUaClient::dataChangeSubscriptionModifyTriggering()
+{
+ QFETCH(QOpcUaClient *, opcuaClient);
+ OpcuaConnector connector(opcuaClient, m_endpoint);
+
+ // Setup triggered and write nodes
+ QScopedPointer<QOpcUaNode> writeNode(opcuaClient->node("ns=2;s=Demo.Static.Scalar.Int32"));
+ QScopedPointer<QOpcUaNode> triggeredNode(opcuaClient->node("ns=2;s=Demo.Static.Scalar.Int32"));
+
+ WRITE_VALUE_ATTRIBUTE(writeNode, 1, QOpcUa::Types::Int32);
+
+ QOpcUaMonitoringParameters triggeredParams(50);
+ triggeredParams.setMonitoringMode(QOpcUaMonitoringParameters::MonitoringMode::Sampling);
+ triggeredParams.setQueueSize(10);
+
+ triggeredNode->enableMonitoring(QOpcUa::NodeAttribute::Value, triggeredParams);
+ QSignalSpy monitoringEnabledSpy2(triggeredNode.data(), &QOpcUaNode::enableMonitoringFinished);
+ QSignalSpy valueChangedSpy(triggeredNode.data(), &QOpcUaNode::attributeUpdated);
+ monitoringEnabledSpy2.wait();
+
+ QCOMPARE(monitoringEnabledSpy2.size(), 1);
+ QCOMPARE(monitoringEnabledSpy2.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value);
+ const auto triggeredStatus = triggeredNode->monitoringStatus(QOpcUa::NodeAttribute::Value);
+ QCOMPARE(triggeredStatus.statusCode(), QOpcUa::UaStatusCode::Good);
+
+ // Setup triggering node
+ QScopedPointer<QOpcUaNode> triggeringNode(opcuaClient->node(readWriteNode));
+ QVERIFY(triggeringNode != nullptr);
+ QSignalSpy monitoringEnabledSpy(triggeringNode.data(), &QOpcUaNode::enableMonitoringFinished);
+
+ QOpcUaMonitoringParameters triggerSettings(50);
+ triggerSettings.setSubscriptionId(triggeredStatus.subscriptionId());
+ triggeringNode->enableMonitoring(QOpcUa::NodeAttribute::Value, triggerSettings);
+ monitoringEnabledSpy.wait(signalSpyTimeout);
+
+ QCOMPARE(monitoringEnabledSpy.size(), 1);
+ QCOMPARE(monitoringEnabledSpy.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value);
+ auto triggerStatus = triggeringNode->monitoringStatus(QOpcUa::NodeAttribute::Value);
+ QCOMPARE(triggerStatus.statusCode(), QOpcUa::UaStatusCode::Good);
+ QCOMPARE(triggerStatus.subscriptionId(), triggeredStatus.subscriptionId());
+ QCOMPARE(triggerStatus.statusCode(), QOpcUa::UaStatusCode::Good);
+
+ // Write, nothing should happen
+ WRITE_VALUE_ATTRIBUTE(writeNode, 2, QOpcUa::Types::Int32);
+
+ valueChangedSpy.wait(1000);
+ QCOMPARE(valueChangedSpy.size(), 0);
+
+ WRITE_VALUE_ATTRIBUTE(triggeringNode, 1235.0, QOpcUa::Types::Double);
+
+ valueChangedSpy.wait(1000);
+ QCOMPARE(valueChangedSpy.size(), 0);
+
+ // Modify triggering node to actually trigger
+ QSignalSpy modifySpy(triggeringNode.get(), &QOpcUaNode::monitoringStatusChanged);
+ triggeringNode->modifyMonitoring(QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters::Parameter::TriggeredItemIds,
+ QVariant::fromValue(QSet<quint32>{triggeredStatus.monitoredItemId(), 10, 11}));
+ modifySpy.wait(signalSpyTimeout);
+ QCOMPARE(modifySpy.size(), 1);
+ QCOMPARE(modifySpy.at(0).at(2).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good);
+ QCOMPARE(triggeringNode->monitoringStatus(QOpcUa::NodeAttribute::Value).triggeredItemIds(),
+ QSet<quint32>{triggeredStatus.monitoredItemId()});
+ QCOMPARE(triggeringNode->monitoringStatus(QOpcUa::NodeAttribute::Value).failedTriggeredItemsStatus(),
+ (QHash<quint32, QOpcUa::UaStatusCode>{
+ { 10, QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid },
+ { 11, QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid }
+ }));
+
+ WRITE_VALUE_ATTRIBUTE(writeNode, 3, QOpcUa::Types::Int32);
+
+ valueChangedSpy.wait(1000);
+ QCOMPARE(valueChangedSpy.size(), 0);
+
+ WRITE_VALUE_ATTRIBUTE(triggeringNode, 1236.0, QOpcUa::Types::Double);
+
+ valueChangedSpy.wait(signalSpyTimeout);
+ QCOMPARE(valueChangedSpy.size(), 1);
+ QCOMPARE(valueChangedSpy.at(0).at(1).value<quint32>(), 3);
+
+ modifySpy.clear();
+ triggeringNode->modifyMonitoring(QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters::Parameter::TriggeredItemIds,
+ QVariant::fromValue(QSet<quint32>()));
+ modifySpy.wait();
+ QCOMPARE(modifySpy.size(), 1);
+ QCOMPARE(modifySpy.at(0).at(2).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good);
+ QCOMPARE(triggeredNode->monitoringStatus(QOpcUa::NodeAttribute::Value).triggeredItemIds(), QSet<quint32>());
+
+ valueChangedSpy.clear();
+ WRITE_VALUE_ATTRIBUTE(writeNode, 4, QOpcUa::Types::Int32);
+
+ valueChangedSpy.wait(1000);
+ QCOMPARE(valueChangedSpy.size(), 0);
+
+ WRITE_VALUE_ATTRIBUTE(triggeringNode, 1237.0, QOpcUa::Types::Double);
+
+ valueChangedSpy.wait(1000);
+ QCOMPARE(valueChangedSpy.size(), 0);
+}
+
void Tst_QOpcUaClient::methodCall()
{
QFETCH(QOpcUaClient *, opcuaClient);