diff options
author | Jannis Voelker <jannis.voelker@basyskom.com> | 2018-02-20 09:16:38 +0100 |
---|---|---|
committer | Maurice Kalinowski <maurice.kalinowski@qt.io> | 2018-03-16 05:57:17 +0000 |
commit | d4c3634267488aed7ef9badd5f269aedcc38af1f (patch) | |
tree | 7680d55172e305cbc118095ef909650c88b31f67 | |
parent | 61f6ad6a5c16c46e4e56471a2306de7c72deca52 (diff) |
Fix and extend support for modifyMonitoring in open62541
The open62541 backend now supports all parameters enumerated
in QOpcUaMonitoringParameters::Parameter.
Change-Id: I04bc55b57661ec9dcbeceac87b68d3324bc22dec
Reviewed-by: Maurice Kalinowski <maurice.kalinowski@qt.io>
Reviewed-by: Frank Meerkoetter <frank.meerkoetter@basyskom.com>
-rw-r--r-- | src/opcua/client/qopcuamonitoringparameters.cpp | 14 | ||||
-rw-r--r-- | src/opcua/client/qopcuamonitoringparameters.h | 1 | ||||
-rw-r--r-- | src/opcua/client/qopcuanode.cpp | 12 | ||||
-rw-r--r-- | src/opcua/client/qopcuanode.h | 1 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541subscription.cpp | 386 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541subscription.h | 8 | ||||
-rw-r--r-- | tests/auto/qopcuaclient/tst_client.cpp | 209 |
7 files changed, 519 insertions, 112 deletions
diff --git a/src/opcua/client/qopcuamonitoringparameters.cpp b/src/opcua/client/qopcuamonitoringparameters.cpp index 18e5433..307bdb1 100644 --- a/src/opcua/client/qopcuamonitoringparameters.cpp +++ b/src/opcua/client/qopcuamonitoringparameters.cpp @@ -108,31 +108,31 @@ QT_BEGIN_NAMESPACE \li X \row \li PublishingEnabled - \li + \li X \li \row \li Filter - \li + \li X \li \row \li QueueSize - \li + \li X \li \row \li DiscardOldest - \li + \li X \li \row \li MonitoringMode - \li + \li X \li \row \li IndexRange - \li + \li X \li \row \li MaxNotificationsPerPublish - \li + \li X \li \endtable */ diff --git a/src/opcua/client/qopcuamonitoringparameters.h b/src/opcua/client/qopcuamonitoringparameters.h index 8a730de..30200c4 100644 --- a/src/opcua/client/qopcuamonitoringparameters.h +++ b/src/opcua/client/qopcuamonitoringparameters.h @@ -164,5 +164,6 @@ Q_DECLARE_METATYPE(QOpcUaMonitoringParameters::DataChangeFilter::DataChangeTrigg Q_DECLARE_METATYPE(QOpcUaMonitoringParameters::DataChangeFilter::DeadbandType) Q_DECLARE_METATYPE(QOpcUaMonitoringParameters::Parameter) Q_DECLARE_METATYPE(QOpcUaMonitoringParameters::Parameters) +Q_DECLARE_METATYPE(QOpcUaMonitoringParameters::MonitoringMode) #endif // QOPCUAMONITORINGPARAMETERS_H diff --git a/src/opcua/client/qopcuanode.cpp b/src/opcua/client/qopcuanode.cpp index 990e438..38dcd3d 100644 --- a/src/opcua/client/qopcuanode.cpp +++ b/src/opcua/client/qopcuanode.cpp @@ -385,6 +385,18 @@ QOpcUaMonitoringParameters QOpcUaNode::monitoringStatus(QOpcUa::NodeAttribute at } /*! + Modifies an existing data change monitoring to use \a filter as data change filter. + + Returns \c true if the filter modification request has been successfully dispatched to the backend. + + \l monitoringStatusChanged for \a attr is emitted after the operation has finished. +*/ +bool QOpcUaNode::modifyDataChangeFilter(QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::DataChangeFilter &filter) +{ + return modifyMonitoring(attr, QOpcUaMonitoringParameters::Parameter::Filter, QVariant::fromValue(filter)); +} + +/*! Writes \a value to the attribute given in \a attribute using the type information from \a type. Returns \c true if the asynchronous call has been successfully dispatched. diff --git a/src/opcua/client/qopcuanode.h b/src/opcua/client/qopcuanode.h index fc12e53..4ab1138 100644 --- a/src/opcua/client/qopcuanode.h +++ b/src/opcua/client/qopcuanode.h @@ -82,6 +82,7 @@ public: bool disableMonitoring(QOpcUa::NodeAttributes attr); bool modifyMonitoring(QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, const QVariant &value); QOpcUaMonitoringParameters monitoringStatus(QOpcUa::NodeAttribute attr); + bool modifyDataChangeFilter(QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::DataChangeFilter &filter); bool browseChildren(QOpcUa::ReferenceTypeId referenceType = QOpcUa::ReferenceTypeId::HierarchicalReferences, QOpcUa::NodeClasses nodeClassMask = QOpcUa::NodeClass::Undefined); diff --git a/src/plugins/opcua/open62541/qopen62541subscription.cpp b/src/plugins/opcua/open62541/qopen62541subscription.cpp index 8100610..36332f9 100644 --- a/src/plugins/opcua/open62541/qopen62541subscription.cpp +++ b/src/plugins/opcua/open62541/qopen62541subscription.cpp @@ -64,6 +64,8 @@ QOpen62541Subscription::QOpen62541Subscription(Open62541AsyncBackend *backend, c , m_maxKeepaliveCount(settings.maxKeepAliveCount() ? settings.maxKeepAliveCount() : UA_SubscriptionSettings_default.requestedMaxKeepAliveCount) , m_shared(settings.shared()) , m_priority(settings.priority()) + , m_maxNotificationsPerPublish(settings.maxNotificationsPerPublish()) + , m_clientHandle(0) { } @@ -79,6 +81,7 @@ UA_UInt32 QOpen62541Subscription::createOnServer() req.requestedLifetimeCount = m_lifetimeCount; req.requestedMaxKeepAliveCount = m_maxKeepaliveCount; req.priority = m_priority; + req.maxNotificationsPerPublish = m_maxNotificationsPerPublish; UA_CreateSubscriptionResponse res = UA_Client_Subscriptions_create(m_backend->m_uaclient, req, this, NULL, NULL); if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { @@ -87,6 +90,9 @@ UA_UInt32 QOpen62541Subscription::createOnServer() } m_subscriptionId = res.subscriptionId; + m_maxKeepaliveCount = res.revisedMaxKeepAliveCount; + m_lifetimeCount = res.revisedLifetimeCount; + m_interval = res.revisedPublishingInterval; return m_subscriptionId; } @@ -117,113 +123,92 @@ void QOpen62541Subscription::modifyMonitoring(uintptr_t handle, QOpcUa::NodeAttr QOpcUaMonitoringParameters p; p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented); - if (!getItemForAttribute(handle, attr)) { + MonitoredItem *monItem = getItemForAttribute(handle, attr); + if (!monItem) { qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify parameter for" << handle << ", there are no monitored items"; p.setStatusCode(QOpcUa::UaStatusCode::BadAttributeIdInvalid); emit m_backend->monitoringStatusChanged(handle, attr, item, p); return; } + p = monItem->parameters; + // SetPublishingMode service if (item == QOpcUaMonitoringParameters::Parameter::PublishingEnabled) { + if (value.type() != QVariant::Bool) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "New value for PublishingEnabled is not a boolean"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return; + } + + UA_SetPublishingModeRequest req; + UA_SetPublishingModeRequest_init(&req); + req.publishingEnabled = value.toBool(); + req.subscriptionIdsSize = 1; + req.subscriptionIds = UA_UInt32_new(); + *req.subscriptionIds = m_subscriptionId; + UA_SetPublishingModeResponse res = UA_Client_Subscriptions_setPublishingMode(m_backend->m_uaclient, req); + + if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to set publishing mode:" << res.responseHeader.serviceResult; + p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult)); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return; + } + + if (res.resultsSize && res.results[0] == UA_STATUSCODE_GOOD) + p.setPublishingEnabled(value.toBool()); + + p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.results[0])); emit m_backend->monitoringStatusChanged(handle, attr, item, p); + + UA_SetPublishingModeRequest_deleteMembers(&req); + UA_SetPublishingModeResponse_deleteMembers(&res); return; } // SetMonitoringMode service if (item == QOpcUaMonitoringParameters::Parameter::MonitoringMode) { - emit m_backend->monitoringStatusChanged(handle, attr, item, p); - return; - } + if (value.type() != QVariant::UserType || value.userType() != QMetaType::type("QOpcUaMonitoringParameters::MonitoringMode")) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "New value for MonitoringMode is not a monitoring mode"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return; + } - // ModifySubscription service - { - UA_ModifySubscriptionRequest req; - UA_ModifySubscriptionRequest_init(&req); + UA_SetMonitoringModeRequest req; + UA_SetMonitoringModeRequest_init(&req); + req.monitoringMode = static_cast<UA_MonitoringMode>(value.value<QOpcUaMonitoringParameters::MonitoringMode>()); + req.monitoredItemIdsSize = 1; + req.monitoredItemIds = UA_UInt32_new(); + *req.monitoredItemIds = monItem->monitoredItemId; req.subscriptionId = m_subscriptionId; - req.requestedPublishingInterval = m_interval; - req.requestedLifetimeCount = m_lifetimeCount; - req.requestedMaxKeepAliveCount = m_maxKeepaliveCount; - - bool match = false; - - switch (item) { - case QOpcUaMonitoringParameters::Parameter::PublishingInterval: { - bool ok; - req.requestedPublishingInterval = value.toDouble(&ok); - if (!ok) { - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify PublishingInterval for" << handle << ", value is not a double"; - p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); - emit m_backend->monitoringStatusChanged(handle, attr, item, p); - return; - } - match = true; - break; - } - case QOpcUaMonitoringParameters::Parameter::LifetimeCount: { - bool ok; - req.requestedLifetimeCount = value.toUInt(&ok); - if (!ok) { - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify LifetimeCount for" << handle << ", value is not an integer"; - p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); - emit m_backend->monitoringStatusChanged(handle, attr, item, p); - return; - } - match = true; - break; - } - case QOpcUaMonitoringParameters::Parameter::MaxKeepAliveCount: { - bool ok; - req.requestedMaxKeepAliveCount = value.toUInt(&ok); - if (!ok) { - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify MaxKeepAliveCount for" << handle << ", value is not an integer"; - p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); - emit m_backend->monitoringStatusChanged(handle, attr, item, p); - return; - } - match = true; - break; - } - default: - break; - } + UA_SetMonitoringModeResponse res = UA_Client_MonitoredItems_setMonitoringMode(m_backend->m_uaclient, req); - if (match) { - UA_ModifySubscriptionResponse res = UA_Client_Subscriptions_modify(m_backend->m_uaclient, req); - - if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { - p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult)); - emit m_backend->monitoringStatusChanged(handle, attr, item, p); - } else { - p.setStatusCode(QOpcUa::UaStatusCode::Good); - p.setPublishingInterval(res.revisedPublishingInterval); - p.setLifetimeCount(res.revisedLifetimeCount); - p.setMaxKeepAliveCount(res.revisedMaxKeepAliveCount); - - QOpcUaMonitoringParameters::Parameters changed = item; - if (!qFuzzyCompare(p.publishingInterval(), m_interval)) - changed |= QOpcUaMonitoringParameters::Parameter::PublishingInterval; - if (p.lifetimeCount() != m_lifetimeCount) - changed |= QOpcUaMonitoringParameters::Parameter::LifetimeCount; - if (p.maxKeepAliveCount() != m_maxKeepaliveCount) - changed |= QOpcUaMonitoringParameters::Parameter::MaxKeepAliveCount; - - for (auto it : qAsConst(m_itemIdToItemMapping)) - emit m_backend->monitoringStatusChanged(it->handle, it->attr, changed, p); - - m_lifetimeCount = res.revisedLifetimeCount; - m_maxKeepaliveCount = res.revisedMaxKeepAliveCount; - m_interval = res.revisedPublishingInterval; - } + if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to set monitoring mode:" << res.responseHeader.serviceResult; + p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult)); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); return; } - } - // ModifyMonitoredItems service - { - // TODO: Add support as soon as Open62541 supports this. + if (res.resultsSize && res.results[0] == UA_STATUSCODE_GOOD) + p.setMonitoringMode(value.value<QOpcUaMonitoringParameters::MonitoringMode>()); + + p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.results[0])); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + + UA_SetMonitoringModeRequest_deleteMembers(&req); + UA_SetMonitoringModeResponse_deleteMembers(&res); + return; } + if (modifySubscriptionParameters(handle, attr, item, value)) + return; + if (modifyMonitoredItemParameters(handle, attr, item, value)) + return; + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Modifying" << item << "is not implemented"; p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented); emit m_backend->monitoringStatusChanged(handle, attr, item, p); @@ -241,8 +226,8 @@ bool QOpen62541Subscription::addAttributeMonitoredItem(uintptr_t handle, QOpcUa: req.requestedParameters.samplingInterval = qFuzzyCompare(settings.samplingInterval(), 0.0) ? m_interval : settings.samplingInterval(); req.requestedParameters.queueSize = settings.queueSize() == 0 ? 1 : settings.queueSize(); req.requestedParameters.discardOldest = settings.discardOldest(); - - if (settings.filter().type() == QVariant::Type::UserType && settings.filter().userType() == QMetaType::type("QOpcUaMonitoringParameters::DataChangeFilter")) + req.requestedParameters.clientHandle = ++m_clientHandle; + if (settings.filter().type() == QVariant::UserType && settings.filter().userType() == QMetaType::type("QOpcUaMonitoringParameters::DataChangeFilter")) req.requestedParameters.filter = createFilter(settings.filter()); UA_MonitoredItemCreateResult res = UA_Client_MonitoredItems_createDataChange(m_backend->m_uaclient, m_subscriptionId, UA_TIMESTAMPSTORETURN_BOTH, req, this, monitoredValueHandler, nullptr); @@ -261,13 +246,19 @@ bool QOpen62541Subscription::addAttributeMonitoredItem(uintptr_t handle, QOpcUa: m_handleToItemMapping[handle][attr] = temp; m_itemIdToItemMapping[res.monitoredItemId] = temp; - QOpcUaMonitoringParameters s; + QOpcUaMonitoringParameters s = settings; + if (settings.filter().type() == QVariant::UserType && settings.filter().userType() == QMetaType::type("QOpcUaMonitoringParameters::DataChangeFilter")) + s.setFilter(QVariant()); s.setSubscriptionId(m_subscriptionId); s.setPublishingInterval(m_interval); s.setMaxKeepAliveCount(m_maxKeepaliveCount); s.setLifetimeCount(m_lifetimeCount); s.setStatusCode(QOpcUa::UaStatusCode::Good); - s.setSamplingInterval(m_interval); + s.setSamplingInterval(res.revisedSamplingInterval); + s.setQueueSize(res.revisedQueueSize); + temp->parameters = s; + temp->clientHandle = m_clientHandle; + emit m_backend->monitoringEnableDisable(handle, attr, true, s); return true; @@ -376,4 +367,221 @@ UA_ExtensionObject QOpen62541Subscription::createFilter(const QVariant &filterDa return obj; } +bool QOpen62541Subscription::modifySubscriptionParameters(uintptr_t handle, QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::Parameter &item, const QVariant &value) +{ + QOpcUaMonitoringParameters p; + + UA_ModifySubscriptionRequest req; + UA_ModifySubscriptionRequest_init(&req); + req.subscriptionId = m_subscriptionId; + req.requestedPublishingInterval = m_interval; + req.requestedLifetimeCount = m_lifetimeCount; + req.requestedMaxKeepAliveCount = m_maxKeepaliveCount; + req.maxNotificationsPerPublish = m_maxNotificationsPerPublish; + + bool match = true; + + switch (item) { + case QOpcUaMonitoringParameters::Parameter::PublishingInterval: { + bool ok; + req.requestedPublishingInterval = value.toDouble(&ok); + if (!ok) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify PublishingInterval for" << handle << ", value is not a double"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return true; + } + break; + } + case QOpcUaMonitoringParameters::Parameter::LifetimeCount: { + bool ok; + req.requestedLifetimeCount = value.toUInt(&ok); + if (!ok) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify LifetimeCount for" << handle << ", value is not an integer"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return true; + } + break; + } + case QOpcUaMonitoringParameters::Parameter::MaxKeepAliveCount: { + bool ok; + req.requestedMaxKeepAliveCount = value.toUInt(&ok); + if (!ok) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify MaxKeepAliveCount for" << handle << ", value is not an integer"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return true; + } + break; + } + case QOpcUaMonitoringParameters::Parameter::Priority: { + bool ok; + req.priority = value.toUInt(&ok); + if (!ok) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify Priority for" << handle << ", value is not an integer"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return true; + } + break; + } + case QOpcUaMonitoringParameters::Parameter::MaxNotificationsPerPublish: { + bool ok; + req.maxNotificationsPerPublish = value.toUInt(&ok); + if (!ok) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify MaxNotificationsPerPublish for" << handle << ", value is not an integer"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return true; + } + break; + } + default: + match = false; + break; + } + + if (match) { + UA_ModifySubscriptionResponse res = UA_Client_Subscriptions_modify(m_backend->m_uaclient, req); + + if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD) { + p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult)); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + } else { + QOpcUaMonitoringParameters::Parameters changed = item; + if (!qFuzzyCompare(p.publishingInterval(), m_interval)) + changed |= QOpcUaMonitoringParameters::Parameter::PublishingInterval; + if (p.lifetimeCount() != m_lifetimeCount) + changed |= QOpcUaMonitoringParameters::Parameter::LifetimeCount; + if (p.maxKeepAliveCount() != m_maxKeepaliveCount) + changed |= QOpcUaMonitoringParameters::Parameter::MaxKeepAliveCount; + + m_lifetimeCount = res.revisedLifetimeCount; + m_maxKeepaliveCount = res.revisedMaxKeepAliveCount; + m_interval = res.revisedPublishingInterval; + if (item == QOpcUaMonitoringParameters::Parameter::Priority) + m_priority = value.toUInt(); + if (item == QOpcUaMonitoringParameters::Parameter::MaxNotificationsPerPublish) + m_maxNotificationsPerPublish = value.toUInt(); + + p.setStatusCode(QOpcUa::UaStatusCode::Good); + p.setPublishingInterval(m_interval); + p.setLifetimeCount(m_lifetimeCount); + p.setMaxKeepAliveCount(m_maxKeepaliveCount); + p.setPriority(m_priority); + p.setMaxNotificationsPerPublish(m_maxNotificationsPerPublish); + + for (auto it : qAsConst(m_itemIdToItemMapping)) + emit m_backend->monitoringStatusChanged(it->handle, it->attr, changed, p); + } + return true; + } + return false; +} + +bool QOpen62541Subscription::modifyMonitoredItemParameters(uintptr_t handle, QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::Parameter &item, const QVariant &value) +{ + MonitoredItem *monItem = getItemForAttribute(handle, attr); + QOpcUaMonitoringParameters p = monItem->parameters; + + UA_ModifyMonitoredItemsRequest req; + UA_ModifyMonitoredItemsRequest_init(&req); + req.subscriptionId = m_subscriptionId; + req.itemsToModifySize = 1; + req.itemsToModify = UA_MonitoredItemModifyRequest_new(); + UA_MonitoredItemModifyRequest_init(req.itemsToModify); + req.itemsToModify->monitoredItemId = monItem->monitoredItemId; + req.itemsToModify->requestedParameters.discardOldest = monItem->parameters.discardOldest(); + req.itemsToModify->requestedParameters.queueSize = monItem->parameters.queueSize(); + req.itemsToModify->requestedParameters.samplingInterval = monItem->parameters.samplingInterval(); + req.itemsToModify->monitoredItemId = monItem->monitoredItemId; + req.itemsToModify->requestedParameters.clientHandle = monItem->clientHandle; + if (item != QOpcUaMonitoringParameters::Parameter::Filter) + req.itemsToModify->requestedParameters.filter = createFilter(monItem->parameters.filter()); + + bool match = true; + + switch (item) { + case QOpcUaMonitoringParameters::Parameter::DiscardOldest: { + if (value.type() != QVariant::Bool) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify DiscardOldest for" << handle << ", value is not a bool"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + UA_ModifyMonitoredItemsRequest_deleteMembers(&req); + return true; + } + req.itemsToModify->requestedParameters.discardOldest = value.toBool(); + break; + } + case QOpcUaMonitoringParameters::Parameter::QueueSize: { + if (value.type() != QVariant::UInt) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify QueueSize for" << handle << ", value is not an integer"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + UA_ModifyMonitoredItemsRequest_deleteMembers(&req); + return true; + } + req.itemsToModify->requestedParameters.queueSize = value.toUInt(); + break; + } + case QOpcUaMonitoringParameters::Parameter::SamplingInterval: { + if (value.type() != QVariant::Double) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not modify SamplingInterval for" << handle << ", value is not a double"; + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + UA_ModifyMonitoredItemsRequest_deleteMembers(&req); + return true; + } + req.itemsToModify->requestedParameters.samplingInterval = value.toDouble(); + break; + } + case QOpcUaMonitoringParameters::Parameter::Filter: { + req.itemsToModify->requestedParameters.filter = createFilter(value); + break; + } + default: + match = false; + break; + } + + if (match) { + UA_ModifyMonitoredItemsResponse res = UA_Client_MonitoredItems_modify(m_backend->m_uaclient, req); + + if (res.responseHeader.serviceResult != UA_STATUSCODE_GOOD || res.results[0].statusCode != UA_STATUSCODE_GOOD) { + p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res.responseHeader.serviceResult == UA_STATUSCODE_GOOD ? res.results[0].statusCode : res.responseHeader.serviceResult)); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + UA_ModifyMonitoredItemsRequest_deleteMembers(&req); + UA_ModifyMonitoredItemsResponse_deleteMembers(&res); + return true; + } else { + p.setStatusCode(QOpcUa::UaStatusCode::Good); + QOpcUaMonitoringParameters::Parameters changed = item; + if (!qFuzzyCompare(p.samplingInterval(), res.results[0].revisedSamplingInterval)) { + p.setSamplingInterval(res.results[0].revisedSamplingInterval); + changed |= QOpcUaMonitoringParameters::Parameter::SamplingInterval; + } + if (p.queueSize() != res.results[0].revisedQueueSize) { + p.setQueueSize(res.results[0].revisedQueueSize); + changed |= QOpcUaMonitoringParameters::Parameter::QueueSize; + } + + if (item == QOpcUaMonitoringParameters::Parameter::DiscardOldest) { + p.setDiscardOldest(value.toBool()); + changed |= QOpcUaMonitoringParameters::Parameter::DiscardOldest; + } + + emit m_backend->monitoringStatusChanged(handle, attr, changed, p); + monItem->parameters = p; + UA_ModifyMonitoredItemsRequest_deleteMembers(&req); + UA_ModifyMonitoredItemsResponse_deleteMembers(&res); + } + return true; + } + + UA_ModifyMonitoredItemsRequest_deleteMembers(&req); + + return false; +} + QT_END_NAMESPACE diff --git a/src/plugins/opcua/open62541/qopen62541subscription.h b/src/plugins/opcua/open62541/qopen62541subscription.h index 2f03a66..58dd407 100644 --- a/src/plugins/opcua/open62541/qopen62541subscription.h +++ b/src/plugins/opcua/open62541/qopen62541subscription.h @@ -64,6 +64,8 @@ public: uintptr_t handle; QOpcUa::NodeAttribute attr; UA_UInt32 monitoredItemId; + UA_UInt32 clientHandle; + QOpcUaMonitoringParameters parameters; MonitoredItem(uintptr_t h, QOpcUa::NodeAttribute a, UA_UInt32 id) : handle(h) , attr(a) @@ -85,6 +87,9 @@ private: MonitoredItem *getItemForAttribute(uintptr_t handle, QOpcUa::NodeAttribute attr); UA_ExtensionObject createFilter(const QVariant &filterData); + bool modifySubscriptionParameters(uintptr_t handle, QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::Parameter &item, const QVariant &value); + bool modifyMonitoredItemParameters(uintptr_t handle, QOpcUa::NodeAttribute attr, const QOpcUaMonitoringParameters::Parameter &item, const QVariant &value); + Open62541AsyncBackend *m_backend; double m_interval; UA_UInt32 m_subscriptionId; @@ -92,9 +97,12 @@ private: UA_UInt32 m_maxKeepaliveCount; QOpcUaMonitoringParameters::SubscriptionType m_shared; quint8 m_priority; + quint32 m_maxNotificationsPerPublish; QHash<uintptr_t, QHash<QOpcUa::NodeAttribute, MonitoredItem *>> m_handleToItemMapping; // Handle -> Attribute -> MonitoredItem QHash<UA_UInt32, MonitoredItem *> m_itemIdToItemMapping; // ItemId -> Item for fast lookup on data change + + quint32 m_clientHandle; }; QT_END_NAMESPACE diff --git a/tests/auto/qopcuaclient/tst_client.cpp b/tests/auto/qopcuaclient/tst_client.cpp index 5326817..6eb6ee6 100644 --- a/tests/auto/qopcuaclient/tst_client.cpp +++ b/tests/auto/qopcuaclient/tst_client.cpp @@ -231,6 +231,13 @@ private slots: void subscriptionIndexRange(); defineDataMethod(subscriptionDataChangeFilter_data) void subscriptionDataChangeFilter(); + defineDataMethod(modifyPublishingMode_data) + void modifyPublishingMode(); + defineDataMethod(modifyMonitoringMode_data) + void modifyMonitoringMode(); + defineDataMethod(modifyMonitoredItem_data) + void modifyMonitoredItem(); + defineDataMethod(stringCharset_data) void stringCharset(); @@ -670,20 +677,6 @@ void Tst_QOpcUaClient::dataChangeSubscription() QCOMPARE(node->monitoringStatus(QOpcUa::NodeAttribute::Value).publishingInterval(), 200.0); QCOMPARE(node->monitoringStatus(QOpcUa::NodeAttribute::DisplayName).publishingInterval(), 200.0); - - monitoringModifiedSpy.clear(); - QOpcUaMonitoringParameters::DataChangeFilter filter; - filter.deadbandType = QOpcUaMonitoringParameters::DataChangeFilter::DeadbandType::Absolute; - filter.trigger = QOpcUaMonitoringParameters::DataChangeFilter::DataChangeTrigger::StatusValue; - filter.deadbandValue = 10; - node->modifyMonitoring(QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters::Parameter::Filter, QVariant::fromValue(filter)); - monitoringModifiedSpy.wait(); - QVERIFY(monitoringModifiedSpy.size() == 1); - QVERIFY(monitoringModifiedSpy.at(0).at(0).value<QOpcUa::NodeAttribute>() == QOpcUa::NodeAttribute::Value); - QVERIFY(monitoringModifiedSpy.at(0).at(1).value<QOpcUaMonitoringParameters::Parameters>() & QOpcUaMonitoringParameters::Parameter::Filter); - QEXPECT_FAIL("", "Modifying monitored items is not yet supported by open62541/uacpp", Continue); - QVERIFY(monitoringModifiedSpy.at(0).at(2).value<QOpcUa::UaStatusCode>() == QOpcUa::UaStatusCode::Good); - } else { qDebug() << "Modifying monitoring settings is not supported by the freeopcua backend"; } @@ -1869,15 +1862,79 @@ void Tst_QOpcUaClient::subscriptionDataChangeFilter() QVERIFY(doubleNode != 0); QOpcUaMonitoringParameters p(100); + + QSignalSpy monitoringEnabledSpy(doubleNode.data(), &QOpcUaNode::enableMonitoringFinished); + QSignalSpy monitoringDisabledSpy(doubleNode.data(), &QOpcUaNode::disableMonitoringFinished); + QSignalSpy dataChangeSpy(doubleNode.data(), &QOpcUaNode::attributeUpdated); + QSignalSpy monitoringModifiedSpy(doubleNode.data(), &QOpcUaNode::monitoringStatusChanged); + + WRITE_VALUE_ATTRIBUTE(doubleNode, 1.0, QOpcUa::Types::Double); + + doubleNode->enableMonitoring(QOpcUa::NodeAttribute::Value, p); + monitoringEnabledSpy.wait(); + QCOMPARE(monitoringEnabledSpy.size(), 1); + QCOMPARE(monitoringEnabledSpy.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value); + QCOMPARE(monitoringEnabledSpy.at(0).at(1).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good); + + dataChangeSpy.wait(); // Wait for the initial data change + QCOMPARE(dataChangeSpy.size(), 1); + dataChangeSpy.clear(); + + WRITE_VALUE_ATTRIBUTE(doubleNode, 1.5, QOpcUa::Types::Double); + + dataChangeSpy.wait(); + QCOMPARE(dataChangeSpy.size(), 1); // Data change without filter + QCOMPARE(doubleNode->attribute(QOpcUa::NodeAttribute::Value), 1.5); + dataChangeSpy.clear(); + QOpcUaMonitoringParameters::DataChangeFilter filter; filter.deadbandType = QOpcUaMonitoringParameters::DataChangeFilter::DeadbandType::Absolute; filter.trigger = QOpcUaMonitoringParameters::DataChangeFilter::DataChangeTrigger::StatusValue; filter.deadbandValue = 1.0; - p.setDataChangeFilter(filter); + doubleNode->modifyDataChangeFilter(QOpcUa::NodeAttribute::Value, filter); + monitoringModifiedSpy.wait(); + QVERIFY(monitoringModifiedSpy.size() == 1); + QVERIFY(monitoringModifiedSpy.at(0).at(0).value<QOpcUa::NodeAttribute>() == QOpcUa::NodeAttribute::Value); + QVERIFY(monitoringModifiedSpy.at(0).at(1).value<QOpcUaMonitoringParameters::Parameters>() & QOpcUaMonitoringParameters::Parameter::Filter); + QVERIFY(monitoringModifiedSpy.at(0).at(2).value<QOpcUa::UaStatusCode>() == QOpcUa::UaStatusCode::Good); + + WRITE_VALUE_ATTRIBUTE(doubleNode, 2.0, QOpcUa::Types::Double); + + dataChangeSpy.wait(); + QCOMPARE(dataChangeSpy.size(), 0); // Filter is active and delta is < 1 + + WRITE_VALUE_ATTRIBUTE(doubleNode, 3.0, QOpcUa::Types::Double); + + dataChangeSpy.wait(); + QCOMPARE(dataChangeSpy.size(), 1); // delta == 1, a data change is expected + QCOMPARE(doubleNode->attribute(QOpcUa::NodeAttribute::Value), 3.0); + + doubleNode->disableMonitoring(QOpcUa::NodeAttribute::Value); + monitoringDisabledSpy.wait(); + QCOMPARE(monitoringDisabledSpy.size(), 1); + QCOMPARE(monitoringDisabledSpy.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value); + QCOMPARE(monitoringDisabledSpy.at(0).at(1).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good); +} + +void Tst_QOpcUaClient::modifyPublishingMode() +{ + QFETCH(QOpcUaClient *, opcuaClient); + OpcuaConnector connector(opcuaClient, m_endpoint); + + if (opcuaClient->backend() == QLatin1String("freeopcua")) + QSKIP("Modification of monitoring is not supported in the freeopcua plugin"); + if (opcuaClient->backend() == QLatin1String("uacpp")) + QSKIP("Modification of monitoring is not supported in the unified automation plugin"); + + QScopedPointer<QOpcUaNode> doubleNode(opcuaClient->node("ns=2;s=Demo.Static.Scalar.Double")); + QVERIFY(doubleNode != 0); + + QOpcUaMonitoringParameters p(100); QSignalSpy monitoringEnabledSpy(doubleNode.data(), &QOpcUaNode::enableMonitoringFinished); QSignalSpy monitoringDisabledSpy(doubleNode.data(), &QOpcUaNode::disableMonitoringFinished); QSignalSpy dataChangeSpy(doubleNode.data(), &QOpcUaNode::attributeUpdated); + QSignalSpy monitoringStatusSpy(doubleNode.data(), &QOpcUaNode::monitoringStatusChanged); WRITE_VALUE_ATTRIBUTE(doubleNode, 1.0, QOpcUa::Types::Double); @@ -1894,13 +1951,133 @@ void Tst_QOpcUaClient::subscriptionDataChangeFilter() WRITE_VALUE_ATTRIBUTE(doubleNode, 1.5, QOpcUa::Types::Double); dataChangeSpy.wait(); + QCOMPARE(dataChangeSpy.size(), 1); + dataChangeSpy.clear(); + + doubleNode->modifyMonitoring(QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters::Parameter::PublishingEnabled, false); + monitoringStatusSpy.wait(); + QCOMPARE(monitoringStatusSpy.size(), 1); + QCOMPARE(monitoringStatusSpy.at(0).at(2).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good); + + WRITE_VALUE_ATTRIBUTE(doubleNode, 3.0, QOpcUa::Types::Double); + + dataChangeSpy.wait(); QCOMPARE(dataChangeSpy.size(), 0); + doubleNode->disableMonitoring(QOpcUa::NodeAttribute::Value); + monitoringDisabledSpy.wait(); + QCOMPARE(monitoringDisabledSpy.size(), 1); + QCOMPARE(monitoringDisabledSpy.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value); + QCOMPARE(monitoringDisabledSpy.at(0).at(1).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good); +} + +void Tst_QOpcUaClient::modifyMonitoringMode() +{ + QFETCH(QOpcUaClient *, opcuaClient); + OpcuaConnector connector(opcuaClient, m_endpoint); + + if (opcuaClient->backend() == QLatin1String("freeopcua")) + QSKIP("Modification of monitoring is not supported in the freeopcua plugin"); + if (opcuaClient->backend() == QLatin1String("uacpp")) + QSKIP("Modification of monitoring is not supported in the unified automation plugin"); + + QScopedPointer<QOpcUaNode> doubleNode(opcuaClient->node("ns=2;s=Demo.Static.Scalar.Double")); + QVERIFY(doubleNode != 0); + + QOpcUaMonitoringParameters p(100); + + QSignalSpy monitoringEnabledSpy(doubleNode.data(), &QOpcUaNode::enableMonitoringFinished); + QSignalSpy monitoringDisabledSpy(doubleNode.data(), &QOpcUaNode::disableMonitoringFinished); + QSignalSpy dataChangeSpy(doubleNode.data(), &QOpcUaNode::attributeUpdated); + QSignalSpy monitoringStatusSpy(doubleNode.data(), &QOpcUaNode::monitoringStatusChanged); + + WRITE_VALUE_ATTRIBUTE(doubleNode, 1.0, QOpcUa::Types::Double); + + doubleNode->enableMonitoring(QOpcUa::NodeAttribute::Value, p); + monitoringEnabledSpy.wait(); + QCOMPARE(monitoringEnabledSpy.size(), 1); + QCOMPARE(monitoringEnabledSpy.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value); + QCOMPARE(monitoringEnabledSpy.at(0).at(1).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good); + + dataChangeSpy.wait(); // Wait for the initial data change + QCOMPARE(dataChangeSpy.size(), 1); + dataChangeSpy.clear(); + + WRITE_VALUE_ATTRIBUTE(doubleNode, 1.5, QOpcUa::Types::Double); + + dataChangeSpy.wait(); + QCOMPARE(dataChangeSpy.size(), 1); + dataChangeSpy.clear(); + + doubleNode->modifyMonitoring(QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters::Parameter::MonitoringMode, + QVariant::fromValue(QOpcUaMonitoringParameters::MonitoringMode::Disabled)); + monitoringStatusSpy.wait(); + QCOMPARE(monitoringStatusSpy.size(), 1); + QCOMPARE(monitoringStatusSpy.at(0).at(2).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good); + + WRITE_VALUE_ATTRIBUTE(doubleNode, 3.0, QOpcUa::Types::Double); + + dataChangeSpy.wait(); + QCOMPARE(dataChangeSpy.size(), 0); + + doubleNode->disableMonitoring(QOpcUa::NodeAttribute::Value); + monitoringDisabledSpy.wait(); + QCOMPARE(monitoringDisabledSpy.size(), 1); + QCOMPARE(monitoringDisabledSpy.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value); + QCOMPARE(monitoringDisabledSpy.at(0).at(1).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good); +} + +void Tst_QOpcUaClient::modifyMonitoredItem() +{ + QFETCH(QOpcUaClient *, opcuaClient); + OpcuaConnector connector(opcuaClient, m_endpoint); + + if (opcuaClient->backend() == QLatin1String("freeopcua")) + QSKIP("Modification of monitoring is not supported in the freeopcua plugin"); + if (opcuaClient->backend() == QLatin1String("uacpp")) + QSKIP("Modification of monitoring is not supported in the unified automation plugin"); + + + QScopedPointer<QOpcUaNode> doubleNode(opcuaClient->node("ns=2;s=Demo.Static.Scalar.Double")); + QVERIFY(doubleNode != 0); + + QOpcUaMonitoringParameters p(100); + + QSignalSpy monitoringEnabledSpy(doubleNode.data(), &QOpcUaNode::enableMonitoringFinished); + QSignalSpy monitoringDisabledSpy(doubleNode.data(), &QOpcUaNode::disableMonitoringFinished); + QSignalSpy dataChangeSpy(doubleNode.data(), &QOpcUaNode::attributeUpdated); + QSignalSpy monitoringStatusSpy(doubleNode.data(), &QOpcUaNode::monitoringStatusChanged); + + WRITE_VALUE_ATTRIBUTE(doubleNode, 1.0, QOpcUa::Types::Double); + + doubleNode->enableMonitoring(QOpcUa::NodeAttribute::Value, p); + monitoringEnabledSpy.wait(); + QCOMPARE(monitoringEnabledSpy.size(), 1); + QCOMPARE(monitoringEnabledSpy.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value); + QCOMPARE(monitoringEnabledSpy.at(0).at(1).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good); + + dataChangeSpy.wait(); // Wait for the initial data change + QCOMPARE(dataChangeSpy.size(), 1); + dataChangeSpy.clear(); + + WRITE_VALUE_ATTRIBUTE(doubleNode, 1.5, QOpcUa::Types::Double); + + dataChangeSpy.wait(); + QCOMPARE(dataChangeSpy.size(), 1); + QCOMPARE(dataChangeSpy.at(0).at(1).value<double>(), 1.5); + dataChangeSpy.clear(); + + doubleNode->modifyMonitoring(QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters::Parameter::SamplingInterval, 50.0); + monitoringStatusSpy.wait(); + QCOMPARE(monitoringStatusSpy.size(), 1); + QCOMPARE(monitoringStatusSpy.at(0).at(2).value<QOpcUa::UaStatusCode>(), QOpcUa::UaStatusCode::Good); + QCOMPARE(monitoringStatusSpy.at(0).at(0).value<QOpcUa::NodeAttribute>(), QOpcUa::NodeAttribute::Value); + QVERIFY(monitoringStatusSpy.at(0).at(1).value<QOpcUaMonitoringParameters::Parameters>() & QOpcUaMonitoringParameters::Parameter::SamplingInterval); + WRITE_VALUE_ATTRIBUTE(doubleNode, 3.0, QOpcUa::Types::Double); dataChangeSpy.wait(); QCOMPARE(dataChangeSpy.size(), 1); - QCOMPARE(doubleNode->attribute(QOpcUa::NodeAttribute::Value), 3.0); doubleNode->disableMonitoring(QOpcUa::NodeAttribute::Value); monitoringDisabledSpy.wait(); |