summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRainer Keller <Rainer.Keller@qt.io>2019-05-09 14:35:04 +0200
committerRainer Keller <Rainer.Keller@qt.io>2019-05-20 08:14:06 +0200
commit03672e4d7801cc927f68083f0d0bbceb82c4ed20 (patch)
tree72696ca73fe358136a0569d0f47b1da8469e2428
parent682e5f43de559bfcadd82b87c1e67251784a4ecd (diff)
qml: Support for setting up a connection in C++
Change-Id: I2b00407ddfa3993e579a5d556241a5483d1529c9 Reviewed-by: Jannis Völker <jannis.voelker@basyskom.com>
-rw-r--r--src/imports/opcua/opcuaconnection.cpp104
-rw-r--r--src/imports/opcua/opcuaconnection.h8
-rw-r--r--tests/auto/auto.pro2
-rw-r--r--tests/auto/clientSetupInCpp/clientSetupInCpp.pro11
-rw-r--r--tests/auto/clientSetupInCpp/tst_clientSetupInCpp.cpp191
-rw-r--r--tests/auto/clientSetupInCpp/tst_clientSetupInCpp.qml82
6 files changed, 379 insertions, 19 deletions
diff --git a/src/imports/opcua/opcuaconnection.cpp b/src/imports/opcua/opcuaconnection.cpp
index 163fc14..3454520 100644
--- a/src/imports/opcua/opcuaconnection.cpp
+++ b/src/imports/opcua/opcuaconnection.cpp
@@ -213,6 +213,45 @@ QT_BEGIN_NAMESPACE
*/
+/*!
+ \qmlproperty QOpcUaClient Connection::connection
+ \since 5.13
+
+ This property is used only to inject a connection from C++. In case of complex setup of
+ a connection you can use C++ to handle all the details. After the connection is established
+ it can be handed to QML using this property. Ownership of the client is transferred to QML.
+
+ \code
+ class MyClass : public QObject {
+ Q_OBJECT
+ Q_PROPERTY(QOpcUaClient* connection READ connection NOTIFY connectionChanged)
+
+ public:
+ MyClass (QObject* parent = nullptr);
+ QOpcUaClient *connection() const;
+
+ signals:
+ void connectionChanged(QOpcUaClient *);
+ \endcode
+
+ Emitting the signal \c connectionChanged when the client setup is completed, the QML code below will
+ use the connection.
+
+ \code
+ import QtOpcUa 5.13 as QtOpcUa
+
+ MyClass {
+ id: myclass
+ }
+
+ QtOpcUa.Connection {
+ connection: myclass.connection
+ }
+ \endcode
+
+*/
+
+
Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML)
OpcUaConnection* OpcUaConnection::m_defaultConnection = nullptr;
@@ -225,10 +264,7 @@ OpcUaConnection::OpcUaConnection(QObject *parent):
OpcUaConnection::~OpcUaConnection()
{
setDefaultConnection(false);
- if (m_client) {
- m_client->deleteLater();
- m_client = nullptr;
- }
+ removeConnection();
}
QStringList OpcUaConnection::availableBackends() const
@@ -256,26 +292,14 @@ void OpcUaConnection::setBackend(const QString &name)
if (m_client->backend() == name)
return;
- m_client->disconnectFromEndpoint();
- m_client->disconnect(this);
- m_client->deleteLater();
+ removeConnection();
}
QOpcUaProvider provider;
m_client = provider.createClient(name);
if (m_client) {
qCDebug(QT_OPCUA_PLUGINS_QML) << "Created plugin" << m_client->backend();
- connect(m_client, &QOpcUaClient::stateChanged, this, &OpcUaConnection::clientStateHandler);
- connect(m_client, &QOpcUaClient::namespaceArrayUpdated, this, &OpcUaConnection::namespacesChanged);
- connect(m_client, &QOpcUaClient::namespaceArrayUpdated, this, [&]() {
- if (!m_connected) {
- m_connected = true;
- emit connectedChanged();
- }
- });
- m_client->setNamespaceAutoupdate(true);
- connect(m_client, &QOpcUaClient::readNodeAttributesFinished, this, &OpcUaConnection::handleReadNodeAttributesFinished);
- connect(m_client, &QOpcUaClient::writeNodeAttributesFinished, this, &OpcUaConnection::handleWriteNodeAttributesFinished);
+ setupConnection();
} else {
qCWarning(QT_OPCUA_PLUGINS_QML) << tr("Backend '%1' could not be created.").arg(name);
}
@@ -374,6 +398,15 @@ void OpcUaConnection::setAuthenticationInformation(const QOpcUaAuthenticationInf
m_client->setAuthenticationInformation(authenticationInformation);
}
+void OpcUaConnection::setConnection(QOpcUaClient *client)
+{
+ if (!client)
+ return;
+ removeConnection();
+ m_client = client;
+ setupConnection();
+}
+
QOpcUaAuthenticationInformation OpcUaConnection::authenticationInformation() const
{
if (!m_client)
@@ -580,6 +613,11 @@ QJSValue OpcUaConnection::supportedUserTokenTypes() const
return returnValue;
}
+QOpcUaClient *OpcUaConnection::connection() const
+{
+ return m_client;
+}
+
void OpcUaConnection::handleReadNodeAttributesFinished(const QVector<QOpcUaReadResult> &results)
{
QVariantList returnValue;
@@ -600,4 +638,34 @@ void OpcUaConnection::handleWriteNodeAttributesFinished(const QVector<QOpcUaWrit
emit writeNodeAttributesFinished(QVariant::fromValue(returnValue));
}
+void OpcUaConnection::removeConnection()
+{
+ if (m_client) {
+ m_client->disconnect(this);
+ m_client->disconnectFromEndpoint();
+ if (!m_client->parent()) {
+ m_client->deleteLater();
+ }
+ m_client = nullptr;
+ }
+}
+
+void OpcUaConnection::setupConnection()
+{
+ connect(m_client, &QOpcUaClient::stateChanged, this, &OpcUaConnection::clientStateHandler);
+ connect(m_client, &QOpcUaClient::namespaceArrayUpdated, this, &OpcUaConnection::namespacesChanged);
+ connect(m_client, &QOpcUaClient::namespaceArrayUpdated, this, [&]() {
+ if (!m_connected) {
+ m_connected = true;
+ emit connectedChanged();
+ }
+ });
+ m_client->setNamespaceAutoupdate(true);
+ connect(m_client, &QOpcUaClient::readNodeAttributesFinished, this, &OpcUaConnection::handleReadNodeAttributesFinished);
+ connect(m_client, &QOpcUaClient::writeNodeAttributesFinished, this, &OpcUaConnection::handleWriteNodeAttributesFinished);
+ m_connected = (!m_client->namespaceArray().isEmpty() && m_client->state() == QOpcUaClient::Connected);
+ if (m_connected)
+ emit connectedChanged();
+}
+
QT_END_NAMESPACE
diff --git a/src/imports/opcua/opcuaconnection.h b/src/imports/opcua/opcuaconnection.h
index a072a65..4fdd2ed 100644
--- a/src/imports/opcua/opcuaconnection.h
+++ b/src/imports/opcua/opcuaconnection.h
@@ -61,6 +61,7 @@ class OpcUaConnection : public QObject
Q_PROPERTY(QStringList supportedSecurityPolicies READ supportedSecurityPolicies CONSTANT)
Q_PROPERTY(QJSValue supportedUserTokenTypes READ supportedUserTokenTypes CONSTANT)
Q_PROPERTY(QOpcUaEndpointDescription currentEndpoint READ currentEndpoint)
+ Q_PROPERTY(QOpcUaClient* connection READ connection WRITE setConnection NOTIFY connectionChanged)
public:
OpcUaConnection(QObject *parent = nullptr);
@@ -82,11 +83,14 @@ public:
QStringList supportedSecurityPolicies() const;
QJSValue supportedUserTokenTypes() const;
+ QOpcUaClient *connection() const;
+
public slots:
void connectToEndpoint(const QOpcUaEndpointDescription &endpointDescription);
void disconnectFromEndpoint();
void setDefaultConnection(bool defaultConnection = true);
void setAuthenticationInformation(const QOpcUaAuthenticationInformation &authenticationInformation);
+ void setConnection(QOpcUaClient *client);
signals:
void availableBackendsChanged();
@@ -96,6 +100,7 @@ signals:
void namespacesChanged();
void readNodeAttributesFinished(const QVariant &value);
void writeNodeAttributesFinished(const QVariant &value);
+ void connectionChanged();
private slots:
void clientStateHandler(QOpcUaClient::ClientState state);
@@ -103,6 +108,9 @@ private slots:
void handleWriteNodeAttributesFinished(const QVector<QOpcUaWriteResult> &results);
private:
+ void removeConnection();
+ void setupConnection();
+
QOpcUaClient *m_client = nullptr;
bool m_connected = false;
static OpcUaConnection* m_defaultConnection;
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index b070f81..a90406f 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -1,5 +1,5 @@
TEMPLATE = subdirs
-SUBDIRS += qopcuaclient connection
+SUBDIRS += qopcuaclient connection clientSetupInCpp
QT_FOR_CONFIG += opcua-private
diff --git a/tests/auto/clientSetupInCpp/clientSetupInCpp.pro b/tests/auto/clientSetupInCpp/clientSetupInCpp.pro
new file mode 100644
index 0000000..57fb5de
--- /dev/null
+++ b/tests/auto/clientSetupInCpp/clientSetupInCpp.pro
@@ -0,0 +1,11 @@
+TEMPLATE = app
+TARGET = tst_clientSetupInCpp
+CONFIG += warn_on qmltestcase
+SOURCES += tst_clientSetupInCpp.cpp
+HEADERS += $$PWD/../../common/backend_environment.h
+INCLUDEPATH += $$PWD/../../common
+IMPORTPATH += $$PWD/../../../src/plugins/declarative
+
+# This tries to check if the server has security support
+QT += opcua_private
+qtConfig(mbedtls): DEFINES += SERVER_SUPPORTS_SECURITY
diff --git a/tests/auto/clientSetupInCpp/tst_clientSetupInCpp.cpp b/tests/auto/clientSetupInCpp/tst_clientSetupInCpp.cpp
new file mode 100644
index 0000000..2e6eed7
--- /dev/null
+++ b/tests/auto/clientSetupInCpp/tst_clientSetupInCpp.cpp
@@ -0,0 +1,191 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt OPC UA module.
+**
+** $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 "backend_environment.h"
+#include <QtQuickTest/quicktest.h>
+#include <QObject>
+#include <QProcess>
+#include <QQmlContext>
+#include <QQmlEngine>
+#include <QTcpServer>
+#include <QTcpSocket>
+#include <QOpcUaClient>
+#include <QOpcUaProvider>
+#include <QSignalSpy>
+
+const int signalSpyTimeout = 10000;
+const quint16 defaultPort = 43344;
+const QHostAddress defaultHost(QHostAddress::LocalHost);
+
+static QString envOrDefault(const char *env, QString def)
+{
+ return qEnvironmentVariableIsSet(env) ? qgetenv(env).constData() : def;
+}
+
+class MyClass : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QOpcUaClient* connection READ connection NOTIFY connectionChanged)
+
+public:
+ MyClass (QObject* parent = nullptr) : QObject(parent) {
+ }
+
+ QOpcUaClient *connection() const {
+ return m_client;
+ }
+
+signals:
+ void connectionChanged(QOpcUaClient *);
+
+public slots:
+ void startConnection() {
+ QOpcUaProvider p;
+ QOpcUaClient *client = p.createClient("open62541");
+
+ if (!client)
+ qFatal("Failed to instantiate backend");
+
+ m_client = client;
+
+ QString host = envOrDefault("OPCUA_HOST", defaultHost.toString());
+ QString port = envOrDefault("OPCUA_PORT", QString::number(defaultPort));
+ const auto discoveryEndpoint = QString("opc.tcp://%1:%2").arg(host).arg(port);
+
+ QSignalSpy endpointSpy(client, &QOpcUaClient::endpointsRequestFinished);
+ client->requestEndpoints(discoveryEndpoint);
+ endpointSpy.wait(signalSpyTimeout);
+ QCOMPARE(endpointSpy.size(), 1);
+
+ const QVector<QOpcUaEndpointDescription> desc = endpointSpy.at(0).at(0).value<QVector<QOpcUaEndpointDescription>>();
+ QVERIFY(desc.size() > 0);
+ QCOMPARE(endpointSpy.at(0).at(2).value<QUrl>(), discoveryEndpoint);
+
+ client->connectToEndpoint(desc.first());
+ QTRY_VERIFY_WITH_TIMEOUT(client->state() == QOpcUaClient::Connected, signalSpyTimeout);
+ qDebug() << "DONE";
+ emit connectionChanged(m_client);
+ }
+
+private:
+ QOpcUaClient *m_client = nullptr;
+};
+
+class SetupClass : public QObject
+{
+ Q_OBJECT
+public:
+ SetupClass() {
+ };
+ ~SetupClass() {
+ }
+
+public slots:
+ void applicationAvailable() {
+ updateEnvironment();
+
+ if (qEnvironmentVariableIsEmpty("OPCUA_HOST") && qEnvironmentVariableIsEmpty("OPCUA_PORT")) {
+ m_testServerPath = qApp->applicationDirPath()
+
+#if defined(Q_OS_MACOS)
+ + QLatin1String("/../../open62541-testserver/open62541-testserver.app/Contents/MacOS/open62541-testserver")
+#else
+
+#ifdef Q_OS_WIN
+ + QLatin1String("/..")
+#endif
+ + QLatin1String("/../../open62541-testserver/open62541-testserver")
+#ifdef Q_OS_WIN
+ + QLatin1String(".exe")
+#endif
+
+#endif
+ ;
+ qDebug() << "Server Path:" << m_testServerPath;
+ if (!QFile::exists(m_testServerPath)) {
+ qFatal("all auto tests rely on an open62541-based test-server");
+ }
+
+ // In this case the test is supposed to open its own server.
+ // Unfortunately there is no way to check if the server has started up successfully
+ // because of missing error handling.
+ // This checks will detect other servers blocking the port.
+
+ // Check for running server
+ QTcpSocket socket;
+ socket.connectToHost(defaultHost, defaultPort);
+ QVERIFY2(socket.waitForConnected(1500) == false, "Server is already running");
+
+ // Check for running server which does not respond
+ QTcpServer server;
+ QVERIFY2(server.listen(defaultHost, defaultPort) == true, "Port is occupied by another process. Check for defunct server.");
+ server.close();
+
+ qDebug() << "Starting test server";
+ //m_serverProcess.setProcessChannelMode(QProcess::ForwardedChannels);
+ m_serverProcess.start(m_testServerPath);
+ QVERIFY2(m_serverProcess.waitForStarted(), qPrintable(m_serverProcess.errorString()));
+ // Let the server come up
+ QTest::qSleep(2000);
+ }
+ const QString host = envOrDefault("OPCUA_HOST", defaultHost.toString());
+ const QString port = envOrDefault("OPCUA_PORT", QString::number(defaultPort));
+ m_opcuaDiscoveryUrl = QString::fromLatin1("opc.tcp://%1:%2").arg(host).arg(port);
+ }
+ void qmlEngineAvailable(QQmlEngine *engine) {
+ bool value = false;
+#ifdef SERVER_SUPPORTS_SECURITY
+ value = true;
+#endif
+ engine->rootContext()->setContextProperty("SERVER_SUPPORTS_SECURITY", value);
+ engine->rootContext()->setContextProperty("OPCUA_DISCOVERY_URL", m_opcuaDiscoveryUrl);
+ qmlRegisterType<MyClass>("App", 1, 0, "MyClass");
+ }
+ void cleanupTestCase() {
+ if (m_serverProcess.state() == QProcess::Running) {
+ m_serverProcess.kill();
+ m_serverProcess.waitForFinished(2000);
+ }
+ }
+private:
+ QProcess m_serverProcess;
+ QString m_testServerPath;
+ QString m_opcuaDiscoveryUrl;
+};
+
+QUICK_TEST_MAIN_WITH_SETUP(opcua, SetupClass)
+
+#include "tst_clientSetupInCpp.moc"
diff --git a/tests/auto/clientSetupInCpp/tst_clientSetupInCpp.qml b/tests/auto/clientSetupInCpp/tst_clientSetupInCpp.qml
new file mode 100644
index 0000000..afbb8ba
--- /dev/null
+++ b/tests/auto/clientSetupInCpp/tst_clientSetupInCpp.qml
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt OPC UA module.
+**
+** $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$
+**
+****************************************************************************/
+
+import QtQuick 2.3
+import QtTest 1.0
+import QtOpcUa 5.13 as QtOpcUa
+import App 1.0
+
+Item {
+ QtOpcUa.Connection {
+ id: connection
+ connection: myclass.connection
+ }
+
+ MyClass {
+ id: myclass
+ }
+
+ Component.onCompleted: myclass.startConnection()
+
+ TestCase {
+ name: "Create String Node Id"
+ when: node1.readyToUse
+
+ function test_nodeTest() {
+ compare(node1.value, "Value", "");
+ compare(node1.browseName, "theStringId");
+ compare(node1.nodeClass, QtOpcUa.Constants.NodeClass.Variable);
+ compare(node1.displayName.text, "theStringId");
+ compare(node1.description.text, "Description for ns=3;s=theStringId");
+ compare(node1.status, QtOpcUa.Node.Status.Valid);
+ tryCompare(node1, "monitored", true);
+ compare(node1.publishingInterval, 100.0);
+
+ compare(connection.currentEndpoint.endpointUrl, "opc.tcp://127.0.0.1:43344");
+ compare(connection.currentEndpoint.securityPolicy, "http://opcfoundation.org/UA/SecurityPolicy#None");
+ compare(connection.currentEndpoint.server.applicationUri, "urn:unconfigured:application");
+ }
+
+ QtOpcUa.ValueNode {
+ connection: connection
+ nodeId: QtOpcUa.NodeId {
+ ns: "Test Namespace"
+ identifier: "s=theStringId"
+ }
+ id: node1
+ }
+ }
+}