diff options
author | Rainer Keller <Rainer.Keller@qt.io> | 2018-08-21 13:51:44 +0200 |
---|---|---|
committer | Rainer Keller <Rainer.Keller@qt.io> | 2018-08-30 11:42:04 +0000 |
commit | ca9dc9f4069b76024141236d2e3b02d305449160 (patch) | |
tree | bc84d5966f2b7e16684d987bff80cd3df8a5be0b | |
parent | 47f8f18352ee088ec9fa3cc7817301e42da3d36b (diff) |
qml: Add basic node properties
Change-Id: Ie32c89dd91e1da6e214debe7a01733a8ce127f50
Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
-rw-r--r-- | src/imports/opcua/opcuamethodnode.cpp | 21 | ||||
-rw-r--r-- | src/imports/opcua/opcuamethodnode.h | 1 | ||||
-rw-r--r-- | src/imports/opcua/opcuanode.cpp | 81 | ||||
-rw-r--r-- | src/imports/opcua/opcuanode.h | 25 | ||||
-rw-r--r-- | src/imports/opcua/plugins.qmltypes | 4 | ||||
-rw-r--r-- | tests/auto/declarative/tst_absolutenode.qml | 78 | ||||
-rw-r--r-- | tests/auto/declarative/tst_generic.qml | 7 | ||||
-rw-r--r-- | tests/auto/declarative/tst_methodnode.qml | 106 | ||||
-rw-r--r-- | tests/auto/declarative/tst_relativenode.qml | 16 |
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); } |