diff options
Diffstat (limited to 'src/plugins/opcua/open62541')
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541backend.cpp | 203 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541backend.h | 31 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541client.cpp | 8 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541client.h | 1 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541node.cpp | 28 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541node.h | 3 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541subscription.cpp | 295 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541subscription.h | 56 |
8 files changed, 474 insertions, 151 deletions
diff --git a/src/plugins/opcua/open62541/qopen62541backend.cpp b/src/plugins/opcua/open62541/qopen62541backend.cpp index 4411a04..7c5226f 100644 --- a/src/plugins/opcua/open62541/qopen62541backend.cpp +++ b/src/plugins/opcua/open62541/qopen62541backend.cpp @@ -60,10 +60,20 @@ struct UaLocalizedTextMemberDeleter Open62541AsyncBackend::Open62541AsyncBackend(QOpen62541Client *parent) : QOpcUaBackend() - , m_clientImpl(parent) , m_uaclient(nullptr) - , m_subscriptionTimer(nullptr) + , m_clientImpl(parent) + , m_subscriptionTimer(this) + , m_sendPublishRequests(false) + , m_shortestInterval(std::numeric_limits<qint32>::max()) +{ + m_subscriptionTimer.setSingleShot(true); + QObject::connect(&m_subscriptionTimer, &QTimer::timeout, + this, &Open62541AsyncBackend::sendPublishRequest); +} + +Open62541AsyncBackend::~Open62541AsyncBackend() { + qDeleteAll(m_subscriptions); } void Open62541AsyncBackend::readAttributes(uintptr_t handle, UA_NodeId id, QOpcUaNode::NodeAttributes attr) @@ -155,6 +165,109 @@ void Open62541AsyncBackend::writeAttributes(uintptr_t handle, UA_NodeId id, QOpc UA_NodeId_deleteMembers(&id); } +void Open62541AsyncBackend::enableMonitoring(uintptr_t handle, UA_NodeId id, QOpcUaNode::NodeAttributes attr, const QOpcUaMonitoringParameters &settings) +{ + QOpen62541Subscription *usedSubscription = nullptr; + + // Create a new subscription if necessary + if (settings.subscriptionId()) { + auto sub = m_subscriptions.find(settings.subscriptionId()); + if (sub == m_subscriptions.end()) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541, "There is no subscription with id %u", settings.subscriptionId()); + + qt_forEachAttribute(attr, [&](QOpcUaNode::NodeAttribute attribute){ + QOpcUaMonitoringParameters s; + s.setStatusCode(QOpcUa::UaStatusCode::BadSubscriptionIdInvalid); + emit monitoringEnableDisable(handle, attribute, true, s); + }); + return; + } + usedSubscription = sub.value(); // Ignore interval != subscription.interval + } else { + usedSubscription = getSubscription(settings); + } + + if (!usedSubscription) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541, "Could not create subscription with interval %f", settings.publishingInterval()); + qt_forEachAttribute(attr, [&](QOpcUaNode::NodeAttribute attribute){ + QOpcUaMonitoringParameters s; + s.setStatusCode(QOpcUa::UaStatusCode::BadSubscriptionIdInvalid); + emit monitoringEnableDisable(handle, attribute, true, s); + }); + return; + } + + qt_forEachAttribute(attr, [&](QOpcUaNode::NodeAttribute attribute){ + bool success = usedSubscription->addAttributeMonitoredItem(handle, attribute, id, settings); + if (success) + m_attributeMapping[handle][attribute] = usedSubscription; + }); + + if (usedSubscription->monitoredItemsCount() == 0) + removeSubscription(usedSubscription->subscriptionId()); // No items were added + + modifyPublishRequests(); +} + +void Open62541AsyncBackend::disableMonitoring(uintptr_t handle, QOpcUaNode::NodeAttributes attr) +{ + qt_forEachAttribute(attr, [&](QOpcUaNode::NodeAttribute attribute){ + QOpen62541Subscription *sub = getSubscriptionForItem(handle, attribute); + if (sub) { + sub->removeAttributeMonitoredItem(handle, attribute); + if (sub->monitoredItemsCount() == 0) + removeSubscription(sub->subscriptionId()); + } + }); + modifyPublishRequests(); +} + +void Open62541AsyncBackend::modifyMonitoring(uintptr_t handle, QOpcUaNode::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value) +{ + QOpen62541Subscription *subscription = getSubscriptionForItem(handle, attr); + if (!subscription) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541, "Could not modify parameter for %lu, the monitored item does not exist", handle); + QOpcUaMonitoringParameters p; + p.setStatusCode(QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid); + emit monitoringStatusChanged(handle, attr, item, p); + return; + } + + subscription->modifyMonitoring(handle, attr, item, value); + modifyPublishRequests(); +} + +QOpen62541Subscription *Open62541AsyncBackend::getSubscription(const QOpcUaMonitoringParameters &settings) +{ + if (settings.shared() == QOpcUaMonitoringParameters::SubscriptionType::Shared) { + for (auto entry : qAsConst(m_subscriptions)) { + if (qFuzzyCompare(entry->interval(), settings.publishingInterval()) && entry->shared() == QOpcUaMonitoringParameters::SubscriptionType::Shared) + return entry; + } + } + + QOpen62541Subscription *sub = new QOpen62541Subscription(this, settings); + UA_UInt32 id = sub->createOnServer(); + if (!id) { + delete sub; + return nullptr; + } + m_subscriptions[id] = sub; + return sub; +} + +bool Open62541AsyncBackend::removeSubscription(UA_UInt32 subscriptionId) +{ + auto sub = m_subscriptions.find(subscriptionId); + if (sub != m_subscriptions.end()) { + sub.value()->removeOnServer(); + delete sub.value(); + m_subscriptions.remove(subscriptionId); + return true; + } + return false; +} + static UA_StatusCode nodeIter(UA_NodeId childId, UA_Boolean isInverse, UA_NodeId referenceTypeId, void *pass) { Q_UNUSED(referenceTypeId); @@ -208,24 +321,6 @@ QStringList Open62541AsyncBackend::childrenIds(const UA_NodeId *parentNode) return result; } -UA_UInt32 Open62541AsyncBackend::createSubscription(int interval) -{ - UA_UInt32 result; - UA_SubscriptionSettings settings = UA_SubscriptionSettings_default; - settings.requestedPublishingInterval = interval; - UA_StatusCode ret = UA_Client_Subscriptions_new(m_uaclient, settings, &result); - if (ret != UA_STATUSCODE_GOOD) - return 0; - return result; -} - -void Open62541AsyncBackend::deleteSubscription(UA_UInt32 id) -{ - UA_StatusCode ret = UA_Client_Subscriptions_remove(m_uaclient, id); - if (ret != UA_STATUSCODE_GOOD) - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "QOpcUa::Open62541: Could not remove subscription"; -} - void Open62541AsyncBackend::connectToEndpoint(const QUrl &url) { m_uaclient = UA_Client_new(UA_ClientConfig_default); @@ -251,12 +346,6 @@ void Open62541AsyncBackend::connectToEndpoint(const QUrl &url) return; } - if (!m_subscriptionTimer) { - m_subscriptionTimer = new QTimer(this); - m_subscriptionTimer->setInterval(5000); - QObject::connect(m_subscriptionTimer, &QTimer::timeout, - this, &Open62541AsyncBackend::updatePublishSubscriptionRequests); - } emit stateAndOrErrorChanged(QOpcUaClient::Connected, QOpcUaClient::NoError); } @@ -270,42 +359,58 @@ void Open62541AsyncBackend::disconnectFromEndpoint() UA_Client_delete(m_uaclient); m_uaclient = nullptr; - m_subscriptionTimer->stop(); + m_subscriptionTimer.stop(); emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::NoError); } -void Open62541AsyncBackend::updatePublishSubscriptionRequests() const +void Open62541AsyncBackend::sendPublishRequest() { - if (m_uaclient) - UA_Client_Subscriptions_manuallySendPublishRequest(m_uaclient); + if (!m_uaclient) + return; + + if (!m_sendPublishRequests) { + return; + } + + if (UA_Client_Subscriptions_manuallySendPublishRequest(m_uaclient) == UA_STATUSCODE_BADSERVERNOTCONNECTED) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541, "Unable to send publish request"); + m_sendPublishRequests = false; + return; + } + + m_subscriptionTimer.start(m_shortestInterval); } -void Open62541AsyncBackend::activateSubscriptionTimer(int timeout) +void Open62541AsyncBackend::modifyPublishRequests() { - // ### TODO: Check all available subscriptions and their timeout value - // Get minimum value - if (timeout <= 0) + if (m_subscriptions.count() == 0) { + m_subscriptionTimer.stop(); + m_sendPublishRequests = false; + m_shortestInterval = std::numeric_limits<qint32>::max(); return; + } - m_subscriptionIntervals.insert(timeout); + for (auto it : qAsConst(m_subscriptions)) + if (it->interval() < m_shortestInterval) + m_shortestInterval = it->interval(); - int minInterval = timeout; - for (auto it : m_subscriptionIntervals) { - if (it < minInterval) - minInterval = it; - } - // Update subscriptionTimer timeout - m_subscriptionTimer->setInterval(minInterval); - // Start / Stop timer - m_subscriptionTimer->start(); + m_subscriptionTimer.stop(); + m_sendPublishRequests = true; + sendPublishRequest(); } -void Open62541AsyncBackend::removeSubscriptionTimer(int timeout) +QOpen62541Subscription *Open62541AsyncBackend::getSubscriptionForItem(uintptr_t handle, QOpcUaNode::NodeAttribute attr) { - if (!m_subscriptionIntervals.remove(timeout)) - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Trying to remove non-existent interval."; - if (m_subscriptionIntervals.isEmpty()) - m_subscriptionTimer->stop(); + auto nodeEntry = m_attributeMapping.find(handle); + if (nodeEntry == m_attributeMapping.end()) + return nullptr; + + auto subscription = nodeEntry->find(attr); + if (subscription == nodeEntry->end()) { + return nullptr; + } + + return subscription.value(); } QT_END_NAMESPACE diff --git a/src/plugins/opcua/open62541/qopen62541backend.h b/src/plugins/opcua/open62541/qopen62541backend.h index 4445f90..d6e2a80 100644 --- a/src/plugins/opcua/open62541/qopen62541backend.h +++ b/src/plugins/opcua/open62541/qopen62541backend.h @@ -35,6 +35,7 @@ ****************************************************************************/ #include "qopen62541client.h" +#include "qopen62541subscription.h" #include <private/qopcuabackend_p.h> #include <QtCore/qset.h> @@ -50,6 +51,7 @@ class Open62541AsyncBackend : public QOpcUaBackend Q_OBJECT public: Open62541AsyncBackend(QOpen62541Client *parent); + ~Open62541AsyncBackend(); public Q_SLOTS: void connectToEndpoint(const QUrl &url); @@ -61,18 +63,31 @@ public Q_SLOTS: void writeAttribute(uintptr_t handle, UA_NodeId id, QOpcUaNode::NodeAttribute attrId, QVariant value, QOpcUa::Types type); void writeAttributes(uintptr_t handle, UA_NodeId id, QOpcUaNode::AttributeMap toWrite, QOpcUa::Types valueAttributeType); + void enableMonitoring(uintptr_t handle, UA_NodeId id, QOpcUaNode::NodeAttributes attr, const QOpcUaMonitoringParameters &settings); + void disableMonitoring(uintptr_t handle, QOpcUaNode::NodeAttributes attr); + void modifyMonitoring(uintptr_t handle, QOpcUaNode::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value); // Subscription - UA_UInt32 createSubscription(int interval); - void deleteSubscription(UA_UInt32 id); - void updatePublishSubscriptionRequests() const; - void activateSubscriptionTimer(int timeout); - void removeSubscriptionTimer(int timeout); + QOpen62541Subscription *getSubscription(const QOpcUaMonitoringParameters &settings); + bool removeSubscription(UA_UInt32 subscriptionId); + void sendPublishRequest(); + void modifyPublishRequests(); + public: - QOpen62541Client *m_clientImpl; UA_Client *m_uaclient; - QTimer *m_subscriptionTimer; - QSet<int> m_subscriptionIntervals; + +private: + QOpen62541Subscription *getSubscriptionForItem(uintptr_t handle, QOpcUaNode::NodeAttribute attr); + + QOpen62541Client *m_clientImpl; + QTimer m_subscriptionTimer; + + QHash<quint32, QOpen62541Subscription *> m_subscriptions; + + QHash<uintptr_t, QHash<QOpcUaNode::NodeAttribute, QOpen62541Subscription *>> m_attributeMapping; // Handle -> Attribute -> Subscription + + bool m_sendPublishRequests; + qint32 m_shortestInterval; }; QT_END_NAMESPACE diff --git a/src/plugins/opcua/open62541/qopen62541client.cpp b/src/plugins/opcua/open62541/qopen62541client.cpp index bbf6467..63867e2 100644 --- a/src/plugins/opcua/open62541/qopen62541client.cpp +++ b/src/plugins/opcua/open62541/qopen62541client.cpp @@ -91,14 +91,6 @@ QOpcUaNode *QOpen62541Client::node(const QString &nodeId) return new QOpcUaNode(new QOpen62541Node(uaNodeId, this, nodeId), m_client); } -QOpcUaSubscription *QOpen62541Client::createSubscription(quint32 interval) -{ - QOpen62541Subscription *backendSubscription = new QOpen62541Subscription(m_backend, interval); - QOpcUaSubscription *subscription = new QOpcUaSubscription(backendSubscription, interval); - backendSubscription->m_qsubscription = subscription; - return subscription; -} - QString QOpen62541Client::backend() const { return QStringLiteral("open62541"); diff --git a/src/plugins/opcua/open62541/qopen62541client.h b/src/plugins/opcua/open62541/qopen62541client.h index 7b605f3..3721d98 100644 --- a/src/plugins/opcua/open62541/qopen62541client.h +++ b/src/plugins/opcua/open62541/qopen62541client.h @@ -59,7 +59,6 @@ public: void disconnectFromEndpoint() override; QOpcUaNode *node(const QString &nodeId) override; - QOpcUaSubscription *createSubscription(quint32 interval) override; QString backend() const override; diff --git a/src/plugins/opcua/open62541/qopen62541node.cpp b/src/plugins/opcua/open62541/qopen62541node.cpp index 8634f65..d765784 100644 --- a/src/plugins/opcua/open62541/qopen62541node.cpp +++ b/src/plugins/opcua/open62541/qopen62541node.cpp @@ -72,6 +72,34 @@ bool QOpen62541Node::readAttributes(QOpcUaNode::NodeAttributes attr) Q_ARG(QOpcUaNode::NodeAttributes, attr)); } +bool QOpen62541Node::enableMonitoring(QOpcUaNode::NodeAttributes attr, const QOpcUaMonitoringParameters &settings) +{ + return QMetaObject::invokeMethod(m_client->m_backend, "enableMonitoring", + Qt::QueuedConnection, + Q_ARG(uintptr_t, reinterpret_cast<uintptr_t>(this)), + Q_ARG(UA_NodeId, m_nodeId), + Q_ARG(QOpcUaNode::NodeAttributes, attr), + Q_ARG(QOpcUaMonitoringParameters, settings)); +} + +bool QOpen62541Node::disableMonitoring(QOpcUaNode::NodeAttributes attr) +{ + return QMetaObject::invokeMethod(m_client->m_backend, "disableMonitoring", + Qt::QueuedConnection, + Q_ARG(uintptr_t, reinterpret_cast<uintptr_t>(this)), + Q_ARG(QOpcUaNode::NodeAttributes, attr)); +} + +bool QOpen62541Node::modifyMonitoring(QOpcUaNode::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, const QVariant &value) +{ + return QMetaObject::invokeMethod(m_client->m_backend, "modifyMonitoring", + Qt::QueuedConnection, + Q_ARG(uintptr_t, reinterpret_cast<uintptr_t>(this)), + Q_ARG(QOpcUaNode::NodeAttribute, attr), + Q_ARG(QOpcUaMonitoringParameters::Parameter, item), + Q_ARG(QVariant, value)); +} + QStringList QOpen62541Node::childrenIds() const { QStringList result = m_client->m_backend->childrenIds(&m_nodeId); diff --git a/src/plugins/opcua/open62541/qopen62541node.h b/src/plugins/opcua/open62541/qopen62541node.h index ef54618..95e794c 100644 --- a/src/plugins/opcua/open62541/qopen62541node.h +++ b/src/plugins/opcua/open62541/qopen62541node.h @@ -51,6 +51,9 @@ public: ~QOpen62541Node() override; bool readAttributes(QOpcUaNode::NodeAttributes attr) override; + bool enableMonitoring(QOpcUaNode::NodeAttributes attr, const QOpcUaMonitoringParameters &settings); + bool disableMonitoring(QOpcUaNode::NodeAttributes attr); + bool modifyMonitoring(QOpcUaNode::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, const QVariant &value); QStringList childrenIds() const override; QString nodeId() const override; diff --git a/src/plugins/opcua/open62541/qopen62541subscription.cpp b/src/plugins/opcua/open62541/qopen62541subscription.cpp index 4f40d3c..8acc41d 100644 --- a/src/plugins/opcua/open62541/qopen62541subscription.cpp +++ b/src/plugins/opcua/open62541/qopen62541subscription.cpp @@ -39,7 +39,6 @@ #include "qopen62541node.h" #include "qopen62541subscription.h" #include "qopen62541valueconverter.h" -#include <private/qopcuamonitoredvalue_p.h> #include <private/qopcuanode_p.h> #include <QtCore/qloggingcategory.h> @@ -54,115 +53,273 @@ static void monitoredValueHandler(UA_UInt32 monId, UA_DataValue *value, void *co subscription->monitoredValueUpdated(monId, value); } -QOpen62541Subscription::QOpen62541Subscription(Open62541AsyncBackend *backend, quint32 interval) +QOpen62541Subscription::QOpen62541Subscription(Open62541AsyncBackend *backend, const QOpcUaMonitoringParameters &settings) : m_backend(backend) - , m_interval(interval) + , m_interval(settings.publishingInterval()) , m_subscriptionId(0) + , m_lifetimeCount(settings.lifetimeCount() ? settings.lifetimeCount() : UA_SubscriptionSettings_default.requestedLifetimeCount) + , m_maxKeepaliveCount(settings.maxKeepAliveCount() ? settings.maxKeepAliveCount() : UA_SubscriptionSettings_default.requestedMaxKeepAliveCount) + , m_shared(settings.shared()) + , m_priority(settings.priority()) { } QOpen62541Subscription::~QOpen62541Subscription() { - removeNativeSubscription(); + removeOnServer(); + qDeleteAll(m_itemIdToItemMapping); } -QOpcUaMonitoredEvent *QOpen62541Subscription::addEvent(QOpcUaNode *node) +UA_UInt32 QOpen62541Subscription::createOnServer() { - Q_UNUSED(node); - Q_UNIMPLEMENTED(); - return nullptr; + UA_UInt32 subscriptionId = 0; + UA_SubscriptionSettings settings = UA_SubscriptionSettings_default; + settings.requestedPublishingInterval = m_interval; + settings.requestedLifetimeCount = m_lifetimeCount; + settings.requestedMaxKeepAliveCount = m_maxKeepaliveCount; + settings.priority = m_priority; + UA_StatusCode res = UA_Client_Subscriptions_new(m_backend->m_uaclient, settings, &subscriptionId); + + if (res != UA_STATUSCODE_GOOD) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541, "Could not create subscription with interval %f: 0x%X", m_interval, res); + return 0; + } + + m_subscriptionId = subscriptionId; + return subscriptionId; } -void QOpen62541Subscription::removeEvent(QOpcUaMonitoredEvent *event) +bool QOpen62541Subscription::removeOnServer() { - Q_UNUSED(event); - Q_UNIMPLEMENTED(); + if (m_subscriptionId == 0) + return false; + + UA_StatusCode res = UA_Client_Subscriptions_remove(m_backend->m_uaclient, m_subscriptionId); + m_subscriptionId = 0; + + return (res == UA_STATUSCODE_GOOD) ? true : false; } -QOpcUaMonitoredValue *QOpen62541Subscription::addValue(QOpcUaNode *node) +void QOpen62541Subscription::modifyMonitoring(uintptr_t handle, QOpcUaNode::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value) { - if (!ensureNativeSubscription()) - return nullptr; + QOpcUaMonitoringParameters p; + p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented); + + if (!getItemForAttribute(handle, attr)) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541, "Could not modify parameter for %lu, there are no monitored items", handle); + p.setStatusCode(QOpcUa::UaStatusCode::BadAttributeIdInvalid); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return; + } - QOpen62541Node *open62541node = static_cast<QOpen62541Node *>(node->d_func()->m_impl.data()); + // SetPublishingMode service + if (item == QOpcUaMonitoringParameters::Parameter::PublishingEnabled) { + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return; + } + + // SetMonitoringMode service + if (item == QOpcUaMonitoringParameters::Parameter::MonitoringMode) { + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return; + } + + // ModifySubscription service + { + UA_ModifySubscriptionRequest req; + UA_ModifySubscriptionRequest_init(&req); + 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 %lu, value is not a double", handle); + 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 %lu, value is not an integer", handle); + 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 %lu, value is not an integer", handle); + p.setStatusCode(QOpcUa::UaStatusCode::BadTypeMismatch); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + return; + } + match = true; + break; + } + default: + break; + } + + if (match) { + UA_ModifySubscriptionResponse res = UA_Client_Service_modifySubscription(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; + } + return; + } + } + + // ModifyMonitoredItems service + { + // TODO: Add support as soon as Open62541 supports this. + } + + qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Revising attribute is not implemented:" << item; + p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); +} + +bool QOpen62541Subscription::addAttributeMonitoredItem(uintptr_t handle, QOpcUaNode::NodeAttribute attr, const UA_NodeId &id, QOpcUaMonitoringParameters settings) +{ + Q_UNUSED(settings); // This is for later applications like including parameters for the monitored item into settings UA_UInt32 monitoredItemId = 0; - UA_StatusCode ret = UA_Client_Subscriptions_addMonitoredItem(m_backend->m_uaclient, m_subscriptionId, - open62541node->nativeNodeId(), UA_ATTRIBUTEID_VALUE, + UA_StatusCode ret = UA_Client_Subscriptions_addMonitoredItem(m_backend->m_uaclient, m_subscriptionId, id, + QOpen62541ValueConverter::toUaAttributeId(attr), monitoredValueHandler, this, &monitoredItemId); + if (ret != UA_STATUSCODE_GOOD) { - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not add monitored item:" << ret; - return nullptr; + qCWarning(QT_OPCUA_PLUGINS_OPEN62541, "Could not add monitored item to subscription %u: 0x%X", m_subscriptionId, ret); + QOpcUaMonitoringParameters s; + s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(ret)); + emit m_backend->monitoringEnableDisable(handle, attr, true, s); + return false; } - QOpcUaMonitoredValue *monitoredValue = new QOpcUaMonitoredValue(node, m_qsubscription); - if (m_dataChangeHandles.contains(monitoredItemId)) { - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "monitoredItemId already handled:" << monitoredItemId; - } else { - m_dataChangeHandles[monitoredItemId] = monitoredValue; - } + MonitoredItem *temp = new MonitoredItem(handle, attr, monitoredItemId); + m_handleToItemMapping[handle][attr] = temp; + m_itemIdToItemMapping[monitoredItemId] = temp; - // Open62541 does not support automated subscriptions, hence we need to poll for the initial - // value. - UA_Client_Subscriptions_manuallySendPublishRequest(m_backend->m_uaclient); - QMetaObject::invokeMethod(m_backend, "activateSubscriptionTimer", Qt::QueuedConnection, Q_ARG(int, m_interval)); + QOpcUaMonitoringParameters s; + s.setSubscriptionId(m_subscriptionId); + s.setPublishingInterval(m_interval); + s.setMaxKeepAliveCount(m_maxKeepaliveCount); + s.setLifetimeCount(m_lifetimeCount); + s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(ret)); + s.setSamplingInterval(m_interval); + emit m_backend->monitoringEnableDisable(handle, attr, true, s); - return monitoredValue; + return true; } -void QOpen62541Subscription::removeValue(QOpcUaMonitoredValue *monitoredValue) +bool QOpen62541Subscription::removeAttributeMonitoredItem(uintptr_t handle, QOpcUaNode::NodeAttribute attr) { - auto it = m_dataChangeHandles.begin(); - while (it != m_dataChangeHandles.end()) { - if (it.value() == monitoredValue) { - UA_StatusCode ret = UA_Client_Subscriptions_removeMonitoredItem(m_backend->m_uaclient, m_subscriptionId, it.key()); - if (ret != UA_STATUSCODE_GOOD) { - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not remove monitored value from subscription:" << it.key(); - } - m_dataChangeHandles.erase(it); - QMetaObject::invokeMethod(m_backend, "removeSubscriptionTimer", Qt::QueuedConnection, Q_ARG(int, m_interval)); - break; - } - ++it; + MonitoredItem *item = getItemForAttribute(handle, attr); + if (!item) { + qCWarning(QT_OPCUA_PLUGINS_OPEN62541, "There is no monitored item for this attribute"); + QOpcUaMonitoringParameters s; + s.setStatusCode(QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid); + emit m_backend->monitoringEnableDisable(handle, attr, false, s); + return false; } + + UA_StatusCode res = UA_Client_Subscriptions_removeMonitoredItem(m_backend->m_uaclient, m_subscriptionId, item->monitoredItemId); + if (res != UA_STATUSCODE_GOOD) + qCWarning(QT_OPCUA_PLUGINS_OPEN62541, "Could not remove monitored item %u from subscription %u: 0x%X", + item->monitoredItemId, m_subscriptionId, res); + + m_itemIdToItemMapping.remove(item->monitoredItemId); + auto it = m_handleToItemMapping.find(handle); + it->remove(attr); + if (it->empty()) + m_handleToItemMapping.erase(it); + + delete item; + + QOpcUaMonitoringParameters s; + s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(res)); + emit m_backend->monitoringEnableDisable(handle, attr, false, s); + + return true; } void QOpen62541Subscription::monitoredValueUpdated(UA_UInt32 monId, UA_DataValue *value) { - auto monitoredValue = m_dataChangeHandles.find(monId); - if (monitoredValue == m_dataChangeHandles.end()) { - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not find object for monitoredItemId:" << monId; + auto item = m_itemIdToItemMapping.constFind(monId); + if (item == m_itemIdToItemMapping.constEnd()) return; - } + emit m_backend->attributeUpdated(item.value()->handle, item.value()->attr, QOpen62541ValueConverter::toQVariant(value->value)); +} - if (!value || !value->hasValue) - return; +double QOpen62541Subscription::interval() const +{ + return m_interval; +} - QVariant var = QOpen62541ValueConverter::toQVariant(value->value); - if (var.isValid()) - (*monitoredValue)->d_func()->triggerValueChanged(var); - else - qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Could not convert value for node:" << (*monitoredValue)->node().nodeId(); +UA_UInt32 QOpen62541Subscription::subscriptionId() const +{ + return m_subscriptionId; } -bool QOpen62541Subscription::ensureNativeSubscription() +int QOpen62541Subscription::monitoredItemsCount() const { - if (m_subscriptionId == 0) { - QMetaObject::invokeMethod(m_backend, "createSubscription", - Qt::BlockingQueuedConnection, - Q_RETURN_ARG(UA_UInt32, m_subscriptionId), - Q_ARG(int, m_interval)); - } - return m_subscriptionId != 0; + return m_itemIdToItemMapping.size(); } -void QOpen62541Subscription::removeNativeSubscription() +QOpcUaMonitoringParameters::SubscriptionType QOpen62541Subscription::shared() const { - if (m_subscriptionId != 0) { - QMetaObject::invokeMethod(m_backend, "deleteSubscription", - Qt::BlockingQueuedConnection, - Q_ARG(UA_UInt32, m_subscriptionId)); - m_subscriptionId = 0; - } + return m_shared; +} + +QOpen62541Subscription::MonitoredItem *QOpen62541Subscription::getItemForAttribute(uintptr_t handle, QOpcUaNode::NodeAttribute attr) +{ + auto nodeEntry = m_handleToItemMapping.constFind(handle); + + if (nodeEntry == m_handleToItemMapping.constEnd()) + return nullptr; + + auto item = nodeEntry->constFind(attr); + if (item == nodeEntry->constEnd()) + return nullptr; + + return item.value(); } QT_END_NAMESPACE diff --git a/src/plugins/opcua/open62541/qopen62541subscription.h b/src/plugins/opcua/open62541/qopen62541subscription.h index e527c7b..a75aa90 100644 --- a/src/plugins/opcua/open62541/qopen62541subscription.h +++ b/src/plugins/opcua/open62541/qopen62541subscription.h @@ -38,39 +38,63 @@ #define QOPEN62541SUBSCRIPTION_H #include "qopen62541.h" -#include <QtOpcUa/qopcuamonitoredevent.h> -#include <QtOpcUa/qopcuamonitoredvalue.h> -#include <private/qopcuasubscriptionimpl_p.h> +#include <QtOpcUa/qopcuanode.h> QT_BEGIN_NAMESPACE class QOpen62541Client; class Open62541AsyncBackend; -class QOpen62541Subscription : public QOpcUaSubscriptionImpl +class QOpen62541Subscription { public: - explicit QOpen62541Subscription(Open62541AsyncBackend *backend, quint32 interval); - ~QOpen62541Subscription() override; + QOpen62541Subscription(Open62541AsyncBackend *backend, const QOpcUaMonitoringParameters &settings); + ~QOpen62541Subscription(); - QOpcUaMonitoredEvent *addEvent(QOpcUaNode *node) override; - void removeEvent(QOpcUaMonitoredEvent *event) override; + UA_UInt32 createOnServer(); + bool removeOnServer(); - QOpcUaMonitoredValue *addValue(QOpcUaNode *node) override; - void removeValue(QOpcUaMonitoredValue *v) override; + void modifyMonitoring(uintptr_t handle, QOpcUaNode::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value); + + bool addAttributeMonitoredItem(uintptr_t handle, QOpcUaNode::NodeAttribute attr, const UA_NodeId &id, QOpcUaMonitoringParameters settings); + bool removeAttributeMonitoredItem(uintptr_t handle, QOpcUaNode::NodeAttribute attr); void monitoredValueUpdated(UA_UInt32 monId, UA_DataValue *value); - QOpcUaSubscription *m_qsubscription; + struct MonitoredItem { + uintptr_t handle; + QOpcUaNode::NodeAttribute attr; + UA_UInt32 monitoredItemId; + MonitoredItem(uintptr_t h, QOpcUaNode::NodeAttribute a, UA_UInt32 id) + : handle(h) + , attr(a) + , monitoredItemId(id) + {} + MonitoredItem() + : handle(0) + , monitoredItemId(0) + {} + }; + + double interval() const; + UA_UInt32 subscriptionId() const; + int monitoredItemsCount() const; + + QOpcUaMonitoringParameters::SubscriptionType shared() const; private: - bool ensureNativeSubscription(); - void removeNativeSubscription(); + MonitoredItem *getItemForAttribute(uintptr_t handle, QOpcUaNode::NodeAttribute attr); + Open62541AsyncBackend *m_backend; - quint32 m_interval; + double m_interval; UA_UInt32 m_subscriptionId; - QMap<UA_UInt32, QOpcUaMonitoredValue *> m_dataChangeHandles; - QMap<UA_UInt32, QOpcUaMonitoredEvent *> m_eventHandles; + UA_UInt32 m_lifetimeCount; + UA_UInt32 m_maxKeepaliveCount; + QOpcUaMonitoringParameters::SubscriptionType m_shared; + quint8 m_priority; + + QHash<uintptr_t, QHash<QOpcUaNode::NodeAttribute, MonitoredItem *>> m_handleToItemMapping; // Handle -> Attribute -> MonitoredItem + QHash<UA_UInt32, MonitoredItem *> m_itemIdToItemMapping; // ItemId -> Item for fast lookup on data change }; QT_END_NAMESPACE |