diff options
author | Maurice Kalinowski <maurice.kalinowski@qt.io> | 2017-12-06 13:56:52 +0100 |
---|---|---|
committer | Maurice Kalinowski <maurice.kalinowski@qt.io> | 2018-02-26 12:05:36 +0000 |
commit | e6410a0fed4c7f43df28dfbf6e02e6db0e145971 (patch) | |
tree | 7de4dd23a97729d86a725cdcc7896c79b718547b | |
parent | ed0a475529f2ec7be427900b08dfedb1d47e7c9a (diff) |
Add Unified Automation C++ SDK backendv5.11.0-beta1
This change introduces support for the OpcUA client implementation by
Unified Automation, more specifically the C++ SDK version 1.5.6-361.
Currently the Windows and CentOS7 versions are supported.
Change-Id: I48f7963bfd7b080bfecc03f57889faba6f3b4ec5
Reviewed-by: Jannis Völker <jannis.voelker@basyskom.com>
Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
21 files changed, 2675 insertions, 3 deletions
diff --git a/config.tests/uacpp/main.cpp b/config.tests/uacpp/main.cpp new file mode 100644 index 0000000..edda47f --- /dev/null +++ b/config.tests/uacpp/main.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <stdio.h> + +#include <uaplatformlayer.h> +#include <uastring.h> +#include <uasession.h> + +using namespace UaClientSdk; + +int main(int /*argc*/, char ** /*argv*/) +{ + + UaPlatformLayer::init(); + UaSession *session = new UaSession; + + UaPlatformLayer::cleanup(); + return 0; +} diff --git a/config.tests/uacpp/uacpp.pro b/config.tests/uacpp/uacpp.pro new file mode 100644 index 0000000..5a3ba9e --- /dev/null +++ b/config.tests/uacpp/uacpp.pro @@ -0,0 +1,6 @@ +SOURCES += main.cpp + +CONFIG(debug, debug|release): \ + LIBS += $$LIBS_DEBUG +else: \ + LIBS += $$LIBS_RELEASE diff --git a/src/opcua/configure.json b/src/opcua/configure.json index 163dd93..39e746e 100644 --- a/src/opcua/configure.json +++ b/src/opcua/configure.json @@ -45,6 +45,23 @@ "args": "open62541" } ] + }, + "uacpp": { + "label": "Unified Automation C++ SDK", + "test": "uacpp", + "sources": [ + { + "builds": { + "debug": "uaclientd.lib uabased.lib coremoduled.lib uastackd.lib uapkid.lib xmlparserd.lib Crypt32.lib Ole32.lib OleAut32.lib ws2_32.lib libeay32.lib libxml2.lib", + "release": "uaclient.lib uabase.lib coremodule.lib uastack.lib uapki.lib xmlparser.lib Crypt32.lib Ole32.lib OleAut32.lib ws2_32.lib libeay32.lib libxml2.lib" + }, + "condition": "config.win32 && var.QT_EDITION != 'OpenSource'" + }, + { + "libs": "-luaclient -luamodule -luamodels -lcoremodule -luabase -luastack -lxmlparser -luapki -lcry", + "condition": "var.QT_EDITION != 'OpenSource'" + } + ] } }, @@ -58,13 +75,18 @@ "label": "Open62541", "condition": "libs.open62541", "output": [ "privateFeature" ] + }, + "uacpp": { + "label": "Unified Automation C++ SDK", + "condition": "libs.uacpp", + "output": [ "privateFeature" ] } }, "summary": [ { "section": "Qt Opcua", - "entries": [ "freeopcua", "open62541" ] + "entries": [ "freeopcua", "open62541", "uacpp" ] } ] } diff --git a/src/plugins/opcua/opcua.pro b/src/plugins/opcua/opcua.pro index 7156cfa..46228fb 100644 --- a/src/plugins/opcua/opcua.pro +++ b/src/plugins/opcua/opcua.pro @@ -8,3 +8,7 @@ qtConfig(freeopcua) { qtConfig(open62541) { SUBDIRS += open62541 } + +qtConfig(uacpp) { + SUBDIRS += uacpp +} diff --git a/src/plugins/opcua/uacpp/quacppbackend.cpp b/src/plugins/opcua/uacpp/quacppbackend.cpp new file mode 100644 index 0000000..098781d --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppbackend.cpp @@ -0,0 +1,453 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#include "quacppbackend.h" +#include "quacppclient.h" +#include "quacppsubscription.h" +#include "quacpputils.h" +#include "quacppvalueconverter.h" + +#include <private/qopcuaclient_p.h> + +#include <QtCore/QLoggingCategory> +#include <QtCore/QStringList> +#include <QtCore/QUrl> +#include <QtCore/QUuid> + +#include <QtNetwork/QHostInfo> + +#include <uasession.h> +#include <uastring.h> + +#include <limits> + +// We only undef max and do not use NOMINMAX, as the UA SDK seems to rely on it. +#ifdef max +#undef max +#endif + +using namespace UaClientSdk; + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_UACPP) + +UACppAsyncBackend::UACppAsyncBackend(QUACppClient *parent) + : QOpcUaBackend() + , m_nativeSession(new UaSession) + , m_clientImpl(parent) +{ +} + +UACppAsyncBackend::~UACppAsyncBackend() +{ + if (m_nativeSession) { + if (m_nativeSession->isConnected() != OpcUa_False) { + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "UACPP: Deleting backend while still connected"; + ServiceSettings serviceSettings; + m_nativeSession->disconnect(serviceSettings, OpcUa_True); + } + qCDebug(QT_OPCUA_PLUGINS_UACPP) << "UACPP: Deleting session"; + delete m_nativeSession; + } +} + +void UACppAsyncBackend::connectionStatusChanged(OpcUa_UInt32 clientConnectionId, UaClientSdk::UaClient::ServerStatus serverStatus) +{ + Q_UNUSED(clientConnectionId); + + switch (serverStatus) { + case UaClient::Disconnected: + qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Connection closed"; + emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::NoError); + break; + case UaClient::Connected: + qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Connection established"; + emit stateAndOrErrorChanged(QOpcUaClient::Connected, QOpcUaClient::NoError); + break; + case UaClient::ConnectionWarningWatchdogTimeout: + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Unimplemented: Connection status changed to ConnectionWarningWatchdogTimeout"; + break; + case UaClient::ConnectionErrorApiReconnect: + qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Connection status changed to ConnectionErrorApiReconnect"; + emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::ConnectionError); + break; + case UaClient::ServerShutdown: + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Unimplemented: Connection status changed to ServerShutdown"; + break; + case UaClient::NewSessionCreated: + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Unimplemented: Connection status changed to NewSessionCreated"; + break; + } +} + +void UACppAsyncBackend::browseChildren(uintptr_t handle, const UaNodeId &id, QOpcUa::ReferenceTypeId referenceType, QOpcUa::NodeClasses nodeClassMask) +{ + UaStatus status; + ServiceSettings serviceSettings; + BrowseContext browseContext; + UaByteString continuationPoint; + UaReferenceDescriptions referenceDescriptions; + + browseContext.referenceTypeId = UaNodeId(static_cast<OpcUa_UInt32>(referenceType)); + browseContext.nodeClassMask = nodeClassMask; + + QStringList result; + QVector<QOpcUaReferenceDescription> ret; + status = m_nativeSession->browse(serviceSettings, id, browseContext, continuationPoint, referenceDescriptions); + bool initialBrowse = true; + do { + if (!initialBrowse) + status = m_nativeSession->browseNext(serviceSettings, OpcUa_False, continuationPoint, referenceDescriptions); + + if (status.isBad()) { + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Could not browse children."; + break; + } + + initialBrowse = false; + + for (quint32 i = 0; i < referenceDescriptions.length(); ++i) + { + const UaNodeId id(referenceDescriptions[i].NodeId.NodeId); + const UaString uastr(id.toXmlString()); + result.append(QString::fromUtf8(uastr.toUtf8(), uastr.size())); + + QOpcUaReferenceDescription temp; + temp.setNodeId(UACppUtils::nodeIdToQString(referenceDescriptions[i].NodeId.NodeId)); + temp.setRefType(static_cast<QOpcUa::ReferenceTypeId>(UaNodeId(referenceDescriptions[i].ReferenceTypeId).identifierNumeric())); + temp.setNodeClass(static_cast<QOpcUa::NodeClass>(referenceDescriptions[i].NodeClass)); + temp.setBrowseName(QUACppValueConverter::scalarToQVariant<QOpcUa::QQualifiedName, OpcUa_QualifiedName>( + &referenceDescriptions[i].BrowseName, QMetaType::Type::UnknownType).value<QOpcUa::QQualifiedName>()); + temp.setDisplayName(QUACppValueConverter::scalarToQVariant<QOpcUa::QLocalizedText, OpcUa_LocalizedText>( + &referenceDescriptions[i].DisplayName, QMetaType::Type::UnknownType).value<QOpcUa::QLocalizedText>()); + ret.append(temp); + } + } while (continuationPoint.length() > 0); + + emit browseFinished(handle, ret, static_cast<QOpcUa::UaStatusCode>(status.statusCode())); +} + +void UACppAsyncBackend::connectToEndpoint(const QUrl &url) +{ + UaStatus result; + + UaString uaUrl(url.toString(QUrl::RemoveUserInfo).toUtf8().constData()); + SessionConnectInfo sessionConnectInfo; + UaString sNodeName(QHostInfo::localHostName().toUtf8().constData()); + + sessionConnectInfo.sApplicationName = "QtOpcUA Unified Automation Backend"; + // Use the host name to generate a unique application URI + sessionConnectInfo.sApplicationUri = UaString("urn:%1:Qt:OpcUAClient").arg(sNodeName); + sessionConnectInfo.sProductUri = "urn:Qt:OpcUAClient"; + sessionConnectInfo.sSessionName = sessionConnectInfo.sApplicationUri; + sessionConnectInfo.applicationType = OpcUa_ApplicationType_Client; + + SessionSecurityInfo sessionSecurityInfo; + if (url.userName().length()) { + UaString username(url.userName().toUtf8().constData()); + UaString password(url.password().toUtf8().constData()); + sessionSecurityInfo.setUserPasswordUserIdentity(username, password); + } + + result = m_nativeSession->connect(uaUrl, sessionConnectInfo, sessionSecurityInfo, this); + + if (result.isNotGood()) { + // ### TODO: Check for bad syntax, which is the "wrong url" part + emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::AccessDenied); + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to connect: " << QString::fromUtf8(result.toString().toUtf8()); + return; + } +} + +void UACppAsyncBackend::disconnectFromEndpoint() +{ + UaStatus result; + + ServiceSettings serviceSettings; // Default settings + const OpcUa_Boolean deleteSubscriptions{OpcUa_True}; + + result = m_nativeSession->disconnect(serviceSettings, deleteSubscriptions); + QOpcUaClient::ClientError err = QOpcUaClient::NoError; + if (result.isNotGood()) { + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to disconnect."; + err = QOpcUaClient::UnknownError; + } + + emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, err); +} + +inline OpcUa_UInt32 toUaAttributeId(QOpcUa::NodeAttribute attr) +{ + const int attributeIdUsedBits = 22; + for (int i = 0; i < attributeIdUsedBits; ++i) + if (static_cast<int>(attr) == (1 << i)) + return static_cast<OpcUa_UInt32>(i + 1); + + return static_cast<OpcUa_UInt32>(0); +} + +void UACppAsyncBackend::readAttributes(uintptr_t handle, const UaNodeId &id, QOpcUa::NodeAttributes attr, QString indexRange) +{ + UaStatus result; + + ServiceSettings settings; + UaReadValueIds nodeToRead; + UaDataValues values; + UaDiagnosticInfos diagnosticInfos; + + QVector<QOpcUaReadResult> vec; + + OpcUa_NodeId *nativeId = id.copy(); + int attributeSize = 0; + + qt_forEachAttribute(attr, [&](QOpcUa::NodeAttribute attribute){ + attributeSize++; + nodeToRead.resize(attributeSize); + OpcUa_NodeId_CopyTo(nativeId, &nodeToRead[attributeSize - 1].NodeId); + nodeToRead[attributeSize - 1].AttributeId = toUaAttributeId(attribute); + if (indexRange.size()) { + UaString ir(indexRange.toUtf8().constData()); + ir.copyTo(&nodeToRead[attributeSize - 1].IndexRange); + } + QOpcUaReadResult temp; + temp.attributeId = attribute; + vec.push_back(temp); + }); + + result = m_nativeSession->read(settings, + 0, + OpcUa_TimestampsToReturn_Both, + nodeToRead, + values, + diagnosticInfos); + if (result.isBad()) { + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Reading attributes failed:" << result.toString().toUtf8(); + } else { + for (int i = 0; i < vec.size(); ++i) { + vec[i].statusCode = static_cast<QOpcUa::UaStatusCode>(values[i].StatusCode); + vec[i].value = QUACppValueConverter::toQVariant(values[i].Value); + } + } + + emit attributesRead(handle, vec, static_cast<QOpcUa::UaStatusCode>(result.statusCode())); +} + +void UACppAsyncBackend::writeAttribute(uintptr_t handle, const UaNodeId &id, QOpcUa::NodeAttribute attrId, QVariant value, QOpcUa::Types type, QString indexRange) +{ + if (type == QOpcUa::Types::Undefined && attrId != QOpcUa::NodeAttribute::Value) + type = attributeIdToTypeId(attrId); + + UaStatus result; + ServiceSettings settings; + UaWriteValues nodesToWrite; + UaStatusCodeArray writeResults; + UaDiagnosticInfos diagnosticInfos; + + nodesToWrite.create(1); + id.copyTo(&nodesToWrite[0].NodeId); + nodesToWrite[0].AttributeId = QUACppValueConverter::toUaAttributeId(attrId); + nodesToWrite[0].Value.Value = QUACppValueConverter::toUACppVariant(value, type); + if (indexRange.size()) { + UaString ir(indexRange.toUtf8()); + ir.copyTo(&nodesToWrite[0].IndexRange); + } + result = m_nativeSession->write(settings, nodesToWrite, writeResults, diagnosticInfos); + + if (result.isBad()) + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Writing attribute failed:" << result.toString().toUtf8(); + + emit attributeWritten(handle, attrId, result.isGood() ? value : QVariant(), static_cast<QOpcUa::UaStatusCode>(writeResults[0])); +} + +void UACppAsyncBackend::writeAttributes(uintptr_t handle, const UaNodeId &id, QOpcUaNode::AttributeMap toWrite, QOpcUa::Types /*valueAttributeType*/) +{ + if (toWrite.size() == 0) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "No values to be written"); + emit attributeWritten(handle, QOpcUa::NodeAttribute::None, QVariant(), QOpcUa::UaStatusCode::BadNothingToDo); + return; + } + + UaStatus result; + ServiceSettings settings; + UaWriteValues nodesToWrite; + UaStatusCodeArray writeResults; + UaDiagnosticInfos diagnosticInfos; + + nodesToWrite.create(toWrite.size()); + quint32 index = 0; + for (auto it = toWrite.constBegin(); it != toWrite.constEnd(); ++it, ++index) { + id.copyTo(&nodesToWrite[index].NodeId); + QOpcUa::Types type = attributeIdToTypeId(it.key()); + nodesToWrite[index].AttributeId = QUACppValueConverter::toUaAttributeId(it.key()); + nodesToWrite[index].Value.Value = QUACppValueConverter::toUACppVariant(it.value(), type); + } + + result = m_nativeSession->write(settings, nodesToWrite, writeResults, diagnosticInfos); + + if (result.isBad()) + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Writing attribute failed:" << result.toString().toUtf8(); + + index = 0; + for (auto it = toWrite.constBegin(); it != toWrite.constEnd(); ++it, ++index) { + QOpcUa::UaStatusCode status = index < writeResults.length() ? + static_cast<QOpcUa::UaStatusCode>(writeResults[index]) : static_cast<QOpcUa::UaStatusCode>(result.statusCode()); + emit attributeWritten(handle, it.key(), it.value(), status); + } +} + +void UACppAsyncBackend::enableMonitoring(uintptr_t handle, const UaNodeId &id, QOpcUa::NodeAttributes attr, const QOpcUaMonitoringParameters &settings) +{ + QUACppSubscription *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_UACPP, "There is no subscription with id %u", settings.subscriptionId()); + + qt_forEachAttribute(attr, [&](QOpcUa::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_UACPP, "Could not create subscription with interval %f", settings.publishingInterval()); + qt_forEachAttribute(attr, [&](QOpcUa::NodeAttribute attribute){ + QOpcUaMonitoringParameters s; + s.setStatusCode(QOpcUa::UaStatusCode::BadSubscriptionIdInvalid); + emit monitoringEnableDisable(handle, attribute, true, s); + }); + return; + } + + qt_forEachAttribute(attr, [&](QOpcUa::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 +} + +void UACppAsyncBackend::modifyMonitoring(uintptr_t handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value) +{ + QUACppSubscription *subscription = m_attributeMapping[handle][attr]; + if (!subscription) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "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); +} + +void UACppAsyncBackend::disableMonitoring(uintptr_t handle, QOpcUa::NodeAttributes attr) +{ + qt_forEachAttribute(attr, [&](QOpcUa::NodeAttribute attribute){ + QUACppSubscription *sub = m_attributeMapping[handle][attribute]; + if (sub) { + sub->removeAttributeMonitoredItem(handle, attribute); + if (sub->monitoredItemsCount() == 0) + removeSubscription(sub->subscriptionId()); + } + }); +} + +void UACppAsyncBackend::callMethod(uintptr_t handle, const UaNodeId &objectId, const UaNodeId &methodId, QVector<QOpcUa::TypedVariant> args) +{ + ServiceSettings settings; + CallIn in; + + in.objectId = objectId; + in.methodId = methodId; + + if (args.size()) { + in.inputArguments.resize(args.size()); + for (int i = 0; i < args.size(); ++i) + in.inputArguments[i] = QUACppValueConverter::toUACppVariant(args[i].first, args[i].second); + } + + CallOut out; + + UaStatus status = m_nativeSession->call(settings, in, out); + if (status.isBad()) + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Calling method failed"; + + if (out.callResult.isBad()) + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Method call returned a failure"; + + QVariant result; + + if (out.outputArguments.length() > 1) { + QVariantList resultList; + for (quint32 i = 0; i < out.outputArguments.length(); ++i) + resultList.append(QUACppValueConverter::toQVariant(out.outputArguments[i])); + result = resultList; + } else if (out.outputArguments.length() == 1) { + result = QUACppValueConverter::toQVariant(out.outputArguments[0]); + } + + emit methodCallFinished(handle, UACppUtils::nodeIdToQString(methodId), result, static_cast<QOpcUa::UaStatusCode>(status.statusCode())); +} + +QUACppSubscription *UACppAsyncBackend::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; + } + } + + QUACppSubscription *sub = new QUACppSubscription(this, settings); + quint32 id = sub->createOnServer(); + if (!id) { + delete sub; + return nullptr; + } + m_subscriptions[id] = sub; + return sub; +} + +bool UACppAsyncBackend::removeSubscription(quint32 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; +} + +QT_END_NAMESPACE diff --git a/src/plugins/opcua/uacpp/quacppbackend.h b/src/plugins/opcua/uacpp/quacppbackend.h new file mode 100644 index 0000000..df5edea --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppbackend.h @@ -0,0 +1,76 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#ifndef QUACPPASYNCBACKEND_H +#define QUACPPASYNCBACKEND_H + +#include <private/qopcuabackend_p.h> + +#include <QtCore/QSet> +#include <QtCore/QString> +#include <QtCore/QTimer> + +#include <uabase.h> +#include <uaclientsdk.h> + +QT_BEGIN_NAMESPACE + +class QUACppClient; +class QUACppNode; +class QUACppSubscription; + +class UACppAsyncBackend : public QOpcUaBackend, + public UaClientSdk::UaSessionCallback +{ + Q_OBJECT +public: + UACppAsyncBackend(QUACppClient *parent); + ~UACppAsyncBackend(); + + void connectionStatusChanged(OpcUa_UInt32 clientConnectionId, UaClientSdk::UaClient::ServerStatus serverStatus) override; + +public Q_SLOTS: + void connectToEndpoint(const QUrl &url); + void disconnectFromEndpoint(); + + void browseChildren(uintptr_t handle, const UaNodeId &id, QOpcUa::ReferenceTypeId referenceType, QOpcUa::NodeClasses nodeClassMask); + void readAttributes(uintptr_t handle, const UaNodeId &id, QOpcUa::NodeAttributes attr, QString indexRange); + void writeAttribute(uintptr_t handle, const UaNodeId &id, QOpcUa::NodeAttribute attrId, QVariant value, QOpcUa::Types type, QString indexRange); + void writeAttributes(uintptr_t handle, const UaNodeId &id, QOpcUaNode::AttributeMap toWrite, QOpcUa::Types valueAttributeType); + void enableMonitoring(uintptr_t handle, const UaNodeId &id, QOpcUa::NodeAttributes attr, const QOpcUaMonitoringParameters &settings); + void modifyMonitoring(uintptr_t handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value); + void disableMonitoring(uintptr_t handle, QOpcUa::NodeAttributes attr); + void callMethod(uintptr_t handle, const UaNodeId &objectId, const UaNodeId &methodId, QVector<QOpcUa::TypedVariant> args); + + QUACppSubscription *getSubscription(const QOpcUaMonitoringParameters &settings); + bool removeSubscription(quint32 subscriptionId); + +public: + Q_DISABLE_COPY(UACppAsyncBackend); + UaClientSdk::UaSession *m_nativeSession; + QUACppClient *m_clientImpl; + QHash<quint32, QUACppSubscription *> m_subscriptions; + QHash<uintptr_t, QHash<QOpcUa::NodeAttribute, QUACppSubscription *>> m_attributeMapping; // Handle -> Attribute -> Subscription +}; + +QT_END_NAMESPACE + +#endif // QUACPPASYNCBACKEND_H diff --git a/src/plugins/opcua/uacpp/quacppclient.cpp b/src/plugins/opcua/uacpp/quacppclient.cpp new file mode 100644 index 0000000..e0305bc --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppclient.cpp @@ -0,0 +1,80 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#include "quacppbackend.h" +#include "quacppclient.h" +#include "quacppnode.h" +#include "quacpputils.h" + +#include <private/qopcuaclient_p.h> + +#include <QtCore/QLoggingCategory> +#include <QtCore/QStringList> +#include <QtCore/QThread> +#include <QtCore/QUrl> +#include <QtCore/QUuid> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_UACPP) + +QUACppClient::QUACppClient() + : QOpcUaClientImpl() + , m_backend(new UACppAsyncBackend(this)) +{ + m_thread = new QThread(); + connectBackendWithClient(m_backend); + m_backend->moveToThread(m_thread); + connect(m_thread, &QThread::finished, m_thread, &QObject::deleteLater); + connect(m_thread, &QThread::finished, m_backend, &QObject::deleteLater); + m_thread->start(); +} + +QUACppClient::~QUACppClient() +{ + if (m_thread->isRunning()) + m_thread->quit(); +} + +void QUACppClient::connectToEndpoint(const QUrl &url) +{ + QMetaObject::invokeMethod(m_backend, "connectToEndpoint", Qt::QueuedConnection, Q_ARG(QUrl, url)); +} + +void QUACppClient::disconnectFromEndpoint() +{ + QMetaObject::invokeMethod(m_backend, "disconnectFromEndpoint", Qt::QueuedConnection); +} + +QOpcUaNode *QUACppClient::node(const QString &nodeId) +{ + UaNodeId nativeId = UACppUtils::nodeIdFromQString(nodeId); + if (nativeId.isNull()) + return nullptr; + return new QOpcUaNode(new QUACppNode(nativeId, this, nodeId), m_client); +} + +QString QUACppClient::backend() const +{ + return QStringLiteral("uacpp"); +} + +QT_END_NAMESPACE diff --git a/src/plugins/opcua/uacpp/quacppclient.h b/src/plugins/opcua/uacpp/quacppclient.h new file mode 100644 index 0000000..a5759c4 --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppclient.h @@ -0,0 +1,56 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#ifndef QUACPPCLIENT_H +#define QUACPPCLIENT_H + +#include <private/qopcuaclientimpl_p.h> + +#include <QtCore/QTimer> + +QT_BEGIN_NAMESPACE + +class UACppAsyncBackend; + +class QUACppClient : public QOpcUaClientImpl +{ + Q_OBJECT + +public: + explicit QUACppClient(); + ~QUACppClient(); + + void connectToEndpoint(const QUrl &url) override; + void disconnectFromEndpoint() override; + + QOpcUaNode *node(const QString &nodeId) override; + + QString backend() const override; + +private: + friend class QUACppNode; + QThread *m_thread; + UACppAsyncBackend *m_backend; +}; + +QT_END_NAMESPACE + +#endif // QUACPPCLIENT_H diff --git a/src/plugins/opcua/uacpp/quacppnode.cpp b/src/plugins/opcua/uacpp/quacppnode.cpp new file mode 100644 index 0000000..920b966 --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppnode.cpp @@ -0,0 +1,136 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#include "quacppbackend.h" +#include "quacppnode.h" +#include "quacpputils.h" + +#include <QtCore/QDateTime> +#include <QtCore/QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_UACPP) + +QUACppNode::QUACppNode(const UaNodeId nodeId, QUACppClient *client, const QString nodeIdString) + : m_client(client) + , m_nodeIdString(nodeIdString) + , m_nodeId(nodeId) +{ + m_client->registerNode(this); +} + +QUACppNode::~QUACppNode() +{ + if (m_client) + m_client->unregisterNode(this); +} + +bool QUACppNode::readAttributes(QOpcUa::NodeAttributes attr, const QString &indexRange) +{ + return QMetaObject::invokeMethod(m_client->m_backend, "readAttributes", + Qt::QueuedConnection, + Q_ARG(uintptr_t, reinterpret_cast<uintptr_t>(this)), + Q_ARG(UaNodeId, m_nodeId), + Q_ARG(QOpcUa::NodeAttributes, attr), + Q_ARG(QString, indexRange)); +} + +bool QUACppNode::enableMonitoring(QOpcUa::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(UaNodeId, m_nodeId), + Q_ARG(QOpcUa::NodeAttributes, attr), + Q_ARG(QOpcUaMonitoringParameters, settings)); +} + +bool QUACppNode::disableMonitoring(QOpcUa::NodeAttributes attr) +{ + return QMetaObject::invokeMethod(m_client->m_backend, "disableMonitoring", + Qt::QueuedConnection, + Q_ARG(uintptr_t, reinterpret_cast<uintptr_t>(this)), + Q_ARG(QOpcUa::NodeAttributes, attr)); +} + +bool QUACppNode::modifyMonitoring(QOpcUa::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(QOpcUa::NodeAttribute, attr), + Q_ARG(QOpcUaMonitoringParameters::Parameter, item), + Q_ARG(QVariant, value)); +} + +bool QUACppNode::browseChildren(QOpcUa::ReferenceTypeId referenceType, QOpcUa::NodeClasses nodeClassMask) +{ + return QMetaObject::invokeMethod(m_client->m_backend, "browseChildren", + Qt::QueuedConnection, + Q_ARG(uintptr_t, reinterpret_cast<uintptr_t>(this)), + Q_ARG(UaNodeId, m_nodeId), + Q_ARG(QOpcUa::ReferenceTypeId, referenceType), + Q_ARG(QOpcUa::NodeClasses, nodeClassMask)); + +} + +QString QUACppNode::nodeId() const +{ + return m_nodeIdString; +} + +bool QUACppNode::writeAttribute(QOpcUa::NodeAttribute attribute, const QVariant &value, QOpcUa::Types type, const QString &indexRange) +{ + return QMetaObject::invokeMethod(m_client->m_backend, "writeAttribute", + Qt::QueuedConnection, + Q_ARG(uintptr_t, reinterpret_cast<uintptr_t>(this)), + Q_ARG(UaNodeId, m_nodeId), + Q_ARG(QOpcUa::NodeAttribute, attribute), + Q_ARG(QVariant, value), + Q_ARG(QOpcUa::Types, type), + Q_ARG(QString, indexRange)); +} + +bool QUACppNode::writeAttributes(const QOpcUaNode::AttributeMap &toWrite, QOpcUa::Types valueAttributeType) +{ + return QMetaObject::invokeMethod(m_client->m_backend, "writeAttributes", + Qt::QueuedConnection, + Q_ARG(uintptr_t, reinterpret_cast<uintptr_t>(this)), + Q_ARG(UaNodeId, m_nodeId), + Q_ARG(QOpcUaNode::AttributeMap, toWrite), + Q_ARG(QOpcUa::Types, valueAttributeType)); + +} + +bool QUACppNode::callMethod(const QString &methodNodeId, const QVector<QOpcUa::TypedVariant> &args) +{ + const UaNodeId methodId = UACppUtils::nodeIdFromQString(methodNodeId); + + return QMetaObject::invokeMethod(m_client->m_backend, "callMethod", + Qt::QueuedConnection, + Q_ARG(uintptr_t, reinterpret_cast<uintptr_t>(this)), + Q_ARG(UaNodeId, m_nodeId), + Q_ARG(UaNodeId, methodId), + Q_ARG(QVector<QOpcUa::TypedVariant>, args)); +} + +QT_END_NAMESPACE diff --git a/src/plugins/opcua/uacpp/quacppnode.h b/src/plugins/opcua/uacpp/quacppnode.h new file mode 100644 index 0000000..0480444 --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppnode.h @@ -0,0 +1,61 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#ifndef QUACPPNODE_H +#define QUACPPNODE_H + +#include "quacppclient.h" + +#include <private/qopcuanodeimpl_p.h> + +#include <QtCore/QPointer> + +#include <uanodeid.h> + +QT_BEGIN_NAMESPACE + +class QUACppNode : public QOpcUaNodeImpl +{ +public: + explicit QUACppNode(const UaNodeId nodeId, QUACppClient *client, const QString nodeIdString); + ~QUACppNode() override; + + bool readAttributes(QOpcUa::NodeAttributes attr, const QString &indexRange) override; + bool enableMonitoring(QOpcUa::NodeAttributes attr, const QOpcUaMonitoringParameters &settings) override; + bool disableMonitoring(QOpcUa::NodeAttributes attr) override; + bool modifyMonitoring(QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, const QVariant &value) override; + bool browseChildren(QOpcUa::ReferenceTypeId referenceType, QOpcUa::NodeClasses nodeClassMask) override; + + QString nodeId() const override; + + bool writeAttribute(QOpcUa::NodeAttribute attribute, const QVariant &value, QOpcUa::Types type, const QString &indexRange) override; + bool writeAttributes(const QOpcUaNode::AttributeMap &toWrite, QOpcUa::Types valueAttributeType) override; + bool callMethod(const QString &methodNodeId, const QVector<QOpcUa::TypedVariant> &args) override; + +private: + QPointer<QUACppClient> m_client; + QString m_nodeIdString; + UaNodeId m_nodeId; +}; + +QT_END_NAMESPACE + +#endif // QUACPPNODE_H diff --git a/src/plugins/opcua/uacpp/quacppplugin.cpp b/src/plugins/opcua/uacpp/quacppplugin.cpp new file mode 100644 index 0000000..03638e5 --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppplugin.cpp @@ -0,0 +1,54 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#include "quacppplugin.h" +#include "quacppclient.h" + +#include <QtCore/QLoggingCategory> + +#include <uaplatformlayer.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_UACPP, "qt.opcua.plugins.uacpp") + +QUACppPlugin::QUACppPlugin(QObject *parent) + : QOpcUaPlugin(parent) +{ + qRegisterMetaType<UaNodeId>(); +} + +QUACppPlugin::~QUACppPlugin() +{ + if (m_initialized) + UaPlatformLayer::cleanup(); +} + +QOpcUaClient *QUACppPlugin::createClient() +{ + if (!m_initialized) { + UaPlatformLayer::init(); + m_initialized = true; + } + return new QOpcUaClient(new QUACppClient); +} + +QT_END_NAMESPACE diff --git a/src/plugins/opcua/uacpp/quacppplugin.h b/src/plugins/opcua/uacpp/quacppplugin.h new file mode 100644 index 0000000..5575e47 --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppplugin.h @@ -0,0 +1,51 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#ifndef QUACPPPLUGIN_H +#define QUACPPPLUGIN_H + +#include "qopcuaplugin.h" + +#include <uanodeid.h> + +QT_BEGIN_NAMESPACE + +class QUACppPlugin : public QOpcUaPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "org.qt-project.qt.opcua.providerfactory/1.0" FILE "uacpp-metadata.json") + Q_INTERFACES(QOpcUaPlugin) + +public: + explicit QUACppPlugin(QObject *parent = 0); + ~QUACppPlugin() override; + + QOpcUaClient *createClient() override; +private: + bool m_initialized{false}; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(UaNodeId) + +#endif // QUACPPPLUGIN_H diff --git a/src/plugins/opcua/uacpp/quacppsubscription.cpp b/src/plugins/opcua/uacpp/quacppsubscription.cpp new file mode 100644 index 0000000..bc45c6e --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppsubscription.cpp @@ -0,0 +1,351 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#include "quacppsubscription.h" +#include "quacppclient.h" +#include "quacppvalueconverter.h" + +#include <QtCore/QLoggingCategory> + +#include <uasession.h> + +using namespace UaClientSdk; + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_UACPP) + +QUACppSubscription::QUACppSubscription(UACppAsyncBackend *backend, const QOpcUaMonitoringParameters &settings) + : UaSubscriptionCallback() + , m_backend(backend) + , m_parameters(settings) + , m_nativeSubscription(nullptr) +{ +} + +QUACppSubscription::~QUACppSubscription() +{ +} + +quint32 QUACppSubscription::createOnServer() +{ + UaStatus result; + ServiceSettings serviceSettings; + SubscriptionSettings subscriptionSettings; + subscriptionSettings.publishingInterval = m_parameters.publishingInterval(); + + result = m_backend->m_nativeSession->createSubscription( + serviceSettings, + this, + 1, + subscriptionSettings, + OpcUa_True, + &m_nativeSubscription); + + if (result.isGood()) { + const OpcUa_UInt32 subId = m_nativeSubscription->subscriptionId(); + qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Subscription suceeded, returning id:" << subId; + return subId; + } + + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Subscription creation failed."; + return 0; +} + +bool QUACppSubscription::removeOnServer() +{ + if (!m_nativeSubscription) { + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Removing non existing subscription"; + return false; + } + + UaStatus result; + ServiceSettings settings; + + result = m_backend->m_nativeSession->deleteSubscription(settings, &m_nativeSubscription); + if (result.isBad()) { + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to delete subscription:"; + return false; + } + m_nativeSubscription = nullptr; + return true; +} + +bool QUACppSubscription::addAttributeMonitoredItem(uintptr_t handle, QOpcUa::NodeAttribute attr, const UaNodeId &id, QOpcUaMonitoringParameters parameters) +{ + qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Adding monitored Item: " << handle << ":" << attr; + static quint32 monitorId = 100; + + UaStatus result; + ServiceSettings settings; + UaMonitoredItemCreateRequests createRequests; + UaMonitoredItemCreateResults createResults; + + createRequests.create(1); + createRequests[0].ItemToMonitor.AttributeId = QUACppValueConverter::toUaAttributeId(attr); + id.copyTo(&createRequests[0].ItemToMonitor.NodeId); + const UaString uaiR(parameters.indexRange().toUtf8().constData()); + uaiR.copyTo(&createRequests[0].ItemToMonitor.IndexRange); + createRequests[0].RequestedParameters.ClientHandle = monitorId; + createRequests[0].RequestedParameters.SamplingInterval = parameters.samplingInterval(); + if (createRequests[0].RequestedParameters.SamplingInterval == 0.) + createRequests[0].RequestedParameters.SamplingInterval = parameters.publishingInterval(); + createRequests[0].RequestedParameters.QueueSize = 1; + createRequests[0].RequestedParameters.DiscardOldest = OpcUa_True; + createRequests[0].MonitoringMode = static_cast<OpcUa_MonitoringMode>(parameters.monitoringMode()); + + result = m_nativeSubscription->createMonitoredItems(settings, OpcUa_TimestampsToReturn_Both, + createRequests, createResults); + + if (result.isBad() || createResults.length() != 1 || OpcUa_IsBad(createResults[0].StatusCode)) { + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "creating monitored item failed."; + QOpcUaMonitoringParameters s; + s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(createResults[0].StatusCode)); + emit m_backend->monitoringEnableDisable(handle, attr, true, s); + return false; + } + + // Store information + const auto p = qMakePair(handle, attr); + m_monitoredItems.insert(p, createResults[0]); + m_monitoredIds.insert(monitorId, p); + monitorId++; + + QOpcUaMonitoringParameters s; + s.setSubscriptionId(m_nativeSubscription->subscriptionId()); + s.setPublishingInterval(m_nativeSubscription->publishingInterval()); + s.setMaxKeepAliveCount(m_nativeSubscription->maxKeepAliveCount()); + s.setLifetimeCount(m_nativeSubscription->lifetimeCount()); + s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(result.statusCode())); + s.setSamplingInterval(createResults[0].RevisedSamplingInterval); + emit m_backend->monitoringEnableDisable(handle, attr, true, s); + + return true; +} + +void QUACppSubscription::modifyMonitoring(uintptr_t handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value) +{ + QOpcUaMonitoringParameters p; + p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented); + const auto key = qMakePair(handle, attr); + + if (!m_monitoredItems.contains(key)) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "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; + } + + // 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 + { + SubscriptionSettings settings; + settings.maxNotificationsPerPublish = m_nativeSubscription->maxNotificationsPerPublish(); + settings.publishingInterval = m_nativeSubscription->publishingInterval(); + settings.lifetimeCount = m_nativeSubscription->lifetimeCount(); + settings.maxKeepAliveCount = m_nativeSubscription->maxKeepAliveCount(); + + bool match = false; + + switch (item) { + case QOpcUaMonitoringParameters::Parameter::PublishingInterval: { + bool ok; + settings.publishingInterval = value.toDouble(&ok); + + if (!ok) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "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; + settings.lifetimeCount = value.toUInt(&ok); + + if (!ok) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "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; + settings.maxKeepAliveCount = value.toUInt(&ok); + + if (!ok) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "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) { + ServiceSettings service; + const double oldPublishInterval = m_nativeSubscription->publishingInterval(); + const quint32 oldLifeTimeCount = m_nativeSubscription->lifetimeCount(); + const quint32 oldMaxKeepAlive = m_nativeSubscription->maxKeepAliveCount(); + UaStatus result = m_nativeSubscription->modifySubscription(service, settings); + + if (result.isBad()) { + p.setStatusCode(static_cast<QOpcUa::UaStatusCode>(result.statusCode())); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); + } else { + p.setStatusCode(QOpcUa::UaStatusCode::Good); + p.setPublishingInterval(settings.publishingInterval); + p.setLifetimeCount(settings.lifetimeCount); + p.setMaxKeepAliveCount(settings.maxKeepAliveCount); + + QOpcUaMonitoringParameters::Parameters changed = item; + if (!qFuzzyCompare(p.publishingInterval(), oldPublishInterval)) + changed |= QOpcUaMonitoringParameters::Parameter::PublishingInterval; + if (p.lifetimeCount() != oldLifeTimeCount) + changed |= QOpcUaMonitoringParameters::Parameter::LifetimeCount; + if (p.maxKeepAliveCount() != oldMaxKeepAlive) + changed |= QOpcUaMonitoringParameters::Parameter::MaxKeepAliveCount; + + for (auto it : qAsConst(m_monitoredIds)) + emit m_backend->monitoringStatusChanged(it.first, it.second, changed, p); + } + return; + } + } + + // ModifyMonitoredItems service + { + // ### TODO: Add support for uacpp + } + + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Modifying" << item << "is not implemented."; + p.setStatusCode(QOpcUa::UaStatusCode::BadNotImplemented); + emit m_backend->monitoringStatusChanged(handle, attr, item, p); +} + +bool QUACppSubscription::removeAttributeMonitoredItem(uintptr_t handle, QOpcUa::NodeAttribute attr) +{ + qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Removing monitored Item: " << handle << ":" << attr; + + const QPair<uintptr_t, QOpcUa::NodeAttribute> pair(handle, attr); + if (!m_monitoredItems.contains(pair)) { + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Trying to remove unknown monitored item:" << handle << ":" << attr; + return false; + } + + auto monitoredItem = m_monitoredItems.take(pair); + UaStatus result; + ServiceSettings settings; + + UaUInt32Array removeIds; + removeIds.resize(1); + removeIds[0] = monitoredItem.MonitoredItemId; + UaStatusCodeArray removeResults; + result = m_nativeSubscription->deleteMonitoredItems(settings, removeIds, removeResults); + if (result.isBad() || removeResults.length() != 1 || OpcUa_IsBad(removeResults[0])) { + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to remove monitored item:" << handle << ":" << attr; + return false; + } + + QOpcUaMonitoringParameters s; + s.setSubscriptionId(m_nativeSubscription->subscriptionId()); + s.setPublishingInterval(m_nativeSubscription->publishingInterval()); + s.setMaxKeepAliveCount(m_nativeSubscription->maxKeepAliveCount()); + s.setLifetimeCount(m_nativeSubscription->lifetimeCount()); + s.setStatusCode(static_cast<QOpcUa::UaStatusCode>(result.statusCode())); + emit m_backend->monitoringEnableDisable(handle, attr, false, s); + + return true; +} + +double QUACppSubscription::interval() const +{ + return m_parameters.publishingInterval(); +} + +quint32 QUACppSubscription::subscriptionId() const +{ + return m_nativeSubscription->subscriptionId(); +} + +int QUACppSubscription::monitoredItemsCount() const +{ + return m_monitoredItems.size(); +} + +QOpcUaMonitoringParameters::SubscriptionType QUACppSubscription::shared() const +{ + return m_parameters.shared(); +} + +void QUACppSubscription::subscriptionStatusChanged(OpcUa_UInt32 clientSubscriptionHandle, const UaStatus &status) +{ + Q_UNUSED(clientSubscriptionHandle); + Q_UNUSED(status); + Q_UNIMPLEMENTED(); + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "subscriptionStatusChange unhandled"; +} + +void QUACppSubscription::dataChange(OpcUa_UInt32 clientSubscriptionHandle, const UaMonitoredItemNotifications &dataNotifications, const UaDiagnosticInfos &diagnosticInfos) +{ + Q_UNUSED(diagnosticInfos); + qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Data Change on:" << clientSubscriptionHandle << ":" << m_nativeSubscription->subscriptionId(); + + for (quint32 i = 0; i < dataNotifications.length(); ++i) { + const quint32 monitorId = dataNotifications[i].ClientHandle; + const QVariant var = QUACppValueConverter::toQVariant(dataNotifications[i].Value.Value); + if (!m_monitoredIds.contains(monitorId)) + continue; + emit m_backend->attributeUpdated(m_monitoredIds[monitorId].first, + m_monitoredIds[monitorId].second, + var); + } +} + +void QUACppSubscription::newEvents(OpcUa_UInt32 clientSubscriptionHandle, UaEventFieldLists &eventFieldList) +{ + Q_UNUSED(clientSubscriptionHandle); + Q_UNUSED(eventFieldList); + Q_UNIMPLEMENTED(); + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "eventsChange unhandled"; +} + +QT_END_NAMESPACE diff --git a/src/plugins/opcua/uacpp/quacppsubscription.h b/src/plugins/opcua/uacpp/quacppsubscription.h new file mode 100644 index 0000000..05ebdf9 --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppsubscription.h @@ -0,0 +1,66 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#ifndef QUACPPSUBSCRIPTION_H +#define QUACPPSUBSCRIPTION_H + +#include <quacppbackend.h> + +#include <uanodeid.h> +#include <uasubscription.h> + +QT_BEGIN_NAMESPACE + +class QUACppSubscription : public UaClientSdk::UaSubscriptionCallback +{ +public: + QUACppSubscription(UACppAsyncBackend *backend, const QOpcUaMonitoringParameters &settings); + ~QUACppSubscription(); + + quint32 createOnServer(); + bool removeOnServer(); + + + bool addAttributeMonitoredItem(uintptr_t handle, QOpcUa::NodeAttribute attr, const UaNodeId &id, QOpcUaMonitoringParameters parameters); + void modifyMonitoring(uintptr_t handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value); + bool removeAttributeMonitoredItem(uintptr_t handle, QOpcUa::NodeAttribute attr); + + double interval() const; + quint32 subscriptionId() const; + int monitoredItemsCount() const; + + QOpcUaMonitoringParameters::SubscriptionType shared() const; + + // UaSubscription overrides + void subscriptionStatusChanged(OpcUa_UInt32 clientSubscriptionHandle, const UaStatus &status) override; + void dataChange(OpcUa_UInt32 clientSubscriptionHandle, const UaMonitoredItemNotifications &dataNotifications, const UaDiagnosticInfos &diagnosticInfos) override; + void newEvents(OpcUa_UInt32 clientSubscriptionHandle, UaEventFieldLists &eventFieldList) override; +private: + UACppAsyncBackend *m_backend; + QOpcUaMonitoringParameters m_parameters; + UaClientSdk::UaSubscription *m_nativeSubscription; + QHash<QPair<uintptr_t, QOpcUa::NodeAttribute>, OpcUa_MonitoredItemCreateResult> m_monitoredItems; + QHash<quint32, QPair<uintptr_t, QOpcUa::NodeAttribute>> m_monitoredIds; +}; + +QT_END_NAMESPACE + +#endif // QUACPPSUBSCRIPTION_H diff --git a/src/plugins/opcua/uacpp/quacpputils.cpp b/src/plugins/opcua/uacpp/quacpputils.cpp new file mode 100644 index 0000000..0b753d2 --- /dev/null +++ b/src/plugins/opcua/uacpp/quacpputils.cpp @@ -0,0 +1,170 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#include "quacpputils.h" + +#include <QtCore/QLoggingCategory> +#include <QtCore/QString> +#include <QtCore/QUuid> + +#include <uaguid.h> +#include <cstring> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_UACPP) + +namespace UACppUtils { + +UaNodeId nodeIdFromQString(const QString &name) +{ + const int semicolonIndex = name.indexOf(';'); + + if (semicolonIndex <= 0) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "Unable to split node id string: %s", qUtf8Printable(name)); + return UaNodeId(); + } + + QStringRef namespaceString = name.leftRef(semicolonIndex); + if (namespaceString.length() <= 3 || !namespaceString.startsWith(QLatin1String("ns="))) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "Not a valid index string in node id string: %s", qUtf8Printable(name)); + return UaNodeId(); + } + namespaceString = namespaceString.mid(3); // Remove "ns=" + + QStringRef identifierString = name.midRef(semicolonIndex + 1); + + if (identifierString.length() <= 2) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "There is no identifier in node id string: %s", qUtf8Printable(name)); + return UaNodeId(); + } + + char identifierType; + if (identifierString.startsWith(QLatin1String("s="))) + identifierType = 's'; + else if (identifierString.startsWith(QLatin1String("i="))) + identifierType = 'i'; + else if (identifierString.startsWith(QLatin1String("g="))) + identifierType = 'g'; + else if (identifierString.startsWith(QLatin1String("b="))) + identifierType = 'b'; + else { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "There is no valid identifier type in node id string: %s", qUtf8Printable(name)); + return UaNodeId(); + } + identifierString = identifierString.mid(2); // Remove identifier type + + bool ok = false; + OpcUa_UInt16 index = static_cast<OpcUa_UInt16>(namespaceString.toUInt(&ok)); + + if (!ok) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "Not a valid namespace index in node id string: %s", qUtf8Printable(name)); + return UaNodeId(); + } + + switch (identifierType) { + case 'i': { + bool isNumber; + OpcUa_UInt32 identifier = static_cast<OpcUa_UInt32>(identifierString.toUInt(&isNumber)); + if (isNumber) + return UaNodeId(identifier, index); + else + qCWarning(QT_OPCUA_PLUGINS_UACPP, "%s does not contain a valid numeric identifier", qUtf8Printable(name)); + break; + } + case 's': { + if (identifierString.length() > 0) + return UaNodeId(UaString(identifierString.toUtf8().constData()), index); + else + qCWarning(QT_OPCUA_PLUGINS_UACPP, "%s does not contain a valid string identifier", qUtf8Printable(name)); + break; + } + case 'g': { + QUuid uuid(identifierString.toString()); + + if (uuid.isNull()) { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "%s does not contain a valid guid identifier", qUtf8Printable(name)); + } + + OpcUa_Guid guid; + guid.Data1 = uuid.data1; + guid.Data2 = uuid.data2; + guid.Data3 = uuid.data3; + std::memcpy(guid.Data4, uuid.data4, sizeof(uuid.data4)); + return UaNodeId(guid, index); + } + case 'b': { + QByteArray temp = QByteArray::fromBase64(identifierString.toLocal8Bit()); + UaByteString bstr((OpcUa_Int32)temp.size(), reinterpret_cast<OpcUa_Byte *>(temp.data())); + if (temp.size() > 0) { + return UaNodeId(bstr, index); + } + else + qCWarning(QT_OPCUA_PLUGINS_UACPP, "%s does not contain a valid byte string identifier", qUtf8Printable(name)); + break; + } + default: + qCWarning(QT_OPCUA_PLUGINS_UACPP, "Could not parse node id: %s", qUtf8Printable(name)); + } + return UaNodeId(); +} + +// We only need this for template<> QVariant scalarToQVariant<QString, OpcUa_NodeId>(OpcUa_NodeId *data, QMetaType::Type type) +// And also only because our unit tests assume that ns=0 has to be included in the string. Even though ns=0 +// can be assumed implicity +QString nodeIdToQString(const UaNodeId &id) +{ + QString result = QString::fromLatin1("ns=%1;").arg(id.namespaceIndex()); + + switch (id.identifierType()) { + case OpcUa_IdentifierType_Numeric: + result.append(QString::fromLatin1("i=%1").arg(id.identifierNumeric())); + break; + case OpcUa_IdentifierType_String: { + result.append(QLatin1String("s=")); + const UaString identifier(id.identifierString()); + result.append(QString::fromUtf8(identifier.toUtf8(), identifier.size())); + break; + } + case OpcUa_IdentifierType_Guid: { + OpcUa_Guid *uaguid = (*id).Identifier.Guid; + const QUuid uuid(uaguid->Data1, uaguid->Data2, uaguid->Data3, + uaguid->Data4[0], uaguid->Data4[1], uaguid->Data4[2], uaguid->Data4[3], + uaguid->Data4[4], uaguid->Data4[5], uaguid->Data4[6], uaguid->Data4[7]); + result.append(QStringLiteral("g=")).append(uuid.toString().midRef(1, 36)); + break; + } + case OpcUa_IdentifierType_Opaque: { + const OpcUa_ByteString &uabs = (*id).Identifier.ByteString; + const QByteArray temp(reinterpret_cast<char *>(uabs.Data), uabs.Length); + result.append(QStringLiteral("b=")).append(temp.toBase64()); + break; + } + default: + qCWarning(QT_OPCUA_PLUGINS_UACPP, "UACpp Utils: Could not convert UA_NodeId to QString"); + result.clear(); + } + return result; +} + +} // namespace UaCppUtils + +QT_END_NAMESPACE diff --git a/src/plugins/opcua/uacpp/quacpputils.h b/src/plugins/opcua/uacpp/quacpputils.h new file mode 100644 index 0000000..8358acc --- /dev/null +++ b/src/plugins/opcua/uacpp/quacpputils.h @@ -0,0 +1,38 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#ifndef QUACPPUTILS_H +#define QUACPPUTILS_H + +#include <QtCore/qstring.h> + +#include <uanodeid.h> + +QT_BEGIN_NAMESPACE + +namespace UACppUtils { + UaNodeId nodeIdFromQString(const QString &name); + QString nodeIdToQString(const UaNodeId &id); +} + +QT_END_NAMESPACE + +#endif // QUACPPUTILS_H diff --git a/src/plugins/opcua/uacpp/quacppvalueconverter.cpp b/src/plugins/opcua/uacpp/quacppvalueconverter.cpp new file mode 100644 index 0000000..b9b82ff --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppvalueconverter.cpp @@ -0,0 +1,889 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#include "quacppvalueconverter.h" +#include "quacpputils.h" + +#include <private/qopcuabinarydataencoding_p.h> + +#include <QtCore/QDateTime> +#include <QtCore/QLoggingCategory> +#include <QtCore/QUuid> + +#include <uastring.h> +#include <ualocalizedtext.h> +#include <uadatetime.h> +#include <uaguid.h> +#include <uavariant.h> +#include <uaextensionobject.h> +#include <uaeuinformation.h> +#include <uaaxisinformation.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_UACPP) + +namespace QUACppValueConverter { + +QOpcUa::Types qvariantTypeToQOpcUaType(QMetaType::Type type) +{ + switch (type) { + case QMetaType::Bool: + return QOpcUa::Boolean; + case QMetaType::UChar: + return QOpcUa::Byte; + case QMetaType::Char: + return QOpcUa::SByte; + case QMetaType::UShort: + return QOpcUa::UInt16; + case QMetaType::Short: + return QOpcUa::Int16; + case QMetaType::Int: + return QOpcUa::Int32; + case QMetaType::UInt: + return QOpcUa::UInt32; + case QMetaType::ULongLong: + return QOpcUa::UInt64; + case QMetaType::LongLong: + return QOpcUa::Int64; + case QMetaType::Double: + return QOpcUa::Double; + case QMetaType::Float: + return QOpcUa::Float; + case QMetaType::QString: + return QOpcUa::String; + //return QOpcUa::LocalizedText; // TODO: unclear + case QMetaType::QDateTime: + return QOpcUa::DateTime; + case QMetaType::QByteArray: + return QOpcUa::ByteString; + case QMetaType::QUuid: + return QOpcUa::Guid; + //return QOpcUa::XmlElement; + //return QOpcUa::NodeId; + default: + break; + } + + return QOpcUa::Undefined; +} + +OpcUa_BuiltInType toDataType(QOpcUa::Types valueType) +{ + switch (valueType) { + case QOpcUa::Boolean: + return OpcUa_BuiltInType::OpcUaType_Boolean; + case QOpcUa::Int32: + return OpcUa_BuiltInType::OpcUaType_Int32; + case QOpcUa::UInt32: + return OpcUa_BuiltInType::OpcUaType_UInt32; + case QOpcUa::Double: + return OpcUa_BuiltInType::OpcUaType_Double; + case QOpcUa::Float: + return OpcUa_BuiltInType::OpcUaType_Float; + case QOpcUa::String: + return OpcUa_BuiltInType::OpcUaType_String; + case QOpcUa::LocalizedText: + return OpcUa_BuiltInType::OpcUaType_LocalizedText; + case QOpcUa::DateTime: + return OpcUa_BuiltInType::OpcUaType_DateTime; + case QOpcUa::UInt16: + return OpcUa_BuiltInType::OpcUaType_UInt16; + case QOpcUa::Int16: + return OpcUa_BuiltInType::OpcUaType_Int16; + case QOpcUa::UInt64: + return OpcUa_BuiltInType::OpcUaType_UInt64; + case QOpcUa::Int64: + return OpcUa_BuiltInType::OpcUaType_Int64; + case QOpcUa::Byte: + return OpcUa_BuiltInType::OpcUaType_Byte; + case QOpcUa::SByte: + return OpcUa_BuiltInType::OpcUaType_SByte; + case QOpcUa::ByteString: + return OpcUa_BuiltInType::OpcUaType_ByteString; + case QOpcUa::XmlElement: + return OpcUa_BuiltInType::OpcUaType_XmlElement; + case QOpcUa::NodeId: + return OpcUa_BuiltInType::OpcUaType_NodeId; + case QOpcUa::Guid: + return OpcUa_BuiltInType::OpcUaType_Guid; + case QOpcUa::QualifiedName: + return OpcUa_BuiltInType::OpcUaType_QualifiedName; + case QOpcUa::StatusCode: + return OpcUa_BuiltInType::OpcUaType_StatusCode; + case QOpcUa::Range: + case QOpcUa::EUInformation: + case QOpcUa::ComplexNumber: + case QOpcUa::DoubleComplexNumber: + case QOpcUa::AxisInformation: + case QOpcUa::XV: + return OpcUa_BuiltInType::OpcUaType_ExtensionObject; + default: + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Trying to convert undefined type:" << valueType; + return OpcUa_BuiltInType::OpcUaType_Null; + } +} + +template<typename TARGETTYPE, typename UATYPE> +QVariant scalarToQVariant(UATYPE *data, QMetaType::Type type) +{ + return QVariant(type, reinterpret_cast<const TARGETTYPE *>(data)); +} + +template<> +QVariant scalarToQVariant<QString, OpcUa_String>(OpcUa_String *data, QMetaType::Type type) +{ + Q_UNUSED(type) + const UaString str(data); + return QVariant(QString::fromUtf8(str.toUtf8(), str.size())); +} + +template<> +QVariant scalarToQVariant<QString, OpcUa_XmlElement>(OpcUa_XmlElement *data, QMetaType::Type type) +{ + Q_UNUSED(type); + return QVariant(QString::fromUtf8((char*)data->Data, data->Length)); +} + +template<> +QVariant scalarToQVariant<QByteArray, OpcUa_ByteString>(OpcUa_ByteString *data, QMetaType::Type type) +{ + Q_UNUSED(type) + UaByteArray ba(*data); + return QVariant(QByteArray(ba.data(), ba.size())); +} + +template<> +QVariant scalarToQVariant<QString, OpcUa_LocalizedText>(OpcUa_LocalizedText *data, QMetaType::Type type) +{ + Q_UNUSED(type) + UaLocalizedText ualt(*data); + const UaString ualtLocal(ualt.locale()); + const UaString ualtText(ualt.text()); + + QOpcUa::QLocalizedText lt; + + lt.locale = QString::fromUtf8(ualtLocal.toUtf8(), ualtLocal.size()); + lt.text = QString::fromUtf8(ualtText.toUtf8(), ualtText.size()); + return QVariant::fromValue(lt); +} + +template<> +QVariant scalarToQVariant<QOpcUa::QLocalizedText, OpcUa_LocalizedText>(OpcUa_LocalizedText *data, QMetaType::Type type) +{ + Q_UNUSED(type) + UaLocalizedText ualt(*data); + const UaString ualtLocal(ualt.locale()); + const UaString ualtText(ualt.text()); + + QOpcUa::QLocalizedText lt; + + lt.locale = QString::fromUtf8(ualtLocal.toUtf8(), ualtLocal.size()); + lt.text = QString::fromUtf8(ualtText.toUtf8(), ualtText.size()); + return QVariant::fromValue(lt); +} + +template<> +QVariant scalarToQVariant<QString, OpcUa_NodeId>(OpcUa_NodeId *data, QMetaType::Type type) +{ + Q_UNUSED(type) + const UaNodeId id(*data); + return QVariant(UACppUtils::nodeIdToQString(id)); +} + +template<> +QVariant scalarToQVariant<QDateTime, OpcUa_DateTime>(OpcUa_DateTime *data, QMetaType::Type type) +{ + Q_UNUSED(type) + const UaDateTime dt(*data); + return QVariant(QDateTime::fromMSecsSinceEpoch(dt)); +} + +template<> +QVariant scalarToQVariant<QUuid, OpcUa_Guid>(OpcUa_Guid *data, QMetaType::Type type) +{ + Q_UNUSED(type) + return QUuid(data->Data1, data->Data2, data->Data3, + data->Data4[0], data->Data4[1], data->Data4[2], data->Data4[3], + data->Data4[4], data->Data4[5], data->Data4[6], data->Data4[7]); +} + +template<> +QVariant scalarToQVariant<QOpcUa::QQualifiedName, OpcUa_QualifiedName>(OpcUa_QualifiedName *data, QMetaType::Type type) +{ + Q_UNUSED(type); + QOpcUa::QQualifiedName temp; + temp.namespaceIndex = data->NamespaceIndex; + temp.name = scalarToQVariant<QString, OpcUa_String>(&data->Name, QMetaType::Type::QString).toString(); + return QVariant::fromValue(temp); +} + +inline QOpcUa::QEUInformation UaEUInformationToQEUInformation(const UaEUInformation &info) +{ + const UaLocalizedText desc = info.getDescription(); + const UaLocalizedText dispName = info.getDisplayName(); + const UaString namespaceUri = info.getNamespaceUri(); + + quint32 qunitId = info.getUnitId(); + QOpcUa::QLocalizedText qDesc = scalarToQVariant<QOpcUa::QLocalizedText, OpcUa_LocalizedText>(const_cast<OpcUa_LocalizedText *>(&*desc)).value<QOpcUa::QLocalizedText>(); + QOpcUa::QLocalizedText qDisp = scalarToQVariant<QOpcUa::QLocalizedText, OpcUa_LocalizedText>(const_cast<OpcUa_LocalizedText *>(&*dispName)).value<QOpcUa::QLocalizedText>(); + QString qNamespaceUri = QString::fromUtf8(namespaceUri.toUtf8(), namespaceUri.size()); + QOpcUa::QEUInformation euinfo(qNamespaceUri, qunitId, qDisp, qDesc); + return euinfo; +} + +template<> +QVariant scalarToQVariant<QVariant, OpcUa_ExtensionObject>(OpcUa_ExtensionObject *data, QMetaType::Type type) +{ + Q_UNUSED(type); + if (data->TypeId.NodeId.IdentifierType != OpcUa_IdentifierType_Numeric || + data->TypeId.NodeId.NamespaceIndex != 0 || + data->Encoding != OpcUa_ExtensionObjectEncoding_EncodeableObject) + { + qCWarning(QT_OPCUA_PLUGINS_UACPP, "Unsupported extension object type"); + return QVariant(); + } + + const char *buffer = reinterpret_cast<const char *>(data->Body.EncodeableObject.Object); + size_t length = data->Body.EncodeableObject.Type->AllocationSize; + + bool success = true; + QVariant result; + QOpcUaBinaryDataEncoding::TypeEncodingId objType = static_cast<QOpcUaBinaryDataEncoding::TypeEncodingId>(data->TypeId.NodeId.Identifier.Numeric); + switch (objType) { + case QOpcUaBinaryDataEncoding::TypeEncodingId::EUInformation: { + // ### TODO: Check for non ns=0 cases. Apparently there is an auto-conversion happening in the + // underlaying OpcUA SDK for ns=0 nodes, hence we do not receive a ByteStream, but rather an + // already decoded object. For non ns=0, we might be able to use the generic decoder from the module. + // Same for AxisInformation + UaEUInformation info(*data); + QOpcUa::QEUInformation euinfo = UaEUInformationToQEUInformation(info); + result = QVariant::fromValue(euinfo); + break; + } + case QOpcUaBinaryDataEncoding::TypeEncodingId::Range: + result = QVariant::fromValue(QOpcUaBinaryDataEncoding::decode<QOpcUa::QRange>(buffer, length, success)); + break; + case QOpcUaBinaryDataEncoding::TypeEncodingId::ComplexNumber: + result = QVariant::fromValue(QOpcUaBinaryDataEncoding::decode<QOpcUa::QComplexNumber>(buffer, length, success)); + break; + case QOpcUaBinaryDataEncoding::TypeEncodingId::DoubleComplexNumber: + result = QVariant::fromValue(QOpcUaBinaryDataEncoding::decode<QOpcUa::QDoubleComplexNumber>(buffer, length, success)); + break; + case QOpcUaBinaryDataEncoding::TypeEncodingId::AxisInformation: { + UaAxisInformation info(*data); + const UaRange uaRange = info.getEURange(); + const UaLocalizedText uaTitle = info.getTitle(); + UaDoubleArray uaDoubleArray; + info.getAxisSteps(uaDoubleArray); + + const QOpcUa::QEUInformation qEuInfo = UaEUInformationToQEUInformation(info.getEngineeringUnits()); + const QOpcUa::QRange qRange(uaRange.getLow(), uaRange.getHigh()); + const QOpcUa::QLocalizedText qTitle = scalarToQVariant<QOpcUa::QLocalizedText, OpcUa_LocalizedText>(const_cast<OpcUa_LocalizedText *>(&*uaTitle)).value<QOpcUa::QLocalizedText>();; + const QOpcUa::AxisScale qScale = static_cast<QOpcUa::AxisScale>(info.getAxisScaleType()); + QVector<double> qAxisSteps; + for (OpcUa_UInt32 i = 0; i < uaDoubleArray.length(); ++i) + qAxisSteps.append(uaDoubleArray[i]); + + QOpcUa::QAxisInformation qAxisInfo(qEuInfo, qRange, qTitle, qScale, qAxisSteps); + result = QVariant::fromValue(qAxisInfo); + break; + } + case QOpcUaBinaryDataEncoding::TypeEncodingId::XV: + result = QVariant::fromValue(QOpcUaBinaryDataEncoding::decode<QOpcUa::QXValue>(buffer, length, success)); + break; + default: + qCWarning(QT_OPCUA_PLUGINS_UACPP, "Unknown extension object type: ns=0;i=%d", data->TypeId.NodeId.Identifier.Numeric); + return QVariant(); + } + + if (success) + return result; + else + return QVariant(); +} + +template<typename TARGETTYPE, typename UATYPE> +QVariant arrayToQVariant(const OpcUa_Variant &var, QMetaType::Type type) +{ + if (var.ArrayType == OpcUa_VariantArrayType_Array) { + QVariantList list; + for (OpcUa_Int32 i = 0; i < var.Value.Array.Length; ++i) { + UATYPE *temp = (UATYPE *)var.Value.Array.Value.Array; + list.append(scalarToQVariant<TARGETTYPE, UATYPE>(&temp[i], type)); + } + if (list.size() == 1) + return list.at(0); + else + return list; + } + UATYPE *temp = (UATYPE *)&var.Value; + return scalarToQVariant<TARGETTYPE, UATYPE>(temp, type); +} + +// We need specializations for OpcUa_Guid, OpcUa_QualifiedName, OpcUa_NodeId, OpcUa_LocalizedText +// and OpcUA_ExtensionObject as the scalar versions contain pointers to data, all the others +// contain the data itself. Hence, there is a difference between the array and scalar version +template<> +QVariant arrayToQVariant<QString, OpcUa_LocalizedText *>(const OpcUa_Variant &var, QMetaType::Type type) +{ + if (var.ArrayType == OpcUa_VariantArrayType_Array) { + QVariantList list; + for (OpcUa_Int32 i = 0; i < var.Value.Array.Length; ++i) { + OpcUa_LocalizedText *temp = var.Value.Array.Value.LocalizedTextArray; + list.append(scalarToQVariant<QString, OpcUa_LocalizedText>(&temp[i], type)); + } + if (list.size() == 1) + return list.at(0); + else + return list; + } + OpcUa_LocalizedText *temp = var.Value.LocalizedText; + return scalarToQVariant<QString, OpcUa_LocalizedText>(temp, type); +} + +template<> +QVariant arrayToQVariant<QString, OpcUa_NodeId *>(const OpcUa_Variant &var, QMetaType::Type type) +{ + if (var.ArrayType == OpcUa_VariantArrayType_Array) { + QVariantList list; + for (OpcUa_Int32 i = 0; i < var.Value.Array.Length; ++i) { + OpcUa_NodeId *temp = var.Value.Array.Value.NodeIdArray; + list.append(scalarToQVariant<QString, OpcUa_NodeId>(&temp[i], type)); + } + if (list.size() == 1) + return list.at(0); + else + return list; + } + OpcUa_NodeId *temp = var.Value.NodeId; + return scalarToQVariant<QString, OpcUa_NodeId>(temp, type); +} + +template<> +QVariant arrayToQVariant<QUuid, OpcUa_Guid *>(const OpcUa_Variant &var, QMetaType::Type type) +{ + if (var.ArrayType == OpcUa_VariantArrayType_Array) { + QVariantList list; + for (OpcUa_Int32 i = 0; i < var.Value.Array.Length; ++i) { + OpcUa_Guid *temp = var.Value.Array.Value.GuidArray; + list.append(scalarToQVariant<QUuid, OpcUa_Guid>(&temp[i], type)); + } + if (list.size() == 1) + return list.at(0); + else + return list; + } + OpcUa_Guid *temp = var.Value.Guid; + return scalarToQVariant<QUuid, OpcUa_Guid>(temp, type); +} + +template<> +QVariant arrayToQVariant<QOpcUa::QQualifiedName, OpcUa_QualifiedName *>(const OpcUa_Variant &var, QMetaType::Type type) +{ + if (var.ArrayType == OpcUa_VariantArrayType_Array) { + QVariantList list; + for (OpcUa_Int32 i = 0; i < var.Value.Array.Length; ++i) { + OpcUa_QualifiedName *temp = var.Value.Array.Value.QualifiedNameArray; + list.append(scalarToQVariant<QOpcUa::QQualifiedName, OpcUa_QualifiedName>(&temp[i], type)); + } + if (list.size() == 1) + return list.at(0); + else + return list; + } + OpcUa_QualifiedName *temp = var.Value.QualifiedName; + return scalarToQVariant<QOpcUa::QQualifiedName, OpcUa_QualifiedName>(temp, type); +} + +template<> +QVariant arrayToQVariant<QVariant, OpcUa_ExtensionObject *>(const OpcUa_Variant &var, QMetaType::Type type) +{ + if (var.ArrayType == OpcUa_VariantArrayType_Array) { + QVariantList list; + for (OpcUa_Int32 i = 0; i < var.Value.Array.Length; ++i) { + OpcUa_ExtensionObject *temp = var.Value.Array.Value.ExtensionObjectArray; + list.append(scalarToQVariant<QVariant, OpcUa_ExtensionObject>(&temp[i], type)); + } + if (list.size() == 1) + return list.at(0); + else + return list; + } + OpcUa_ExtensionObject *temp = var.Value.ExtensionObject; + return scalarToQVariant<QVariant, OpcUa_ExtensionObject>(temp, type); +} + +template<typename TARGETTYPE, typename QTTYPE> +void scalarFromQVariant(const QVariant &var, TARGETTYPE *ptr) +{ + *ptr = static_cast<TARGETTYPE>(var.value<QTTYPE>()); +} + +template<> +void scalarFromQVariant<OpcUa_DateTime, QDateTime>(const QVariant &var, OpcUa_DateTime *ptr) +{ + const UaDateTime dt = var.toDateTime().toMSecsSinceEpoch(); + *ptr = dt; +} + +template<> +void scalarFromQVariant<OpcUa_String, QString>(const QVariant &var, OpcUa_String *ptr) +{ + UaString str(var.toString().toUtf8().constData()); + str.copyTo(ptr); +} + +template<> +void scalarFromQVariant<OpcUa_LocalizedText, QString>(const QVariant &var, OpcUa_LocalizedText *ptr) +{ + QOpcUa::QLocalizedText lt = var.canConvert<QOpcUa::QLocalizedText>() + ? var.value<QOpcUa::QLocalizedText>() + : QOpcUa::QLocalizedText(QLatin1String(""), var.value<QString>()); + + UaLocalizedText ualt; + if (lt.locale.size()) + ualt.setLocale(UaString(lt.locale.toUtf8().constData())); + if (lt.text.size()) + ualt.setText(UaString(lt.text.toUtf8().constData())); + ualt.copyTo(ptr); +} + +template<> +void scalarFromQVariant<OpcUa_ByteString, QByteArray>(const QVariant &var, OpcUa_ByteString *ptr) +{ + QByteArray arr = var.toByteArray(); + UaByteString str(arr.length(), (OpcUa_Byte*)arr.data()); + str.copyTo(ptr); +} + +template<> +void scalarFromQVariant<OpcUa_XmlElement, QString>(const QVariant &var, OpcUa_XmlElement *ptr) +{ + scalarFromQVariant<OpcUa_ByteString, QByteArray>(var, ptr); +} + +template<> +void scalarFromQVariant<OpcUa_NodeId, QString>(const QVariant &var, OpcUa_NodeId *ptr) +{ + UaNodeId id = UaNodeId::fromXmlString(UaString(var.toString().toUtf8().constData())); + id.copyTo(ptr); +} + +template<> +void scalarFromQVariant<OpcUa_QualifiedName, QOpcUa::QQualifiedName>(const QVariant &var, OpcUa_QualifiedName *ptr) +{ + QOpcUa::QQualifiedName temp = var.value<QOpcUa::QQualifiedName>(); + ptr->NamespaceIndex = temp.namespaceIndex; + const UaString name(temp.name.toUtf8().constData()); + name.copyTo(&ptr->Name); +} + +template<> +void scalarFromQVariant<OpcUa_Guid, QUuid>(const QVariant &var, OpcUa_Guid *ptr) +{ + const QUuid uuid = var.toUuid(); + ptr->Data1 = uuid.data1; + ptr->Data2 = uuid.data2; + ptr->Data3 = uuid.data3; + memcpy(ptr->Data4, uuid.data4, sizeof(uuid.data4)); +} + +void createExtensionObject(QByteArray &data, QOpcUaBinaryDataEncoding::TypeEncodingId id, OpcUa_ExtensionObject *ptr) +{ + OpcUa_ExtensionObject *opcuaObj; + OpcUa_ExtensionObject_Create(&opcuaObj); + opcuaObj->Encoding = OpcUa_ExtensionObjectEncoding_Binary; + opcuaObj->Body.Binary.Data = reinterpret_cast<OpcUa_Byte *>(data.data()); + opcuaObj->Body.Binary.Length = data.size(); + opcuaObj->BodySize = data.length(); + const UaNodeId temp(static_cast<OpcUa_UInt32>(id)); + temp.copyTo(&opcuaObj->TypeId.NodeId); + OpcUa_ExtensionObject_CopyTo(opcuaObj, ptr); + //OpcUa_ExtensionObject_Clear(opcuaObj); +} + +template<> +void scalarFromQVariant<OpcUa_ExtensionObject, QOpcUa::QRange>(const QVariant &var, OpcUa_ExtensionObject *ptr) +{ + const QOpcUa::QRange range = var.value<QOpcUa::QRange>(); + QByteArray temp; + QOpcUaBinaryDataEncoding::encode<QOpcUa::QRange>(range, temp); + return createExtensionObject(temp, QOpcUaBinaryDataEncoding::TypeEncodingId::Range, ptr); +} + +template<> +void scalarFromQVariant<OpcUa_ExtensionObject, QOpcUa::QEUInformation>(const QVariant &var, OpcUa_ExtensionObject *ptr) +{ + const QOpcUa::QEUInformation info = var.value<QOpcUa::QEUInformation>(); + QByteArray temp; + QOpcUaBinaryDataEncoding::encode<QOpcUa::QEUInformation>(info, temp); + return createExtensionObject(temp, QOpcUaBinaryDataEncoding::TypeEncodingId::EUInformation, ptr); +} + +template<> +void scalarFromQVariant<OpcUa_ExtensionObject, QOpcUa::QComplexNumber>(const QVariant &var, OpcUa_ExtensionObject *ptr) +{ + const QOpcUa::QComplexNumber num = var.value<QOpcUa::QComplexNumber>(); + QByteArray temp; + QOpcUaBinaryDataEncoding::encode<QOpcUa::QComplexNumber>(num, temp); + return createExtensionObject(temp, QOpcUaBinaryDataEncoding::TypeEncodingId::ComplexNumber, ptr); +} + +template<> +void scalarFromQVariant<OpcUa_ExtensionObject, QOpcUa::QDoubleComplexNumber>(const QVariant &var, OpcUa_ExtensionObject *ptr) +{ + const QOpcUa::QDoubleComplexNumber num = var.value<QOpcUa::QDoubleComplexNumber>(); + QByteArray temp; + QOpcUaBinaryDataEncoding::encode<QOpcUa::QDoubleComplexNumber>(num, temp); + return createExtensionObject(temp, QOpcUaBinaryDataEncoding::TypeEncodingId::DoubleComplexNumber, ptr); +} + +template<> +void scalarFromQVariant<OpcUa_ExtensionObject, QOpcUa::QAxisInformation>(const QVariant &var, OpcUa_ExtensionObject *ptr) +{ + const QOpcUa::QAxisInformation num = var.value<QOpcUa::QAxisInformation>(); + QByteArray temp; + QOpcUaBinaryDataEncoding::encode<QOpcUa::QAxisInformation>(num, temp); + return createExtensionObject(temp, QOpcUaBinaryDataEncoding::TypeEncodingId::AxisInformation, ptr); +} + +template<> +void scalarFromQVariant<OpcUa_ExtensionObject, QOpcUa::QXValue>(const QVariant &var, OpcUa_ExtensionObject *ptr) +{ + const QOpcUa::QXValue num = var.value<QOpcUa::QXValue>(); + QByteArray temp; + QOpcUaBinaryDataEncoding::encode<QOpcUa::QXValue>(num, temp); + return createExtensionObject(temp, QOpcUaBinaryDataEncoding::TypeEncodingId::XV, ptr); +} + +template<typename TARGETTYPE, typename QTTYPE> +OpcUa_Variant arrayFromQVariant(const QVariant &var, const OpcUa_BuiltInType type) +{ + OpcUa_Variant opcuavariant; + OpcUa_Variant_Initialize(&opcuavariant); + + if (var.type() == QVariant::List) { + const QVariantList list = var.toList(); + if (list.isEmpty()) + return opcuavariant; + + opcuavariant.Datatype = type; + opcuavariant.ArrayType = OpcUa_True; + opcuavariant.Value.Array.Length = list.size(); + TARGETTYPE *arr = new TARGETTYPE[list.size()]; + opcuavariant.Value.Array.Value.Array = static_cast<OpcUa_Void *>(arr); + + for (int i = 0; i < list.size(); ++i) + scalarFromQVariant<TARGETTYPE, QTTYPE>(list[i], &arr[i]); + + return opcuavariant; + } + + TARGETTYPE *temp = (TARGETTYPE*)&opcuavariant.Value; + scalarFromQVariant<TARGETTYPE, QTTYPE>(var, temp); + opcuavariant.Datatype = type; + return opcuavariant; +} + +// We need specializations for OpcUa_Guid, OpcUa_QualifiedName, OpcUa_NodeId, OpcUa_LocalizedText +// and OpcUa_ExtensionObject as the scalar versions contain pointers to data, all the others +// contain the data itself. Hence, there is a difference between the array and scalar version +template<typename TARGETTYPE, typename QTTYPE> +OpcUa_Variant arrayFromQVariantPointer(const QVariant &var, const OpcUa_BuiltInType type) +{ + OpcUa_Variant opcuavariant; + OpcUa_Variant_Initialize(&opcuavariant); + + if (var.type() == QVariant::List) { + const QVariantList list = var.toList(); + if (list.isEmpty()) + return opcuavariant; + + opcuavariant.Datatype = type; + opcuavariant.ArrayType = OpcUa_True; + opcuavariant.Value.Array.Length = list.size(); + TARGETTYPE *arr = new TARGETTYPE[list.size()]; + opcuavariant.Value.Array.Value.Array = arr; + + for (int i = 0; i < list.size(); ++i) + scalarFromQVariant<TARGETTYPE, QTTYPE>(list[i], &arr[i]); + + return opcuavariant; + } + + // Taking one pointer for all as it is union + TARGETTYPE **temp = reinterpret_cast<TARGETTYPE **>(&opcuavariant.Value.Guid); + // We have to allocate, otherwise copyTo() will not do any action + *temp = new TARGETTYPE; + scalarFromQVariant<TARGETTYPE, QTTYPE>(var, *temp); + opcuavariant.Datatype = type; + return opcuavariant; +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_Guid, QUuid>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_Guid, QUuid>(var, type); +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_QualifiedName, QOpcUa::QQualifiedName>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_QualifiedName, QOpcUa::QQualifiedName>(var, type); +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_NodeId, QString>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_NodeId, QString>(var, type); +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_LocalizedText, QString>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_LocalizedText, QString>(var, type); +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QRange>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_ExtensionObject, QOpcUa::QRange>(var, type); +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QEUInformation>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_ExtensionObject, QOpcUa::QEUInformation>(var, type); +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QComplexNumber>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_ExtensionObject, QOpcUa::QComplexNumber>(var, type); +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QDoubleComplexNumber>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_ExtensionObject, QOpcUa::QDoubleComplexNumber>(var, type); +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QAxisInformation>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_ExtensionObject, QOpcUa::QAxisInformation>(var, type); +} + +template<> +OpcUa_Variant arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QXValue>(const QVariant &var, const OpcUa_BuiltInType type) +{ + return arrayFromQVariantPointer<OpcUa_ExtensionObject, QOpcUa::QXValue>(var, type); +} + +QVariant toQVariant(const OpcUa_Variant &value) +{ + switch (value.Datatype) { + case OpcUa_BuiltInType::OpcUaType_Boolean: + return arrayToQVariant<bool, OpcUa_Boolean>(value, QMetaType::Bool); + case OpcUa_BuiltInType::OpcUaType_SByte: + return arrayToQVariant<signed char, OpcUa_SByte>(value, QMetaType::SChar); + case OpcUa_BuiltInType::OpcUaType_Byte: + return arrayToQVariant<uchar, OpcUa_Byte>(value, QMetaType::UChar); + case OpcUa_BuiltInType::OpcUaType_Int16: + return arrayToQVariant<qint16, OpcUa_Int16>(value, QMetaType::Short); + case OpcUa_BuiltInType::OpcUaType_UInt16: + return arrayToQVariant<quint16, OpcUa_UInt16>(value, QMetaType::UShort); + case OpcUa_BuiltInType::OpcUaType_Int32: + return arrayToQVariant<qint32, OpcUa_Int32>(value, QMetaType::Int); + case OpcUa_BuiltInType::OpcUaType_UInt32: + return arrayToQVariant<quint32, OpcUa_UInt32>(value, QMetaType::UInt); + case OpcUa_BuiltInType::OpcUaType_Int64: + return arrayToQVariant<int64_t, OpcUa_Int64>(value, QMetaType::LongLong); + case OpcUa_BuiltInType::OpcUaType_UInt64: + return arrayToQVariant<uint64_t, OpcUa_UInt64>(value, QMetaType::ULongLong); + case OpcUa_BuiltInType::OpcUaType_Float: + return arrayToQVariant<float, OpcUa_Float>(value, QMetaType::Float); + case OpcUa_BuiltInType::OpcUaType_Double: + return arrayToQVariant<double, OpcUa_Double>(value, QMetaType::Double); + case OpcUa_BuiltInType::OpcUaType_String: + return arrayToQVariant<QString, OpcUa_String>(value, QMetaType::QString); + case OpcUa_BuiltInType::OpcUaType_ByteString: + return arrayToQVariant<QByteArray, OpcUa_ByteString>(value, QMetaType::QByteArray); + case OpcUa_BuiltInType::OpcUaType_LocalizedText: + return arrayToQVariant<QString, OpcUa_LocalizedText *>(value, QMetaType::QString); + case OpcUa_BuiltInType::OpcUaType_NodeId: + return arrayToQVariant<QString, OpcUa_NodeId *>(value, QMetaType::QString); + case OpcUa_BuiltInType::OpcUaType_DateTime: + return arrayToQVariant<QDateTime, OpcUa_DateTime>(value, QMetaType::QDateTime); + case OpcUa_BuiltInType::OpcUaType_Guid: + return arrayToQVariant<QUuid, OpcUa_Guid *>(value, QMetaType::QUuid); + case OpcUa_BuiltInType::OpcUaType_XmlElement: + return arrayToQVariant<QString, OpcUa_XmlElement>(value, QMetaType::QString); + case OpcUa_BuiltInType::OpcUaType_QualifiedName: + return arrayToQVariant<QOpcUa::QQualifiedName, OpcUa_QualifiedName *>(value); + case OpcUa_BuiltInType::OpcUaType_StatusCode: + return arrayToQVariant<QOpcUa::UaStatusCode, OpcUa_StatusCode>(value, QMetaType::UInt); + case OpcUa_BuiltInType::OpcUaType_ExtensionObject: + return arrayToQVariant<QVariant, OpcUa_ExtensionObject *>(value); + default: + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Variant conversion from UACpp for typeIndex" << value.Datatype << " not implemented"; + return QVariant(); + } +} + +/*constexpr*/ OpcUa_UInt32 toUaAttributeId(QOpcUa::NodeAttribute attr) +{ + switch (attr) { + case QOpcUa::NodeAttribute::None: + return 0; + case QOpcUa::NodeAttribute::NodeId: + return OpcUa_Attributes_NodeId; + case QOpcUa::NodeAttribute::NodeClass: + return OpcUa_Attributes_NodeClass; + case QOpcUa::NodeAttribute::BrowseName: + return OpcUa_Attributes_BrowseName; + case QOpcUa::NodeAttribute::DisplayName: + return OpcUa_Attributes_DisplayName; + case QOpcUa::NodeAttribute::Description: + return OpcUa_Attributes_Description; + case QOpcUa::NodeAttribute::WriteMask: + return OpcUa_Attributes_WriteMask; + case QOpcUa::NodeAttribute::UserWriteMask: + return OpcUa_Attributes_UserWriteMask; + case QOpcUa::NodeAttribute::IsAbstract: + return OpcUa_Attributes_IsAbstract; + case QOpcUa::NodeAttribute::Symmetric: + return OpcUa_Attributes_Symmetric; + case QOpcUa::NodeAttribute::InverseName: + return OpcUa_Attributes_InverseName; + case QOpcUa::NodeAttribute::ContainsNoLoops: + return OpcUa_Attributes_ContainsNoLoops; + case QOpcUa::NodeAttribute::EventNotifier: + return OpcUa_Attributes_EventNotifier; + // Objects also add the EventNotifier attribute, see part 4, 5.5.1 + // ObjectType also add the IsAbstract attribute, see part 4, 5.5.2 + case QOpcUa::NodeAttribute::Value: + return OpcUa_Attributes_Value; + case QOpcUa::NodeAttribute::DataType: + return OpcUa_Attributes_DataType; + case QOpcUa::NodeAttribute::ValueRank: + return OpcUa_Attributes_ValueRank; + case QOpcUa::NodeAttribute::ArrayDimensions: + return OpcUa_Attributes_ArrayDimensions; + case QOpcUa::NodeAttribute::AccessLevel: + return OpcUa_Attributes_AccessLevel; + case QOpcUa::NodeAttribute::UserAccessLevel: + return OpcUa_Attributes_UserAccessLevel; + case QOpcUa::NodeAttribute::MinimumSamplingInterval: + return OpcUa_Attributes_MinimumSamplingInterval; + case QOpcUa::NodeAttribute::Historizing: + return OpcUa_Attributes_Historizing; + // VariableType also adds the Value, DataType, ValueRank, ArrayDimensions + // and isAbstract attributes, see part 4, 5.6.5 + case QOpcUa::NodeAttribute::Executable: + return OpcUa_Attributes_Executable; + case QOpcUa::NodeAttribute::UserExecutable: + return OpcUa_Attributes_UserExecutable; + default: + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Unknown NodeAttribute to convert:" << attr; + break; + } + return 0; +} + +OpcUa_Variant toUACppVariant(const QVariant &value, QOpcUa::Types type) +{ + OpcUa_Variant uacppvalue; + OpcUa_Variant_Initialize(&uacppvalue); + + if (value.type() == QVariant::List && value.toList().size() == 0) + return uacppvalue; + + QVariant temp = (value.type() == QVariant::List) ? value.toList().at(0) : value; + QOpcUa::Types valueType = type == QOpcUa::Undefined ? + qvariantTypeToQOpcUaType(static_cast<QMetaType::Type>(temp.type())) : type; + + const OpcUa_BuiltInType dt = toDataType(valueType); + + switch (valueType) { + case QOpcUa::Boolean: + return arrayFromQVariant<OpcUa_Boolean, bool>(value, dt); + case QOpcUa::SByte: + return arrayFromQVariant<OpcUa_SByte, char>(value, dt); + case QOpcUa::Byte: + return arrayFromQVariant<OpcUa_Byte, uchar>(value, dt); + case QOpcUa::Int16: + return arrayFromQVariant<OpcUa_Int16, qint16>(value, dt); + case QOpcUa::UInt16: + return arrayFromQVariant<OpcUa_UInt16, quint16>(value, dt); + case QOpcUa::Int32: + return arrayFromQVariant<OpcUa_Int32, qint32>(value, dt); + case QOpcUa::UInt32: + return arrayFromQVariant<OpcUa_UInt32, quint32>(value, dt); + case QOpcUa::Int64: + return arrayFromQVariant<OpcUa_Int64, int64_t>(value, dt); + case QOpcUa::UInt64: + return arrayFromQVariant<OpcUa_UInt64, uint64_t>(value, dt); + case QOpcUa::Float: + return arrayFromQVariant<OpcUa_Float, float>(value, dt); + case QOpcUa::Double: + return arrayFromQVariant<OpcUa_Double, double>(value, dt); + case QOpcUa::DateTime: + return arrayFromQVariant<OpcUa_DateTime, QDateTime>(value, dt); + case QOpcUa::String: + return arrayFromQVariant<OpcUa_String, QString>(value, dt); + case QOpcUa::LocalizedText: + return arrayFromQVariant<OpcUa_LocalizedText, QString>(value, dt); + case QOpcUa::ByteString: + return arrayFromQVariant<OpcUa_ByteString, QByteArray>(value, dt); + case QOpcUa::NodeId: + return arrayFromQVariant<OpcUa_NodeId, QString>(value, dt); + case QOpcUa::Guid: + return arrayFromQVariant<OpcUa_Guid, QUuid>(value, dt); + case QOpcUa::XmlElement: + return arrayFromQVariant<OpcUa_XmlElement, QString>(value, dt); + case QOpcUa::QualifiedName: + return arrayFromQVariant<OpcUa_QualifiedName, QOpcUa::QQualifiedName>(value, dt); + case QOpcUa::StatusCode: + return arrayFromQVariant<OpcUa_StatusCode, QOpcUa::UaStatusCode>(value, dt); + case QOpcUa::Range: + return arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QRange>(value, dt); + case QOpcUa::EUInformation: + return arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QEUInformation>(value, dt); + case QOpcUa::ComplexNumber: + return arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QComplexNumber>(value, dt); + case QOpcUa::DoubleComplexNumber: + return arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QDoubleComplexNumber>(value, dt); + case QOpcUa::AxisInformation: + return arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QAxisInformation>(value, dt); + case QOpcUa::XV: + return arrayFromQVariant<OpcUa_ExtensionObject, QOpcUa::QXValue>(value, dt); + default: + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Variant conversion to UACpp for typeIndex" << type << " not implemented"; + } + + return uacppvalue; +} + +} + +QT_END_NAMESPACE diff --git a/src/plugins/opcua/uacpp/quacppvalueconverter.h b/src/plugins/opcua/uacpp/quacppvalueconverter.h new file mode 100644 index 0000000..21ba880 --- /dev/null +++ b/src/plugins/opcua/uacpp/quacppvalueconverter.h @@ -0,0 +1,58 @@ +/****************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtOpcUa module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#ifndef QUACPPVALUECONVERTER_H +#define QUACPPVALUECONVERTER_H + +#include "qopcuanode.h" +#include "qopcuatype.h" + +#include <QtCore/QVariant> + +#include <uanodeid.h> + +QT_BEGIN_NAMESPACE + +namespace QUACppValueConverter { + QOpcUa::Types qvariantTypeToQOpcUaType(QVariant::Type type); + + /*constexpr*/ OpcUa_UInt32 toUaAttributeId(QOpcUa::NodeAttribute attr); + + OpcUa_Variant toUACppVariant(const QVariant&, QOpcUa::Types); + QVariant toQVariant(const OpcUa_Variant&); + OpcUa_BuiltInType toDataType(QOpcUa::Types valueType); + + template<typename TARGETTYPE, typename UATYPE> + QVariant scalarToQVariant(UATYPE *data, QMetaType::Type type = QMetaType::UnknownType); + + template<typename TARGETTYPE, typename UATYPE> + QVariant arrayToQVariant(const OpcUa_Variant &var, QMetaType::Type type = QMetaType::UnknownType); + + template<typename TARGETTYPE, typename QTTYPE> + void scalarFromQVariant(const QVariant &var, TARGETTYPE *ptr); + + template<typename TARGETTYPE, typename QTTYPE> + OpcUa_Variant arrayFromQVariant(const QVariant &var, const OpcUa_BuiltInType type); +} + +QT_END_NAMESPACE + +#endif // QUACPPVALUECONVERTER_H diff --git a/src/plugins/opcua/uacpp/uacpp-metadata.json b/src/plugins/opcua/uacpp/uacpp-metadata.json new file mode 100644 index 0000000..0dfd4fb --- /dev/null +++ b/src/plugins/opcua/uacpp/uacpp-metadata.json @@ -0,0 +1,7 @@ +{ + "Keys" : [ "UnifiedAutomationCPPSDK" ], + "Provider" : "UnifiedAutomationCPPSDK", + "Version" : "1.0", + "Features" : [ "client" ], + "stability" : 1 +} diff --git a/src/plugins/opcua/uacpp/uacpp.pro b/src/plugins/opcua/uacpp/uacpp.pro new file mode 100644 index 0000000..d53953c --- /dev/null +++ b/src/plugins/opcua/uacpp/uacpp.pro @@ -0,0 +1,40 @@ +TARGET = uacpp_backend +QT += core core-private opcua opcua-private network + +PLUGIN_TYPE = opcua +PLUGIN_CLASS_NAME = QUACppPlugin +load(qt_plugin) + +win32 { + CONFIG(debug, debug|release): LIBS += uaclientd.lib uabased.lib coremoduled.lib uastackd.lib uapkid.lib xmlparserd.lib + else: LIBS += uaclient.lib uabase.lib coremodule.lib uastack.lib uapki.lib xmlparser.lib + + LIBS += Crypt32.lib Ole32.lib OleAut32.lib ws2_32.lib + + # The UA SDK bundles hardcoded builds of libxml and openssl. Preferably we should get rid of + # this at some point. + LIBS += libeay32.lib libxml2.lib +} +unix { + LIBS += -luaclient -luamodule -luamodels -lcoremodule -luabase -luastack -lxmlparser -luapki -lcrypto -lssl -lxml2 +} + +HEADERS += \ + quacppbackend.h \ + quacppclient.h \ + quacppnode.h \ + quacppplugin.h \ + quacppsubscription.h \ + quacppvalueconverter.h \ + quacpputils.h + +SOURCES += \ + quacppbackend.cpp \ + quacppclient.cpp \ + quacppnode.cpp \ + quacppplugin.cpp \ + quacppsubscription.cpp \ + quacppvalueconverter.cpp \ + quacpputils.cpp + +OTHER_FILES = uacpp-metadata.json diff --git a/tests/auto/qopcuaclient/tst_client.cpp b/tests/auto/qopcuaclient/tst_client.cpp index 3366f53..494d4e7 100644 --- a/tests/auto/qopcuaclient/tst_client.cpp +++ b/tests/auto/qopcuaclient/tst_client.cpp @@ -638,7 +638,8 @@ void Tst_QOpcUaClient::dataChangeSubscription() QVector<QOpcUa::NodeAttribute> attrs; - if (opcuaClient->backend() == QLatin1String("open62541")) { + if (opcuaClient->backend() == QLatin1String("open62541") || + opcuaClient->backend() == QLatin1String("uacpp")) { QSignalSpy monitoringModifiedSpy(node.data(), &QOpcUaNode::monitoringStatusChanged); node->modifyMonitoring(QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters::Parameter::PublishingInterval, 200); @@ -670,7 +671,7 @@ void Tst_QOpcUaClient::dataChangeSubscription() 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", Continue); + 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 { |