Qt OPC UA Viewer▲
Sélectionnez
// 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 <QOpcUaGenericStructHandler>
#include <QOpcUaGenericStructValue>
#include <QOpcUaLocalizedText>
#include <QOpcUaNode>
#include <QOpcUaQualifiedName>
#include <QOpcUaRange>
#include <QOpcUaXValue>
#include <QMetaEnum>
#include <QPixmap>
using
namespace
Qt::Literals::
StringLiterals;
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(), &
amp;QOpcUaNode::
attributeRead, this
, &
amp;TreeItem::
handleAttributes);
connect(mOpcNode.get(), &
amp;QOpcUaNode::
attributeUpdated, this
, &
amp;TreeItem::
handleAttributes);
connect(mOpcNode.get(), &
amp;QOpcUaNode::
browseFinished, this
, &
amp;TreeItem::
browseFinished);
if
(!
mOpcNode-&
gt;readAttributes( QOpcUa::NodeAttribute::
Value
|
QOpcUa::NodeAttribute::
NodeClass
|
QOpcUa::NodeAttribute::
Description
|
QOpcUa::NodeAttribute::
DataType
|
QOpcUa::NodeAttribute::
BrowseName
|
QOpcUa::NodeAttribute::
DisplayName
|
QOpcUa::NodeAttribute::
Historizing
)) {
qWarning() &
lt;&
lt; "Reading attributes"
&
lt;&
lt; mOpcNode-&
gt;nodeId() &
lt;&
lt; "failed"
;
}
}
TreeItem::
TreeItem(QOpcUaNode *
node, OpcUaModel *
model, const
QOpcUaReferenceDescription &
amp;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 &
gt;=
mChildItems.size())
qCritical() &
lt;&
lt; "TreeItem in row"
&
lt;&
lt; row &
lt;&
lt; "does not exist."
;
return
mChildItems[row];
}
int
TreeItem::
childIndex(const
TreeItem *
child) const
{
return
mChildItems.indexOf(const_cast
&
lt;TreeItem *&
gt;(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-&
gt;attribute(QOpcUa::NodeAttribute::
DataType).toString();
const
auto
value =
mOpcNode-&
gt;attribute(QOpcUa::NodeAttribute::
Value);
return
variantToString(value, type);
}
if
(column ==
2
) {
QMetaEnum metaEnum =
QMetaEnum::
fromType&
lt;QOpcUa::
NodeClass&
gt;();
QString name =
metaEnum.valueToKey(int
(mNodeClass));
return
name +
" ("
+
QString::
number(int
(mNodeClass)) +
')'
;
}
if
(column ==
3
) {
if
(!
mAttributesReady)
return
tr("Loading ..."
);
const
QString typeId =
mOpcNode-&
gt;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-&
gt;attribute(QOpcUa::NodeAttribute::
Description).value&
lt;QOpcUaLocalizedText&
gt;().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-&
gt;childIndex(this
);
}
TreeItem *
TreeItem::
parentItem()
{
return
mParentItem;
}
void
TreeItem::
appendChild(TreeItem *
child)
{
if
(!
child)
return
;
if
(!
hasChildNodeItem(child-&
gt;mNodeId)) {
mChildItems.append(child);
mChildNodeIds.insert(child-&
gt;mNodeId);
}
else
{
child-&
gt;deleteLater();
}
}
static
QPixmap createPixmap(const
QColor &
amp;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 &
amp;nodeId) const
{
return
mChildNodeIds.contains(nodeId);
}
void
TreeItem::
setMonitoringEnabled(bool
active)
{
if
(!
supportsMonitoring())
return
;
if
(active) {
mOpcNode-&
gt;enableMonitoring(QOpcUa::NodeAttribute::
Value, QOpcUaMonitoringParameters(500
));
}
else
{
mOpcNode-&
gt;disableMonitoring(QOpcUa::NodeAttribute::
Value);
}
}
bool
TreeItem::
monitoringEnabled() const
{
QOpcUaMonitoringParameters monitoring =
mOpcNode.get()-&
gt;monitoringStatus(QOpcUa::NodeAttribute::
Value);
return
monitoring.statusCode() ==
QOpcUa::UaStatusCode::
Good &
amp;&
amp;
monitoring.monitoringMode() ==
QOpcUaMonitoringParameters::MonitoringMode::
Reporting;
}
bool
TreeItem::
supportsMonitoring() const
{
return
mNodeClass ==
QOpcUa::NodeClass::
Variable;
}
void
TreeItem::
startBrowsing()
{
if
(mBrowseStarted)
return
;
if
(!
mOpcNode-&
gt;browseChildren())
qWarning() &
lt;&
lt; "Browsing node"
&
lt;&
lt; mOpcNode-&
gt;nodeId() &
lt;&
lt; "failed"
;
else
mBrowseStarted =
true
;
}
void
TreeItem::
handleAttributes(QOpcUa::
NodeAttributes attr)
{
if
(attr &
amp; QOpcUa::NodeAttribute::
NodeClass)
mNodeClass =
mOpcNode-&
gt;attribute(QOpcUa::NodeAttribute::
NodeClass).value&
lt;QOpcUa::
NodeClass&
gt;();
if
(attr &
amp; QOpcUa::NodeAttribute::
BrowseName)
mNodeBrowseName =
mOpcNode-&
gt;attribute(QOpcUa::NodeAttribute::
BrowseName).value&
lt;QOpcUaQualifiedName&
gt;().name();
if
(attr &
amp; QOpcUa::NodeAttribute::
DisplayName)
mNodeDisplayName =
mOpcNode-&
gt;attribute(QOpcUa::NodeAttribute::
DisplayName).value&
lt;QOpcUaLocalizedText&
gt;().text();
if
(attr &
amp; QOpcUa::NodeAttribute::
Historizing) {
mHistorizing =
mOpcNode-&
gt;attribute(QOpcUa::NodeAttribute::
Historizing).value&
lt;bool
&
gt;();
}
mAttributesReady =
true
;
emit mModel-&
gt;dataChanged(mModel-&
gt;createIndex(row(), 0
, this
), mModel-&
gt;createIndex(row(), numberOfDisplayColumns -
1
, this
));
}
void
TreeItem::
browseFinished(const
QList&
lt;QOpcUaReferenceDescription&
gt; &
amp;children, QOpcUa::
UaStatusCode statusCode)
{
if
(statusCode !=
QOpcUa::
Good) {
qWarning() &
lt;&
lt; "Browsing node"
&
lt;&
lt; mOpcNode-&
gt;nodeId() &
lt;&
lt; "finally failed:"
&
lt;&
lt; statusCode;
return
;
}
auto
index =
mModel-&
gt;createIndex(row(), 0
, this
);
for
(const
auto
&
amp;item : children) {
if
(hasChildNodeItem(item.targetNodeId().nodeId()))
continue
;
auto
node =
mModel-&
gt;opcUaClient()-&
gt;node(item.targetNodeId());
if
(!
node) {
qWarning() &
lt;&
lt; "Failed to instantiate node:"
&
lt;&
lt; item.targetNodeId().nodeId();
continue
;
}
mModel-&
gt;beginInsertRows(index, mChildItems.size(), mChildItems.size() +
1
);
appendChild(new
TreeItem(node, mModel, item, this
));
mModel-&
gt;endInsertRows();
}
emit mModel-&
gt;dataChanged(mModel-&
gt;createIndex(row(), 0
, this
), mModel-&
gt;createIndex(row(), numberOfDisplayColumns -
1
, this
));
}
QString TreeItem::
variantToString(const
QVariant &
amp;value, const
QString &
amp;typeNodeId) const
{
if
(value.metaType().id() ==
QMetaType::
QVariantList) {
const
auto
list =
value.toList();
QString concat;
for
(int
i =
0
, size =
list.size(); i &
lt; size; ++
i) {
if
(i)
concat.append('
\n
'
_L1);
concat.append(variantToString(list.at(i), typeNodeId));
}
return
concat;
}
if
(typeNodeId ==
"ns=0;i=19"
_L1) {
// StatusCode
const
char
*
name =
QMetaEnum::
fromType&
lt;QOpcUa::
UaStatusCode&
gt;().valueToKey(value.toInt());
return
name ? QLatin1StringView(name) : "Unknown StatusCode"
_L1;
}
if
(typeNodeId ==
"ns=0;i=2"
_L1) // Char
return
QString::
number(value.toInt());
if
(typeNodeId ==
"ns=0;i=3"
_L1) // SChar
return
QString::
number(value.toUInt());
if
(typeNodeId ==
"ns=0;i=4"
_L1) // Int16
return
QString::
number(value.toInt());
if
(typeNodeId ==
"ns=0;i=5"
_L1) // UInt16
return
QString::
number(value.toUInt());
if
(value.metaType().id() ==
QMetaType::
QByteArray)
return
"0x"
_L1 +
value.toByteArray().toHex();
if
(value.metaType().id() ==
QMetaType::
QDateTime)
return
value.toDateTime().toString(Qt::
ISODate);
if
(value.canConvert&
lt;QOpcUaQualifiedName&
gt;()) {
const
auto
name =
value.value&
lt;QOpcUaQualifiedName&
gt;();
return
u"[NamespaceIndex: %1, Name:
\"
%2
\"
]"
_s.arg(name.namespaceIndex()).arg(name.name());
}
if
(value.canConvert&
lt;QOpcUaLocalizedText&
gt;()) {
const
auto
text =
value.value&
lt;QOpcUaLocalizedText&
gt;();
return
localizedTextToString(text);
}
if
(value.canConvert&
lt;QOpcUaRange&
gt;()) {
const
auto
range =
value.value&
lt;QOpcUaRange&
gt;();
return
rangeToString(range);
}
if
(value.canConvert&
lt;QOpcUaComplexNumber&
gt;()) {
const
auto
complex =
value.value&
lt;QOpcUaComplexNumber&
gt;();
return
u"[Real: %1, Imaginary: %2]"
_s.arg(complex.real()).arg(complex.imaginary());
}
if
(value.canConvert&
lt;QOpcUaDoubleComplexNumber&
gt;()) {
const
auto
complex =
value.value&
lt;QOpcUaDoubleComplexNumber&
gt;();
return
u"[Real: %1, Imaginary: %2]"
_s.arg(complex.real()).arg(complex.imaginary());
}
if
(value.canConvert&
lt;QOpcUaXValue&
gt;()) {
const
auto
xv =
value.value&
lt;QOpcUaXValue&
gt;();
return
u"[X: %1, Value: %2]"
_s.arg(xv.x()).arg(xv.value());
}
if
(value.canConvert&
lt;QOpcUaEUInformation&
gt;()) {
const
auto
info =
value.value&
lt;QOpcUaEUInformation&
gt;();
return
euInformationToString(info);
}
if
(value.canConvert&
lt;QOpcUaAxisInformation&
gt;()) {
const
auto
info =
value.value&
lt;QOpcUaAxisInformation&
gt;();
return
u"[EUInformation: %1, EURange: %2, Title: %3 , AxisScaleType: %4, AxisSteps: %5]"
_s.arg(
euInformationToString(info.engineeringUnits()), rangeToString(info.eURange()), localizedTextToString(info.title()),
info.axisScaleType() ==
QOpcUa::AxisScale::
Linear ? "Linear"
: (info.axisScaleType() ==
QOpcUa::AxisScale::
Ln) ? "Ln"
: "Log"
,
numberArrayToString(info.axisSteps()));
}
if
(value.canConvert&
lt;QOpcUaExpandedNodeId&
gt;()) {
const
auto
id =
value.value&
lt;QOpcUaExpandedNodeId&
gt;();
return
u"[NodeId:
\"
%1
\"
, ServerIndex:
\"
%2
\"
, NamespaceUri:
\"
%3
\"
]"
_s.arg(
id.nodeId()).arg(id.serverIndex()).arg(id.namespaceUri());
}
if
(value.canConvert&
lt;QOpcUaArgument&
gt;()) {
const
auto
a =
value.value&
lt;QOpcUaArgument&
gt;();
return
u"[Name:
\"
%1
\"
, DataType:
\"
%2
\"
, ValueRank:
\"
%3
\"
, ArrayDimensions: %4, Description: %5]"
_s.arg(
a.name(), a.dataTypeId()).arg(a.valueRank()).arg(numberArrayToString(a.arrayDimensions()),
localizedTextToString(a.description()));
}
if
(value.canConvert&
lt;QOpcUaExtensionObject&
gt;()) {
auto
obj =
value.value&
lt;QOpcUaExtensionObject&
gt;();
if
(mModel-&
gt;genericStructHandler() &
amp;&
amp;
mModel-&
gt;genericStructHandler()-&
gt;dataTypeKindForTypeId(mModel-&
gt;genericStructHandler()-&
gt;typeIdForBinaryEncodingId(obj.encodingTypeId()))
==
QOpcUaGenericStructHandler::DataTypeKind::
Struct) {
const
auto
decodedValue =
mModel-&
gt;genericStructHandler()-&
gt;decode(obj);
if
(decodedValue)
return
decodedValue-&
gt;toString();
}
return
u"[TypeId:
\"
%1
\"
, Encoding: %2, Body: 0x%3]"
_s.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&
lt;QString&
gt;())
return
value.toString();
return
QString();
}
QString TreeItem::
localizedTextToString(const
QOpcUaLocalizedText &
amp;text) const
{
return
u"[Locale:
\"
%1
\"
, Text:
\"
%2
\"
]"
_s.arg(text.locale(), text.text());
}
QString TreeItem::
rangeToString(const
QOpcUaRange &
amp;range) const
{
return
u"[Low: %1, High: %2]"
_s.arg(range.low()).arg(range.high());
}
QString TreeItem::
euInformationToString(const
QOpcUaEUInformation &
amp;info) const
{
return
u"[UnitId: %1, NamespaceUri:
\"
%2
\"
, DisplayName: %3, Description: %4]"
_s.arg(info.unitId()).arg(
info.namespaceUri(), localizedTextToString(info.displayName()), localizedTextToString(info.description()));
}