Qt OPC UA Viewer Example
/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt OPC UA module. ** ** $QT_BEGIN_LICENSE:BSD$ ** 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 https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/#include "treeitem.h" #include "opcuamodel.h" #include <QOpcUaArgument> #include <QOpcUaAxisInformation> #include <QOpcUaClient> #include <QOpcUaComplexNumber> #include <QOpcUaDoubleComplexNumber> #include <QOpcUaEUInformation> #include <QOpcUaExtensionObject> #include <QOpcUaLocalizedText> #include <QOpcUaQualifiedName> #include <QOpcUaRange> #include <QOpcUaXValue> #include <QMetaEnum> #include <QPixmap> const int numberOfDisplayColumns = 7; // NodeId, Value, NodeClass, DataType, BrowseName, DisplayName, Description TreeItem::TreeItem(OpcUaModel *model) : QObject(nullptr) , mModel(model) { } TreeItem::TreeItem(QOpcUaNode *node, OpcUaModel *model, TreeItem *parent) : QObject(parent) , mOpcNode(node) , mModel(model) , mParentItem(parent) { connect(mOpcNode.get(), &QOpcUaNode::attributeRead, this, &TreeItem::handleAttributes); connect(mOpcNode.get(), &QOpcUaNode::attributeUpdated, this, &TreeItem::handleAttributes); connect(mOpcNode.get(), &QOpcUaNode::browseFinished, this, &TreeItem::browseFinished); if (!mOpcNode->readAttributes( QOpcUa::NodeAttribute::Value | QOpcUa::NodeAttribute::NodeClass | QOpcUa::NodeAttribute::Description | QOpcUa::NodeAttribute::DataType | QOpcUa::NodeAttribute::BrowseName | QOpcUa::NodeAttribute::DisplayName )) qWarning() << "Reading attributes" << mOpcNode->nodeId() << "failed"; } TreeItem::TreeItem(QOpcUaNode *node, OpcUaModel *model, const QOpcUaReferenceDescription &browsingData, TreeItem *parent) : TreeItem(node, model, parent) { mNodeBrowseName = browsingData.browseName().name(); mNodeClass = browsingData.nodeClass(); mNodeId = browsingData.targetNodeId().nodeId(); mNodeDisplayName = browsingData.displayName().text(); } TreeItem::~TreeItem() { qDeleteAll(mChildItems); } TreeItem *TreeItem::child(int row) { if (row >= mChildItems.size()) qCritical() << "TreeItem in row" << row << "does not exist."; return mChildItems[row]; } int TreeItem::childIndex(const TreeItem *child) const { return mChildItems.indexOf(const_cast<TreeItem *>(child)); } int TreeItem::childCount() { startBrowsing(); return mChildItems.size(); } int TreeItem::columnCount() const { return numberOfDisplayColumns; } QVariant TreeItem::data(int column) { if (column == 0) return mNodeBrowseName; if (column == 1) { if (!mAttributesReady) return tr("Loading ..."); const auto type = mOpcNode->attribute(QOpcUa::NodeAttribute::DataType).toString(); const auto value = mOpcNode->attribute(QOpcUa::NodeAttribute::Value); return variantToString(value, type); } if (column == 2) { QMetaEnum metaEnum = QMetaEnum::fromType<QOpcUa::NodeClass>(); QString name = metaEnum.valueToKey(int(mNodeClass)); return name + " (" + QString::number(int(mNodeClass)) + ')'; } if (column == 3) { if (!mAttributesReady) return tr("Loading ..."); const QString typeId = mOpcNode->attribute(QOpcUa::NodeAttribute::DataType).toString(); auto enumEntry = QOpcUa::namespace0IdFromNodeId(typeId); if (enumEntry == QOpcUa::NodeIds::Namespace0::Unknown) return typeId; return QOpcUa::namespace0IdName(enumEntry) + " (" + typeId + ")"; } if (column == 4) return mNodeId; if (column == 5) return mNodeDisplayName; if (column == 6) { return mAttributesReady ? mOpcNode->attribute(QOpcUa::NodeAttribute::Description).value<QOpcUaLocalizedText>().text() : tr("Loading ..."); } return QVariant(); } int TreeItem::row() const { if (!mParentItem) return 0; return mParentItem->childIndex(this); } TreeItem *TreeItem::parentItem() { return mParentItem; } void TreeItem::appendChild(TreeItem *child) { if (!child) return; if (!hasChildNodeItem(child->mNodeId)) { mChildItems.append(child); mChildNodeIds.insert(child->mNodeId); } else { child->deleteLater(); } } static QPixmap createPixmap(const QColor &c) { QPixmap p(10,10); p.fill(c); return p; } QPixmap TreeItem::icon(int column) const { if (column != 0 || !mOpcNode) return QPixmap(); static const QPixmap objectPixmap = createPixmap(Qt::darkGreen); static const QPixmap variablePixmap = createPixmap(Qt::darkBlue); static const QPixmap methodPixmap = createPixmap(Qt::darkRed); static const QPixmap defaultPixmap = createPixmap(Qt::gray); switch (mNodeClass) { case QOpcUa::NodeClass::Object: return objectPixmap; case QOpcUa::NodeClass::Variable: return variablePixmap; case QOpcUa::NodeClass::Method: return methodPixmap; default: break; } return defaultPixmap; } bool TreeItem::hasChildNodeItem(const QString &nodeId) const { return mChildNodeIds.contains(nodeId); } void TreeItem::setMonitoringEnabled(bool active) { if (!supportsMonitoring()) return; if (active) { mOpcNode->enableMonitoring(QOpcUa::NodeAttribute::Value, QOpcUaMonitoringParameters(500)); } else { mOpcNode->disableMonitoring(QOpcUa::NodeAttribute::Value); } } bool TreeItem::monitoringEnabled() const { QOpcUaMonitoringParameters monitoring = mOpcNode.get()->monitoringStatus(QOpcUa::NodeAttribute::Value); return monitoring.statusCode() == QOpcUa::UaStatusCode::Good && monitoring.monitoringMode() == QOpcUaMonitoringParameters::MonitoringMode::Reporting; } bool TreeItem::supportsMonitoring() const { return mNodeClass == QOpcUa::NodeClass::Variable; } void TreeItem::startBrowsing() { if (mBrowseStarted) return; if (!mOpcNode->browseChildren()) qWarning() << "Browsing node" << mOpcNode->nodeId() << "failed"; else mBrowseStarted = true; } void TreeItem::handleAttributes(QOpcUa::NodeAttributes attr) { if (attr & QOpcUa::NodeAttribute::NodeClass) mNodeClass = mOpcNode->attribute(QOpcUa::NodeAttribute::NodeClass).value<QOpcUa::NodeClass>(); if (attr & QOpcUa::NodeAttribute::BrowseName) mNodeBrowseName = mOpcNode->attribute(QOpcUa::NodeAttribute::BrowseName).value<QOpcUaQualifiedName>().name(); if (attr & QOpcUa::NodeAttribute::DisplayName) mNodeDisplayName = mOpcNode->attribute(QOpcUa::NodeAttribute::DisplayName).value<QOpcUaLocalizedText>().text(); mAttributesReady = true; emit mModel->dataChanged(mModel->createIndex(row(), 0, this), mModel->createIndex(row(), numberOfDisplayColumns - 1, this)); } void TreeItem::browseFinished(const QList<QOpcUaReferenceDescription> &children, QOpcUa::UaStatusCode statusCode) { if (statusCode != QOpcUa::Good) { qWarning() << "Browsing node" << mOpcNode->nodeId() << "finally failed:" << statusCode; return; } auto index = mModel->createIndex(row(), 0, this); for (const auto &item : children) { if (hasChildNodeItem(item.targetNodeId().nodeId())) continue; auto node = mModel->opcUaClient()->node(item.targetNodeId()); if (!node) { qWarning() << "Failed to instantiate node:" << item.targetNodeId().nodeId(); continue; } mModel->beginInsertRows(index, mChildItems.size(), mChildItems.size() + 1); appendChild(new TreeItem(node, mModel, item, this)); mModel->endInsertRows(); } emit mModel->dataChanged(mModel->createIndex(row(), 0, this), mModel->createIndex(row(), numberOfDisplayColumns - 1, this)); } QString TreeItem::variantToString(const QVariant &value, const QString &typeNodeId) const { if (value.metaType().id() == QMetaType::QVariantList) { const auto list = value.toList(); QString concat; for (int i = 0, size = list.size(); i < size; ++i) { if (i) concat.append(QLatin1Char('\n')); concat.append(variantToString(list.at(i), typeNodeId)); } return concat; } if (typeNodeId == QLatin1String("ns=0;i=19")) { // StatusCode const char *name = QMetaEnum::fromType<QOpcUa::UaStatusCode>().valueToKey(value.toInt()); return name ? QLatin1String(name) : QLatin1String("Unknown StatusCode"); } if (typeNodeId == QLatin1String("ns=0;i=2")) // Char return QString::number(value.toInt()); if (typeNodeId == QLatin1String("ns=0;i=3")) // SChar return QString::number(value.toUInt()); if (typeNodeId == QLatin1String("ns=0;i=4")) // Int16 return QString::number(value.toInt()); if (typeNodeId == QLatin1String("ns=0;i=5")) // UInt16 return QString::number(value.toUInt()); if (value.metaType().id() == QMetaType::QByteArray) return QLatin1String("0x") + value.toByteArray().toHex(); if (value.metaType().id() == QMetaType::QDateTime) return value.toDateTime().toString(Qt::ISODate); if (value.canConvert<QOpcUaQualifiedName>()) { const auto name = value.value<QOpcUaQualifiedName>(); return QStringLiteral("[NamespaceIndex: %1, Name: \"%2\"]").arg(name.namespaceIndex()).arg(name.name()); } if (value.canConvert<QOpcUaLocalizedText>()) { const auto text = value.value<QOpcUaLocalizedText>(); return localizedTextToString(text); } if (value.canConvert<QOpcUaRange>()) { const auto range = value.value<QOpcUaRange>(); return rangeToString(range); } if (value.canConvert<QOpcUaComplexNumber>()) { const auto complex = value.value<QOpcUaComplexNumber>(); return QStringLiteral("[Real: %1, Imaginary: %2]").arg(complex.real()).arg(complex.imaginary()); } if (value.canConvert<QOpcUaDoubleComplexNumber>()) { const auto complex = value.value<QOpcUaDoubleComplexNumber>(); return QStringLiteral("[Real: %1, Imaginary: %2]").arg(complex.real()).arg(complex.imaginary()); } if (value.canConvert<QOpcUaXValue>()) { const auto xv = value.value<QOpcUaXValue>(); return QStringLiteral("[X: %1, Value: %2]").arg(xv.x()).arg(xv.value()); } if (value.canConvert<QOpcUaEUInformation>()) { const auto info = value.value<QOpcUaEUInformation>(); return euInformationToString(info); } if (value.canConvert<QOpcUaAxisInformation>()) { const auto info = value.value<QOpcUaAxisInformation>(); return QStringLiteral("[EUInformation: %1, EURange: %2, Title: %3 , AxisScaleType: %4, AxisSteps: %5]").arg( euInformationToString(info.engineeringUnits())).arg(rangeToString(info.eURange())).arg(localizedTextToString(info.title())).arg( info.axisScaleType() == QOpcUa::AxisScale::Linear ? "Linear" : (info.axisScaleType() == QOpcUa::AxisScale::Ln) ? "Ln" : "Log").arg( numberArrayToString(info.axisSteps())); } if (value.canConvert<QOpcUaExpandedNodeId>()) { const auto id = value.value<QOpcUaExpandedNodeId>(); return QStringLiteral("[NodeId: \"%1\", ServerIndex: \"%2\", NamespaceUri: \"%3\"]").arg( id.nodeId()).arg(id.serverIndex()).arg(id.namespaceUri()); } if (value.canConvert<QOpcUaArgument>()) { const auto a = value.value<QOpcUaArgument>(); return QStringLiteral("[Name: \"%1\", DataType: \"%2\", ValueRank: \"%3\", ArrayDimensions: %4, Description: %5]").arg( a.name(), a.dataTypeId()).arg(a.valueRank()).arg(numberArrayToString(a.arrayDimensions()), localizedTextToString(a.description())); } if (value.canConvert<QOpcUaExtensionObject>()) { const auto obj = value.value<QOpcUaExtensionObject>(); return QStringLiteral("[TypeId: \"%1\", Encoding: %2, Body: 0x%3]").arg(obj.encodingTypeId(), obj.encoding() == QOpcUaExtensionObject::Encoding::NoBody ? "NoBody" : (obj.encoding() == QOpcUaExtensionObject::Encoding::ByteString ? "ByteString" : "XML")).arg(obj.encodedBody().isEmpty() ? "0" : QString(obj.encodedBody().toHex())); } if (value.canConvert<QString>()) return value.toString(); return QString(); } QString TreeItem::localizedTextToString(const QOpcUaLocalizedText &text) const { return QStringLiteral("[Locale: \"%1\", Text: \"%2\"]").arg(text.locale()).arg(text.text()); } QString TreeItem::rangeToString(const QOpcUaRange &range) const { return QStringLiteral("[Low: %1, High: %2]").arg(range.low()).arg(range.high()); } QString TreeItem::euInformationToString(const QOpcUaEUInformation &info) const { return QStringLiteral("[UnitId: %1, NamespaceUri: \"%2\", DisplayName: %3, Description: %4]").arg(info.unitId()).arg( info.namespaceUri()).arg(localizedTextToString(info.displayName())).arg(localizedTextToString(info.description())); }