summaryrefslogtreecommitdiffstats
path: root/src/imports/opcua/opcuanode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/imports/opcua/opcuanode.cpp')
-rw-r--r--src/imports/opcua/opcuanode.cpp362
1 files changed, 362 insertions, 0 deletions
diff --git a/src/imports/opcua/opcuanode.cpp b/src/imports/opcua/opcuanode.cpp
new file mode 100644
index 0000000..28fe73c
--- /dev/null
+++ b/src/imports/opcua/opcuanode.cpp
@@ -0,0 +1,362 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "opcuanode.h"
+#include "opcuanodeidtype.h"
+#include "opcuaconnection.h"
+#include "opcuanodeid.h"
+#include "opcuarelativenodepath.h"
+#include "opcuarelativenodeid.h"
+#include "opcuapathresolver.h"
+#include "opcuaattributevalue.h"
+#include <qopcuatype.h>
+#include <QOpcUaNode>
+#include <QOpcUaClient>
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype Node
+ \inqmlmodule QtOpcUa
+ \brief Represents a node on a server.
+ \since QtOpcUa 5.12
+
+ \code
+ import QtOpcUa 5.12 as QtOpcUa
+
+ QtOpcUa.Node {
+ nodeId : QtOpcUa.NodeId {
+ identifier: "s=Example.Method"
+ ns: "Example Namespace"
+ }
+ connection: myConnection
+ }
+ \endcode
+*/
+
+/*!
+ \qmlproperty NodeId Node::nodeId
+
+ ID of the node on the server to be used.
+ This can be an absolute node ID or a relative node ID.
+
+ \sa NodeId, RelativeNodeId
+*/
+
+/*!
+ \qmlproperty Connection Node::connection
+
+ The connection to be used for node instances.
+ The node will automatically be accessible when the associated connection
+ has established a connection to a server.
+
+ If this property is not set, the default connection will be used, if any.
+
+ \sa Connection, Connection::defaultConnection
+*/
+
+/*!
+ \qmlproperty bool Node::readyToUse
+ \readonly
+
+ This property returns whether the node is ready to use.
+ This happens once after a successful connection to a server was established
+ and the node was successfully set up.
+*/
+
+/*!
+ \qmlsignal Connection::nodeChanged()
+
+ Emitted when the underlying node has changed.
+ 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):
+ QObject(parent),
+ m_nodeId(new OpcUaNodeIdType(this)),
+ m_attributesToRead(QOpcUaNode::mandatoryBaseAttributes())
+{
+ m_attributesToRead |= QOpcUa::NodeAttribute::Description;
+ 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()
+{
+ delete m_node;
+}
+
+OpcUaNodeIdType *OpcUaNode::nodeId() const
+{
+ return m_nodeId;
+}
+
+OpcUaConnection *OpcUaNode::connection()
+{
+ if (!m_connection)
+ setConnection(OpcUaConnection::defaultConnection());
+
+ return m_connection;
+}
+
+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 QOpcUa::QLocalizedText &value)
+{
+ if (!m_connection || !m_node)
+ return;
+ m_node->writeAttribute(QOpcUa::NodeAttribute::DisplayName, value);
+}
+
+QOpcUa::QLocalizedText OpcUaNode::displayName()
+{
+ return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::DisplayName).value<QOpcUa::QLocalizedText>();
+}
+
+void OpcUaNode::setDescription(const QOpcUa::QLocalizedText &value)
+{
+ if (!m_connection || !m_node)
+ return;
+ m_node->writeAttribute(QOpcUa::NodeAttribute::Description, value);
+}
+
+QOpcUa::QLocalizedText OpcUaNode::description()
+{
+ return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::Description).value<QOpcUa::QLocalizedText>();
+}
+
+void OpcUaNode::setNodeId(OpcUaNodeIdType *nodeId)
+{
+ if (m_nodeId == nodeId)
+ return;
+
+ // This deletes the initial dummy object that was created in the
+ // constructor in case a "real" nodeId is set.
+ if (m_nodeId->parent() == this)
+ m_nodeId->deleteLater();
+
+ // Disconnect signals from old node
+ m_nodeId->disconnect(this);
+ m_nodeId = nodeId;
+ connect(m_nodeId, &OpcUaNodeIdType::nodeChanged, this, &OpcUaNode::updateNode);
+ connect(m_nodeId, &OpcUaNodeIdType::destroyed, this, [&]() { m_nodeId = nullptr; });
+
+ updateNode();
+}
+
+void OpcUaNode::setConnection(OpcUaConnection *connection)
+{
+ if (connection == m_connection)
+ return;
+
+ m_connection = connection;
+ connect(connection, SIGNAL(connectedChanged()), this, SLOT(updateNode()));
+
+ updateNode();
+ emit connectionChanged(connection);
+}
+
+void OpcUaNode::setupNode(const QString &absoluteNodePath)
+{
+ m_attributeCache.invalidate();
+ m_absoluteNodePath = absoluteNodePath;
+
+ if (m_node) {
+ m_node->deleteLater();
+ m_node = nullptr;
+ }
+
+ if (m_absoluteNodePath.isEmpty())
+ return;
+
+ auto conn = connection();
+ if (!conn || !m_nodeId || !conn->m_client)
+ return;
+
+ if (!conn->connected())
+ return;
+
+ m_node = conn->m_client->node(m_absoluteNodePath);
+ if (!m_node) {
+ qCWarning(QT_OPCUA_PLUGINS_QML) << "Invalid node:" << m_absoluteNodePath;
+ return;
+ }
+
+ connect(m_node, &QOpcUaNode::attributeUpdated, &m_attributeCache, &OpcUaAttributeCache::setAttributeValue);
+ connect(m_node, &QOpcUaNode::attributeRead, this, [this](){
+ setReadyToUse(true);
+ });
+
+ // Read mandatory attributes
+ if (!m_node->readAttributes(m_attributesToRead))
+ qCWarning(QT_OPCUA_PLUGINS_QML) << "Reading attributes" << m_node->nodeId() << "failed";
+}
+
+void OpcUaNode::updateNode()
+{
+ retrieveAbsoluteNodePath(m_nodeId, [this](const QString &absoluteNodePath) {setupNode(absoluteNodePath);});
+}
+
+const UniversalNode &OpcUaNode::resolvedNode() const
+{
+ return m_resolvedNode;
+}
+
+QOpcUaNode *OpcUaNode::node() const
+{
+ return m_node;
+}
+
+void OpcUaNode::setAttributesToRead(QOpcUa::NodeAttributes attributes)
+{
+ m_attributesToRead = attributes;
+}
+
+QOpcUa::NodeAttributes OpcUaNode::attributesToRead() const
+{
+ return m_attributesToRead;
+}
+
+void OpcUaNode::retrieveAbsoluteNodePath(OpcUaNodeIdType *node, std::function<void (const QString &)> functor)
+{
+ auto conn = connection();
+ if (!conn || !m_nodeId || !conn->m_client) {
+ qCWarning(QT_OPCUA_PLUGINS_QML) << "connection, nodeID or client is invalid";
+ return;
+ }
+
+ if (!conn->connected()) {
+ qCWarning(QT_OPCUA_PLUGINS_QML) << "not connected";
+ return;
+ }
+
+ if (qobject_cast<const OpcUaNodeId *>(node)) {
+ UniversalNode tmp(node);
+ tmp.resolveNamespace(conn->m_client);
+ m_resolvedNode.from(tmp);
+ functor(m_resolvedNode.fullNodeId());
+ emit nodeIdChanged(m_nodeId);
+ emit nodeChanged();
+ } else if (qobject_cast<OpcUaRelativeNodeId *>(node)) {
+ auto nodeId = qobject_cast<OpcUaRelativeNodeId *>(node);
+ OpcUaPathResolver *resolver = new OpcUaPathResolver(nodeId, conn->m_client, this);
+ connect(resolver, &OpcUaPathResolver::resolvedNode, this, [this, functor, resolver](UniversalNode nodeToUse, const QString &errorMessage) {
+ resolver->deleteLater();
+
+ if (!errorMessage.isEmpty()) {
+ qCWarning(QT_OPCUA_PLUGINS_QML) << "Failed to resolve node:" << errorMessage;
+ functor(QString());
+ return;
+ }
+
+ m_resolvedNode.from(nodeToUse);
+ functor(m_resolvedNode.fullNodeId());
+ emit nodeIdChanged(m_nodeId);
+ emit nodeChanged();
+ });
+ resolver->startResolving();
+ } else {
+ functor(QString());
+ }
+}
+
+void OpcUaNode::setReadyToUse(bool value)
+{
+ bool old = m_readyToUse;
+ m_readyToUse = value;
+ if (!old && value)
+ emit readyToUseChanged();
+}
+
+QT_END_NAMESPACE