Qt OPC UA Viewer Example
#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;
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")) {
const char *name = QMetaEnum::fromType<QOpcUa::UaStatusCode>().valueToKey(value.toInt());
return name ? QLatin1String(name) : QLatin1String("Unknown StatusCode");
}
if (typeNodeId == QLatin1String("ns=0;i=2"))
return QString::number(value.toInt());
if (typeNodeId == QLatin1String("ns=0;i=3"))
return QString::number(value.toUInt());
if (typeNodeId == QLatin1String("ns=0;i=4"))
return QString::number(value.toInt());
if (typeNodeId == QLatin1String("ns=0;i=5"))
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
{