summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaurice Kalinowski <maurice.kalinowski@qt.io>2017-12-06 13:56:52 +0100
committerMaurice Kalinowski <maurice.kalinowski@qt.io>2018-02-26 12:05:36 +0000
commite6410a0fed4c7f43df28dfbf6e02e6db0e145971 (patch)
tree7de4dd23a97729d86a725cdcc7896c79b718547b
parented0a475529f2ec7be427900b08dfedb1d47e7c9a (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>
-rw-r--r--config.tests/uacpp/main.cpp53
-rw-r--r--config.tests/uacpp/uacpp.pro6
-rw-r--r--src/opcua/configure.json24
-rw-r--r--src/plugins/opcua/opcua.pro4
-rw-r--r--src/plugins/opcua/uacpp/quacppbackend.cpp453
-rw-r--r--src/plugins/opcua/uacpp/quacppbackend.h76
-rw-r--r--src/plugins/opcua/uacpp/quacppclient.cpp80
-rw-r--r--src/plugins/opcua/uacpp/quacppclient.h56
-rw-r--r--src/plugins/opcua/uacpp/quacppnode.cpp136
-rw-r--r--src/plugins/opcua/uacpp/quacppnode.h61
-rw-r--r--src/plugins/opcua/uacpp/quacppplugin.cpp54
-rw-r--r--src/plugins/opcua/uacpp/quacppplugin.h51
-rw-r--r--src/plugins/opcua/uacpp/quacppsubscription.cpp351
-rw-r--r--src/plugins/opcua/uacpp/quacppsubscription.h66
-rw-r--r--src/plugins/opcua/uacpp/quacpputils.cpp170
-rw-r--r--src/plugins/opcua/uacpp/quacpputils.h38
-rw-r--r--src/plugins/opcua/uacpp/quacppvalueconverter.cpp889
-rw-r--r--src/plugins/opcua/uacpp/quacppvalueconverter.h58
-rw-r--r--src/plugins/opcua/uacpp/uacpp-metadata.json7
-rw-r--r--src/plugins/opcua/uacpp/uacpp.pro40
-rw-r--r--tests/auto/qopcuaclient/tst_client.cpp5
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 {