summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--demo/NodePage.qml266
-rw-r--r--src/OpcUaMonitoredNode.cpp24
-rw-r--r--src/OpcUaMonitoredNode.h4
3 files changed, 163 insertions, 131 deletions
diff --git a/demo/NodePage.qml b/demo/NodePage.qml
index 2de0a15..1468817 100644
--- a/demo/NodePage.qml
+++ b/demo/NodePage.qml
@@ -1,4 +1,4 @@
-// NodePage.qml — Demo page displaying a single OPC UA node.
+// NodePage.qml — Displays 10 OPC UA nodes per page with tooltips.
import QtQuick
import QtQuick.Controls
@@ -12,167 +12,179 @@ Page {
required property int pageNumber
required property var logFunction
- OpcUaMonitoredNode {
- id: demoNode
- nodeId: nodePage.pageNumber === 1 ? "ns=1;s=double_rw_scalar" : "ns=1;s=string_rw_scalar"
- monitored: nodePage.StackView.status === StackView.Active
- onMonitoredChanged: nodePage.logFunction("Page " + nodePage.pageNumber + " node [" + nodeId + "] " + (monitored ? "MONITORED" : "UNMONITORED"))
- onValueChanged: nodePage.logFunction("Page " + nodePage.pageNumber + " value = " + value)
- }
+ readonly property var pages: [
+ {
+ title: "Read-Write Scalars",
+ nodes: [
+ "ns=1;s=bool_rw_scalar",
+ "ns=1;s=int16_rw_scalar",
+ "ns=1;s=uint16_rw_scalar",
+ "ns=1;s=int32_rw_scalar",
+ "ns=1;s=uint32_rw_scalar",
+ "ns=1;s=int64_rw_scalar",
+ "ns=1;s=uint64_rw_scalar",
+ "ns=1;s=float_rw_scalar",
+ "ns=1;s=double_rw_scalar",
+ "ns=1;s=string_rw_scalar"
+ ]
+ },
+ {
+ title: "Read-Only Scalars",
+ nodes: [
+ "ns=1;s=bool_ro_scalar",
+ "ns=1;s=int16_ro_scalar",
+ "ns=1;s=uint16_ro_scalar",
+ "ns=1;s=int32_ro_scalar",
+ "ns=1;s=uint32_ro_scalar",
+ "ns=1;s=int64_ro_scalar",
+ "ns=1;s=uint64_ro_scalar",
+ "ns=1;s=float_ro_scalar",
+ "ns=1;s=double_ro_scalar",
+ "ns=1;s=string_ro_scalar"
+ ]
+ },
+ {
+ title: "Read-Write Arrays",
+ nodes: [
+ "ns=1;s=bool_rw_array",
+ "ns=1;s=int16_rw_array",
+ "ns=1;s=uint16_rw_array",
+ "ns=1;s=int32_rw_array",
+ "ns=1;s=uint32_rw_array",
+ "ns=1;s=int64_rw_array",
+ "ns=1;s=uint64_rw_array",
+ "ns=1;s=float_rw_array",
+ "ns=1;s=double_rw_array",
+ "ns=1;s=string_rw_array"
+ ]
+ }
+ ]
+
+ readonly property var currentPage: pages[pageNumber - 1]
+
+ Component.onCompleted: nodePage.logFunction(
+ currentPage.title + " page loaded (" + currentPage.nodes.length + " nodes)")
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
- spacing: 8
+ spacing: 4
+ // Header
RowLayout {
Label {
- text: "Node Page " + nodePage.pageNumber
+ text: currentPage.title
font.bold: true
font.pointSize: 14
}
- Item {
- Layout.fillWidth: true
+ Label {
+ text: "(" + nodePage.pageNumber + "/3)"
+ color: "gray"
}
+ Item { Layout.fillWidth: true }
Button {
text: "Disconnect"
onClicked: Bobink.disconnectFromServer()
}
}
- // Value (prominent)
- Rectangle {
- Layout.fillWidth: true
- height: 60
- color: "#f0f0f0"
- radius: 4
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 8
- spacing: 2
-
- Label {
- text: "Value"
- font.pointSize: 10
- color: "gray"
- }
- Label {
- text: demoNode.value !== undefined ? String(demoNode.value) : "—"
- font.pointSize: 16
- font.bold: true
- elide: Text.ElideRight
- Layout.fillWidth: true
- }
- }
- }
-
- // Node info
- GridLayout {
+ // Column headers
+ RowLayout {
Layout.fillWidth: true
- columns: 2
- columnSpacing: 12
- rowSpacing: 4
-
- Label {
- text: "Node ID:"
- color: "gray"
- }
- Label {
- text: demoNode.nodeId
- Layout.fillWidth: true
- }
-
- Label {
- text: "Display Name:"
- color: "gray"
- }
- Label {
- text: demoNode.info.displayName || "—"
- Layout.fillWidth: true
- }
-
+ spacing: 0
Label {
- text: "Data Type:"
- color: "gray"
+ text: "Identifier"
+ font.bold: true
+ Layout.preferredWidth: 200
+ leftPadding: 12
}
Label {
- text: demoNode.info.dataType || "—"
+ text: "Value"
+ font.bold: true
Layout.fillWidth: true
}
+ }
- Label {
- text: "Node Class:"
- color: "gray"
- }
- Label {
- text: demoNode.info.nodeClass || "—"
- Layout.fillWidth: true
- }
+ Rectangle {
+ Layout.fillWidth: true
+ height: 1
+ color: "#ccc"
+ }
- Label {
- text: "Access Level:"
- color: "gray"
- }
- Label {
- text: demoNode.info.accessLevel
- Layout.fillWidth: true
- }
+ // Nodes
+ Repeater {
+ model: currentPage.nodes
- Label {
- text: "Status:"
- color: "gray"
- }
- Label {
- text: demoNode.info.status || "—"
+ ItemDelegate {
+ id: delegate
+ required property string modelData
+ required property int index
Layout.fillWidth: true
- }
+ padding: 4
+ leftPadding: 12
+ rightPadding: 12
+
+ OpcUaMonitoredNode {
+ id: node
+ nodeId: delegate.modelData
+ monitored: nodePage.StackView.status === StackView.Active
+ }
- Label {
- text: "Source Time:"
- color: "gray"
- }
- Label {
- text: demoNode.info.sourceTimestamp.toLocaleString() || "—"
- Layout.fillWidth: true
- }
+ background: Rectangle {
+ color: delegate.index % 2 === 0 ? "#f8f8f8" : "transparent"
+ radius: 2
+ }
- Label {
- text: "Server Time:"
- color: "gray"
- }
- Label {
- text: demoNode.info.serverTimestamp.toLocaleString() || "—"
- Layout.fillWidth: true
- }
+ contentItem: RowLayout {
+ spacing: 12
+ Label {
+ text: {
+ var idx = node.nodeId.indexOf(";s=");
+ return idx >= 0 ? node.nodeId.substring(idx + 3) : node.nodeId;
+ }
+ Layout.preferredWidth: 188
+ elide: Text.ElideRight
+ }
+ Label {
+ text: node.value !== undefined && String(node.value) !== "" ? String(node.value) : "—"
+ Layout.fillWidth: true
+ elide: Text.ElideRight
+ }
+ }
- Label {
- text: "Monitored:"
- color: "gray"
- }
- Label {
- text: demoNode.monitored ? "Yes" : "No"
- color: demoNode.monitored ? "green" : "gray"
- Layout.fillWidth: true
+ ToolTip.visible: hovered
+ ToolTip.delay: 400
+ ToolTip.text:
+ "Display Name: " + (node.info.displayName || "—")
+ + "\nDescription: " + (node.info.description || "—")
+ + "\nNode Class: " + (node.info.nodeClass || "—")
+ + "\nData Type: " + (node.info.dataType || "—")
+ + "\nAccess Level: " + node.info.accessLevel
+ + "\nStatus: " + (node.info.status || "—")
+ + "\nSource Time: " + (node.info.sourceTimestamp.toLocaleString() || "—")
+ + "\nServer Time: " + (node.info.serverTimestamp.toLocaleString() || "—")
}
}
- Item {
- Layout.fillHeight: true
- }
+ Item { Layout.fillHeight: true }
- Button {
+ // Navigation
+ RowLayout {
Layout.fillWidth: true
- text: nodePage.pageNumber === 1 ? "Go to Page 2" : "Back to Page 1"
- onClicked: {
- if (nodePage.pageNumber === 1)
- nodePage.stackRef.push("NodePage.qml", {
- stackRef: nodePage.stackRef,
- pageNumber: 2,
- logFunction: nodePage.logFunction
- });
- else
- nodePage.stackRef.pop();
+ Button {
+ text: "← Previous"
+ visible: nodePage.pageNumber > 1
+ onClicked: nodePage.stackRef.pop()
+ }
+ Item { Layout.fillWidth: true }
+ Button {
+ text: "Next →"
+ visible: nodePage.pageNumber < 3
+ onClicked: nodePage.stackRef.push("NodePage.qml", {
+ stackRef: nodePage.stackRef,
+ pageNumber: nodePage.pageNumber + 1,
+ logFunction: nodePage.logFunction
+ })
}
}
}
diff --git a/src/OpcUaMonitoredNode.cpp b/src/OpcUaMonitoredNode.cpp
index 0ffe300..b158881 100644
--- a/src/OpcUaMonitoredNode.cpp
+++ b/src/OpcUaMonitoredNode.cpp
@@ -7,6 +7,7 @@
#include <QMetaEnum>
#include <QOpcUaLocalizedText>
+#include <QStringList>
OpcUaMonitoredNode::OpcUaMonitoredNode (QObject *parent) : QObject (parent) {}
@@ -161,10 +162,29 @@ OpcUaMonitoredNode::handleAttributeUpdated (QOpcUa::NodeAttribute attr,
}
break;
case QOpcUa::NodeAttribute::DataType:
- m_info.dataType = value.toString ();
+ {
+ auto ns0 = QOpcUa::namespace0IdFromNodeId (value.toString ());
+ m_info.dataType = QOpcUa::namespace0IdName (ns0);
+ if (m_info.dataType.isEmpty ())
+ m_info.dataType = value.toString ();
+ }
break;
case QOpcUa::NodeAttribute::AccessLevel:
- m_info.accessLevel = value.toUInt ();
+ {
+ quint32 bits = value.toUInt ();
+ QStringList flags;
+ if (bits & quint32 (QOpcUa::AccessLevelBit::CurrentRead))
+ flags << QStringLiteral ("Read");
+ if (bits & quint32 (QOpcUa::AccessLevelBit::CurrentWrite))
+ flags << QStringLiteral ("Write");
+ if (bits & quint32 (QOpcUa::AccessLevelBit::HistoryRead))
+ flags << QStringLiteral ("History Read");
+ if (bits & quint32 (QOpcUa::AccessLevelBit::HistoryWrite))
+ flags << QStringLiteral ("History Write");
+ m_info.accessLevel = flags.isEmpty ()
+ ? QStringLiteral ("None")
+ : flags.join (QStringLiteral (", "));
+ }
break;
default:
return;
diff --git a/src/OpcUaMonitoredNode.h b/src/OpcUaMonitoredNode.h
index 53e9537..ac5983a 100644
--- a/src/OpcUaMonitoredNode.h
+++ b/src/OpcUaMonitoredNode.h
@@ -30,7 +30,7 @@ struct OpcUaNodeInfo
Q_PROPERTY (QString description MEMBER description)
Q_PROPERTY (QString nodeClass MEMBER nodeClass)
Q_PROPERTY (QString dataType MEMBER dataType)
- Q_PROPERTY (quint32 accessLevel MEMBER accessLevel)
+ Q_PROPERTY (QString accessLevel MEMBER accessLevel)
Q_PROPERTY (QString status MEMBER status)
Q_PROPERTY (QDateTime sourceTimestamp MEMBER sourceTimestamp)
Q_PROPERTY (QDateTime serverTimestamp MEMBER serverTimestamp)
@@ -40,7 +40,7 @@ public:
QString description;
QString nodeClass;
QString dataType;
- quint32 accessLevel = 0;
+ QString accessLevel;
QString status;
QDateTime sourceTimestamp;
QDateTime serverTimestamp;