Qt OPC UA Viewer Example | Qt OPC UA 6.4.1

- Qt 6.4
- Qt OPC UA
- Qt OPC UA Examples
- Qt OPC UA Viewer Example
- Qt 6.4.1 Reference Documentation

# Qt OPC UA Viewer Example

     // Copyright (C) 2018 The Qt Company Ltd.
     // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

     #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 = 8; // NodeId, Value, NodeClass, DataType, BrowseName, DisplayName, Description, Historizing

     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
                                 | QOpcUa::NodeAttribute::Historizing
                                 ))
             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();
         mHistorizing = false;
     }

     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 ...");
         }
         if (column == 7) {
             return mAttributesReady
                 ? mHistorizing ? QString("true") : QString("false") : 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();
         if (attr & QOpcUa::NodeAttribute::Historizing) {
             mHistorizing = mOpcNode->attribute(QOpcUa::NodeAttribute::Historizing).value<bool>();
         }

         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()));
     }