diff options
author | Jannis Voelker <jannis.voelker@basyskom.com> | 2023-10-24 15:32:47 +0200 |
---|---|---|
committer | Jannis Voelker <jannis.voelker@basyskom.com> | 2023-12-07 14:42:21 +0100 |
commit | c23cccdd17e0529946f0e923a85378e070817c93 (patch) | |
tree | 924577b16ef9f79d9cf76862322216b1151fdc18 | |
parent | bba934aebc0cc37f012cea446258ec9b2cc106c9 (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.cpp | 54 | ||||
-rw-r--r-- | src/opcua/client/qopcuamonitoringparameters.h | 8 | ||||
-rw-r--r-- | src/opcua/client/qopcuamonitoringparameters_p.h | 2 | ||||
-rw-r--r-- | src/opcua/client/qopcuanode_p.h | 4 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541subscription.cpp | 138 | ||||
-rw-r--r-- | tests/auto/qopcuaclient/tst_client.cpp | 168 |
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); |