summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRainer Keller <Rainer.Keller@qt.io>2018-08-21 13:51:44 +0200
committerRainer Keller <Rainer.Keller@qt.io>2018-08-30 11:42:04 +0000
commitca9dc9f4069b76024141236d2e3b02d305449160 (patch)
treebc84d5966f2b7e16684d987bff80cd3df8a5be0b
parent47f8f18352ee088ec9fa3cc7817301e42da3d36b (diff)
qml: Add basic node properties
Change-Id: Ie32c89dd91e1da6e214debe7a01733a8ce127f50 Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
-rw-r--r--src/imports/opcua/opcuamethodnode.cpp21
-rw-r--r--src/imports/opcua/opcuamethodnode.h1
-rw-r--r--src/imports/opcua/opcuanode.cpp81
-rw-r--r--src/imports/opcua/opcuanode.h25
-rw-r--r--src/imports/opcua/plugins.qmltypes4
-rw-r--r--tests/auto/declarative/tst_absolutenode.qml78
-rw-r--r--tests/auto/declarative/tst_generic.qml7
-rw-r--r--tests/auto/declarative/tst_methodnode.qml106
-rw-r--r--tests/auto/declarative/tst_relativenode.qml16
9 files changed, 332 insertions, 7 deletions
diff --git a/src/imports/opcua/opcuamethodnode.cpp b/src/imports/opcua/opcuamethodnode.cpp
index 8f9ce23..bf41b14 100644
--- a/src/imports/opcua/opcuamethodnode.cpp
+++ b/src/imports/opcua/opcuamethodnode.cpp
@@ -132,6 +132,23 @@ void OpcUaMethodNode::callMethod()
m_node->callMethod(m_objectNodePath, QVector<QOpcUa::TypedVariant> ());
}
+void OpcUaMethodNode::objectNodePathResolved(const QString &str)
+{
+ m_objectNodePath = str;
+
+ connect(m_node, &QOpcUaNode::attributeRead, this, [this](){
+ setReadyToUse(true);
+ });
+
+ if (!m_node->readAttributes(QOpcUa::NodeAttribute::NodeClass
+ | QOpcUa::NodeAttribute::Description
+ | QOpcUa::NodeAttribute::DataType
+ | QOpcUa::NodeAttribute::BrowseName
+ | QOpcUa::NodeAttribute::DisplayName
+ ))
+ qCWarning(QT_OPCUA_PLUGINS_QML) << "Reading attributes" << m_node->nodeId() << "failed";
+}
+
void OpcUaMethodNode::setupNode(const QString &absolutePath)
{
OpcUaNode::setupNode(absolutePath);
@@ -140,7 +157,9 @@ void OpcUaMethodNode::setupNode(const QString &absolutePath)
void OpcUaMethodNode::retrieveObjectNodePath()
{
- retrieveAbsoluteNodePath(m_objectNodeId, [&](const QString &str) { m_objectNodePath = str;});
+ retrieveAbsoluteNodePath(m_objectNodeId, [this](const QString &str) {
+ this->objectNodePathResolved(str);
+ });
}
QT_END_NAMESPACE
diff --git a/src/imports/opcua/opcuamethodnode.h b/src/imports/opcua/opcuamethodnode.h
index ec6550a..a79ddd4 100644
--- a/src/imports/opcua/opcuamethodnode.h
+++ b/src/imports/opcua/opcuamethodnode.h
@@ -60,6 +60,7 @@ signals:
private:
void setupNode(const QString &absolutePath) override;
+ void objectNodePathResolved(const QString &);
private:
void retrieveObjectNodePath();
diff --git a/src/imports/opcua/opcuanode.cpp b/src/imports/opcua/opcuanode.cpp
index fdb2188..6f24b1d 100644
--- a/src/imports/opcua/opcuanode.cpp
+++ b/src/imports/opcua/opcuanode.cpp
@@ -42,6 +42,7 @@
#include "opcuarelativenodeid.h"
#include "opcuapathresolver.h"
#include "opcuaattributevalue.h"
+#include <qopcuatype.h>
#include <QOpcUaNode>
#include <QOpcUaClient>
#include <QLoggingCategory>
@@ -104,6 +105,35 @@ QT_BEGIN_NAMESPACE
This happens when the namespace or identifier of the \l NodeId changed.
*/
+/*!
+ \qmlproperty QOpcUa::NodeClass Node::nodeClass
+ \readonly
+
+ The node class of the node. In case the information is not available
+ \c QtOpcUa.Constants.NodeClass.Undefined is returned.
+*/
+
+/*!
+ \qmlproperty string Node::browseName
+
+ The browse name of the node. In case the information is not available
+ an empty string is returned.
+*/
+
+/*!
+ \qmlproperty LocalizedText Node::displayName
+
+ The localized text of the node. In case the information is not available
+ a default constructed \l LocalizedText is returned.
+*/
+
+/*!
+ \qmlproperty LocalizedText Node::description
+
+ The description of the node. In case the information is not available
+ a default constructed \l LocalizedText is returned.
+*/
+
Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML)
OpcUaNode::OpcUaNode(QObject *parent):
@@ -111,6 +141,10 @@ OpcUaNode::OpcUaNode(QObject *parent):
m_nodeId(new OpcUaNodeIdType(this))
{
connect(&m_resolvedNode, &UniversalNode::nodeChanged, this, &OpcUaNode::nodeChanged);
+ connect(m_attributeCache.attribute(QOpcUa::NodeAttribute::BrowseName), &OpcUaAttributeValue::changed, this, &OpcUaNode::browseNameChanged);
+ connect(m_attributeCache.attribute(QOpcUa::NodeAttribute::NodeClass), &OpcUaAttributeValue::changed, this, &OpcUaNode::nodeClassChanged);
+ connect(m_attributeCache.attribute(QOpcUa::NodeAttribute::DisplayName), &OpcUaAttributeValue::changed, this, &OpcUaNode::displayNameChanged);
+ connect(m_attributeCache.attribute(QOpcUa::NodeAttribute::Description), &OpcUaAttributeValue::changed, this, &OpcUaNode::descriptionChanged);
}
OpcUaNode::~OpcUaNode()
@@ -136,6 +170,50 @@ bool OpcUaNode::readyToUse() const
return m_readyToUse;
}
+void OpcUaNode::setBrowseName(const QString &value)
+{
+ if (!m_connection || !m_node)
+ return;
+ if (!m_resolvedNode.isNamespaceIndexValid())
+ return;
+
+ m_node->writeAttribute(QOpcUa::NodeAttribute::BrowseName, QOpcUa::QQualifiedName(m_resolvedNode.namespaceIndex(), value));
+}
+
+QString OpcUaNode::browseName()
+{
+ return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::BrowseName).value<QOpcUa::QQualifiedName>().name();
+}
+
+QOpcUa::NodeClass OpcUaNode::nodeClass()
+{
+ return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::NodeClass).value<QOpcUa::NodeClass>();
+}
+
+void OpcUaNode::setDisplayName(const LocalizedText &value)
+{
+ if (!m_connection || !m_node)
+ return;
+ m_node->writeAttribute(QOpcUa::NodeAttribute::DisplayName, value);
+}
+
+LocalizedText OpcUaNode::displayName()
+{
+ return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::DisplayName).value<QOpcUa::QLocalizedText>();
+}
+
+void OpcUaNode::setDescription(const LocalizedText &value)
+{
+ if (!m_connection || !m_node)
+ return;
+ m_node->writeAttribute(QOpcUa::NodeAttribute::Description, value);
+}
+
+LocalizedText OpcUaNode::description()
+{
+ return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::Description).value<QOpcUa::QLocalizedText>();
+}
+
void OpcUaNode::setNodeId(OpcUaNodeIdType *nodeId)
{
if (m_nodeId == nodeId)
@@ -162,8 +240,6 @@ void OpcUaNode::setConnection(OpcUaConnection *connection)
m_connection = connection;
connect(connection, SIGNAL(connectedChanged()), this, SLOT(updateNode()));
- // Existing namespaces are not allowed to change during a connection
- // connect(connection, &OpcUaConnection::namespacesChanged, this, &OpcUaNode::updateNode);
updateNode();
emit connectionChanged(connection);
@@ -171,6 +247,7 @@ void OpcUaNode::setConnection(OpcUaConnection *connection)
void OpcUaNode::setupNode(const QString &absoluteNodePath)
{
+ m_attributeCache.invalidate();
m_absoluteNodePath = absoluteNodePath;
if (m_node) {
diff --git a/src/imports/opcua/opcuanode.h b/src/imports/opcua/opcuanode.h
index 4466967..f0d5b88 100644
--- a/src/imports/opcua/opcuanode.h
+++ b/src/imports/opcua/opcuanode.h
@@ -37,7 +37,7 @@
#pragma once
#include <QObject>
-#include <qopcuatype.h>
+#include "opcuatype.h"
#include "universalnode.h"
#include "opcuaattributecache.h"
@@ -55,6 +55,14 @@ class OpcUaNode : public QObject
Q_PROPERTY(OpcUaConnection* connection READ connection WRITE setConnection NOTIFY connectionChanged)
Q_PROPERTY(bool readyToUse READ readyToUse NOTIFY readyToUseChanged)
+ // basic node properties
+ Q_PROPERTY(QString browseName READ browseName WRITE setBrowseName NOTIFY browseNameChanged)
+ Q_PROPERTY(QOpcUa::NodeClass nodeClass READ nodeClass NOTIFY nodeClassChanged)
+ Q_PROPERTY(LocalizedText displayName READ displayName WRITE setDisplayName NOTIFY displayNameChanged)
+ Q_PROPERTY(LocalizedText description READ description WRITE setDescription NOTIFY descriptionChanged)
+
+ Q_ENUM(QOpcUa::NodeClass);
+
public:
OpcUaNode(QObject *parent = nullptr);
~OpcUaNode();
@@ -62,6 +70,17 @@ public:
OpcUaConnection *connection();
bool readyToUse() const;
+ void setBrowseName(const QString &value);
+ QString browseName();
+
+ QOpcUa::NodeClass nodeClass();
+
+ void setDisplayName(const LocalizedText &value);
+ LocalizedText displayName();
+
+ void setDescription(const LocalizedText &value);
+ LocalizedText description();
+
public slots:
void setNodeId(OpcUaNodeIdType *nodeId);
void setConnection(OpcUaConnection *);
@@ -71,6 +90,10 @@ signals:
void connectionChanged(OpcUaConnection *);
void nodeChanged();
void readyToUseChanged();
+ void browseNameChanged();
+ void nodeClassChanged();
+ void displayNameChanged();
+ void descriptionChanged();
protected slots:
virtual void setupNode(const QString &absoluteNodePath);
diff --git a/src/imports/opcua/plugins.qmltypes b/src/imports/opcua/plugins.qmltypes
index ff35612..407162d 100644
--- a/src/imports/opcua/plugins.qmltypes
+++ b/src/imports/opcua/plugins.qmltypes
@@ -67,6 +67,10 @@ Module {
Property { name: "nodeId"; type: "OpcUaNodeIdType"; isPointer: true }
Property { name: "connection"; type: "OpcUaConnection"; isPointer: true }
Property { name: "readyToUse"; type: "bool"; isReadonly: true }
+ Property { name: "browseName"; type: "string" }
+ Property { name: "nodeClass"; type: "QOpcUa::NodeClass"; isReadonly: true }
+ Property { name: "displayName"; type: "LocalizedText" }
+ Property { name: "description"; type: "LocalizedText" }
Signal {
name: "nodeIdChanged"
Parameter { name: "nodeId"; type: "const OpcUaNodeIdType"; isPointer: true }
diff --git a/tests/auto/declarative/tst_absolutenode.qml b/tests/auto/declarative/tst_absolutenode.qml
index b92ab1a..ce39531 100644
--- a/tests/auto/declarative/tst_absolutenode.qml
+++ b/tests/auto/declarative/tst_absolutenode.qml
@@ -56,6 +56,10 @@ Item {
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");
}
QtOpcUa.ValueNode {
@@ -193,12 +197,18 @@ Item {
verify(node8IdentifierSpy.valid);
node8.nodeId.identifier = "b=UXQgZnR3IQ==";
node8ValueSpy.wait();
+
+ // value has to be undefined because when node IDs are changed
+ // all attributes become undefined before they get the new values.
+ verify(!node8.value);
+
+ node8ValueSpy.wait();
compare(node8IdentifierSpy.count, 1);
compare(node8NodeIdSpy.count, 1);
compare(node8NamespaceSpy.count, 0);
verify(node8NodeChangedSpy.count > 0);
compare(node8NodeIdChangedSpy.count, 1);
- compare(node8ValueSpy.count, 1);
+ compare(node8ValueSpy.count, 2); // first undefined, then the real value
compare(node8.value, "Value");
}
@@ -265,4 +275,70 @@ Item {
}
}
+ TestCase {
+ name: "Standard attributes on variable node"
+ when: node10.readyToUse
+
+ SignalSpy {
+ id: node10BrowseNameSpy
+ target: node10
+ signalName: "browseNameChanged"
+ }
+
+ SignalSpy {
+ id: node10NodeClassSpy
+ target: node10
+ signalName: "nodeClassChanged"
+ }
+
+ SignalSpy {
+ id: node10DisplayNameSpy
+ target: node10
+ signalName: "displayNameChanged"
+ }
+
+ SignalSpy {
+ id: node10DescriptionSpy
+ target: node10
+ signalName: "descriptionChanged"
+ }
+
+ function test_nodeTest() {
+ compare(node10.browseName, "FullyWritableTest");
+ compare(node10.nodeClass, QtOpcUa.Constants.NodeClass.Variable);
+ compare(node10.displayName.text, "FullyWritableTest");
+ compare(node10.description.text, "Description for ns=3;s=Demo.Static.Scalar.FullyWritable");
+
+ node10BrowseNameSpy.clear();
+ node10NodeClassSpy.clear();
+ node10DisplayNameSpy.clear();
+ node10DescriptionSpy.clear();
+
+ node10.browseName = "modifiedBrowseName";
+ node10BrowseNameSpy.wait();
+ compare(node10BrowseNameSpy.count, 1);
+ node10.browseName = "FullyWritableTest"; // Setting back to default
+
+ // node class is not supposed to be changed: skipping tests
+
+ node10.displayName.text = "modifiedDisplayName";
+ node10DisplayNameSpy.wait();
+ compare(node10DisplayNameSpy.count, 1);
+ node10.displayName.text = "FullyWritableTest"; // Setting back to default
+
+ node10.description.text = "modifiedDescription";
+ node10DescriptionSpy.wait();
+ compare(node10DescriptionSpy.count, 1);
+ node10.description.text = "Description for ns=3;s=Demo.Static.Scalar.FullyWritable"; // Setting back to default
+ }
+
+ QtOpcUa.ValueNode {
+ connection: connection
+ nodeId: QtOpcUa.NodeId {
+ ns: "Test Namespace"
+ identifier: "s=Demo.Static.Scalar.FullyWritable"
+ }
+ id: node10
+ }
+ }
}
diff --git a/tests/auto/declarative/tst_generic.qml b/tests/auto/declarative/tst_generic.qml
index 009c84a..52a9fdb 100644
--- a/tests/auto/declarative/tst_generic.qml
+++ b/tests/auto/declarative/tst_generic.qml
@@ -45,6 +45,13 @@ Item {
function test_enumExports() {
compare(QtOpcUa.Constants.NodeClass.Method, 4);
compare(QtOpcUa.Constants.NodeAttribute.DisplayName, 8);
+
+ // Test return value of undefined node
+ compare(node1.nodeClass, QtOpcUa.Constants.NodeClass.Undefined);
+ }
+
+ QtOpcUa.Node {
+ id: node1
}
}
}
diff --git a/tests/auto/declarative/tst_methodnode.qml b/tests/auto/declarative/tst_methodnode.qml
new file mode 100644
index 0000000..40e702c
--- /dev/null
+++ b/tests/auto/declarative/tst_methodnode.qml
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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.12 as QtOpcUa
+
+Item {
+
+ QtOpcUa.Connection {
+ id: connection
+ backend: connection.availableBackends[0]
+ defaultConnection: true
+ }
+
+ Component.onCompleted: {
+ connection.connectToEndpoint("opc.tcp://127.0.0.1:43344");
+ }
+
+ TestCase {
+ name: "Standard attributes on method node"
+ when: node1.readyToUse
+
+ SignalSpy {
+ id: node1BrowseNameSpy
+ target: node1
+ signalName: "browseNameChanged"
+ }
+
+ SignalSpy {
+ id: node1NodeClassSpy
+ target: node1
+ signalName: "nodeClassChanged"
+ }
+
+ SignalSpy {
+ id: node1DisplayNameSpy
+ target: node1
+ signalName: "displayNameChanged"
+ }
+
+ SignalSpy {
+ id: node1DescriptionSpy
+ target: node1
+ signalName: "descriptionChanged"
+ }
+
+ function test_nodeTest() {
+ compare(node1.browseName, "TestFolder");
+ compare(node1.nodeClass, QtOpcUa.Constants.NodeClass.Object);
+ compare(node1.displayName.text, "TestFolder");
+ compare(node1.description.text, "");
+
+ compare(node1BrowseNameSpy.count, 1)
+ compare(node1NodeClassSpy.count, 1)
+ compare(node1DisplayNameSpy.count, 1)
+ compare(node1DescriptionSpy.count, 1)
+ }
+
+ QtOpcUa.MethodNode {
+ connection: connection
+ nodeId: QtOpcUa.NodeId {
+ ns: "Test Namespace"
+ identifier: "s=TestFolder"
+ }
+ objectNodeId: QtOpcUa.NodeId {
+ ns: "Test Namespace"
+ identifier: "s=Test.Method.Multiply"
+ }
+ id: node1
+ }
+ }
+}
diff --git a/tests/auto/declarative/tst_relativenode.qml b/tests/auto/declarative/tst_relativenode.qml
index f17d720..f379bcb 100644
--- a/tests/auto/declarative/tst_relativenode.qml
+++ b/tests/auto/declarative/tst_relativenode.qml
@@ -124,12 +124,18 @@ Item {
node3PathSpy.clear();
node3.nodeId.startNode.identifier = "s=TestFolder2";
node3ValueSpy.wait();
+
+ // value has to be undefined because when node IDs are changed
+ // all attributes become undefined before they get the new values.
+ verify(!node3.value);
+
+ node3ValueSpy.wait();
compare(node3NodeIdSpy.count, 1);
compare(node3NamespaceSpy.count, 0);
verify(node3NodeChangedSpy.count > 0);
compare(node3PathSpy.count, 0);
compare(node3NodeIdChangedSpy.count, 1);
- compare(node3ValueSpy.count, 1);
+ compare(node3ValueSpy.count, 2); // first undefined, then the real value
compare(node3.value, 0.1);
}
@@ -200,11 +206,17 @@ Item {
node4.nodeId.startNode.path = replacementNode.createObject(parent);
node4NodeChangedSpy.wait();
node4ValueSpy.wait();
+
+ // value has to be undefined because when node IDs are changed
+ // all attributes become undefined before they get the new values.
+ verify(!node4.value);
+
+ node4ValueSpy.wait();
compare(node4NodeIdSpy.count, 2);
compare(node4NamespaceSpy.count, 0);
verify(node4NodeChangedSpy.count > 0);
compare(node4NodeIdChangedSpy.count, 1);
- compare(node4ValueSpy.count, 1);
+ compare(node4ValueSpy.count, 2); // first undefined, then the real value
compare(node4.value, 0.1);
compare(node4PathSpy.count, 2);
}