summaryrefslogtreecommitdiffstats
path: root/demo
diff options
context:
space:
mode:
Diffstat (limited to 'demo')
-rw-r--r--demo/Main.qml10
-rw-r--r--demo/NodePage.qml282
2 files changed, 178 insertions, 114 deletions
diff --git a/demo/Main.qml b/demo/Main.qml
index 9d3e168..ca0a419 100644
--- a/demo/Main.qml
+++ b/demo/Main.qml
@@ -82,7 +82,7 @@ ApplicationWindow {
Bobink.startDiscovery();
}
- BobinkAuth {
+ OpcUaAuth {
id: auth
mode: authModeCombo.currentValue
username: usernameField.text
@@ -267,22 +267,22 @@ ApplicationWindow {
model: [
{
text: "Anonymous",
- mode: BobinkAuth.Anonymous
+ mode: OpcUaAuth.Anonymous
},
{
text: "Username / Password",
- mode: BobinkAuth.UserPass
+ mode: OpcUaAuth.UserPass
},
{
text: "Certificate",
- mode: BobinkAuth.Certificate
+ mode: OpcUaAuth.Certificate
}
]
}
GridLayout {
columns: 2
- visible: authModeCombo.currentValue === BobinkAuth.UserPass
+ visible: authModeCombo.currentValue === OpcUaAuth.UserPass
Layout.fillWidth: true
Label {
diff --git a/demo/NodePage.qml b/demo/NodePage.qml
index fd81db5..442a6fd 100644
--- a/demo/NodePage.qml
+++ b/demo/NodePage.qml
@@ -1,6 +1,4 @@
-// NodePage.qml — Monitors a single OPC UA node.
-// Two instances demonstrate visibility lifecycle: switching pages
-// stops monitoring on the hidden page automatically.
+// NodePage.qml — Displays 10 OPC UA nodes per page with tooltips.
import QtQuick
import QtQuick.Controls
@@ -14,35 +12,75 @@ Page {
required property int pageNumber
required property var logFunction
- property string monitoredNodeId: ""
- property string nodeDescription: ""
- property bool nodeWritable: true
- property bool metadataLoaded: false
-
- BobinkNode {
- id: node
- nodeId: nodePage.monitoredNodeId
- onAttributeRead: (name, val) => {
- if (name === "DisplayName")
- nodePage.nodeDescription = val.toString();
- else if (name === "AccessLevel")
- nodePage.nodeWritable = (val & 0x02) !== 0;
- nodePage.metadataLoaded = true;
+ 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"
+ ]
}
- onWriteError: (message) => nodePage.logFunction("WRITE: " + message)
- }
+ ]
+
+ 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: 12
+ spacing: 4
+ // Header
RowLayout {
Label {
- text: "Node Page " + nodePage.pageNumber
+ text: currentPage.title
font.bold: true
font.pointSize: 14
}
+ Label {
+ text: "(" + nodePage.pageNumber + "/3)"
+ color: "gray"
+ }
Item { Layout.fillWidth: true }
Button {
text: "Disconnect"
@@ -50,114 +88,140 @@ Page {
}
}
+ // Column headers
RowLayout {
- TextField {
- id: nodeIdField
- Layout.fillWidth: true
- placeholderText: "ns=2;s=Temperature"
- enabled: !nodePage.monitoredNodeId
+ Layout.fillWidth: true
+ Layout.leftMargin: 12
+ Layout.rightMargin: 12
+ spacing: 12
+ Label {
+ text: "Identifier"
+ font.bold: true
+ Layout.preferredWidth: 160
}
- Button {
- text: nodePage.monitoredNodeId ? "Stop" : "Monitor"
- onClicked: {
- if (nodePage.monitoredNodeId) {
- nodePage.monitoredNodeId = "";
- nodePage.nodeDescription = "";
- nodePage.nodeWritable = true;
- nodePage.metadataLoaded = false;
- } else {
- nodePage.monitoredNodeId = nodeIdField.text;
- node.readAttribute("DisplayName");
- node.readAttribute("AccessLevel");
- }
- }
+ Label {
+ text: "Value"
+ font.bold: true
+ Layout.preferredWidth: 160
+ }
+ Label {
+ text: "Write"
+ font.bold: true
+ Layout.fillWidth: true
}
}
- GroupBox {
+ Rectangle {
+ id: separator
Layout.fillWidth: true
- visible: nodePage.monitoredNodeId.length > 0
- title: nodePage.nodeDescription || nodePage.monitoredNodeId
-
- GridLayout {
- columns: 2
- width: parent.width
-
- Label { text: "Value:" }
- Label {
- text: node.value !== undefined ? node.value.toString() : "—"
- font.family: "monospace"
- font.bold: true
- }
+ height: 1
+ color: "#ccc"
+ }
- Label { text: "Status:" }
- Label {
- text: node.status === BobinkNode.Good ? "Good"
- : node.status === BobinkNode.Uncertain ? "Uncertain"
- : "Bad"
- color: node.status === BobinkNode.Good ? "green"
- : node.status === BobinkNode.Uncertain ? "orange"
- : "red"
- }
+ // Nodes
+ Repeater {
+ model: currentPage.nodes
- Label { text: "Source TS:" }
- Label {
- text: node.sourceTimestamp.toLocaleString(
- Qt.locale(), "HH:mm:ss.zzz")
- font.family: "monospace"
+ 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
+ onWriteCompleted: (success, message) => {
+ var short_id = node.nodeId.substring(node.nodeId.indexOf(";s=") + 3);
+ nodePage.logFunction(short_id + ": " + message);
+ }
}
- Label { text: "Server TS:" }
- Label {
- text: node.serverTimestamp.toLocaleString(
- Qt.locale(), "HH:mm:ss.zzz")
- font.family: "monospace"
+ background: Rectangle {
+ color: delegate.index % 2 === 0 ? "#f8f8f8" : "transparent"
+ radius: 2
}
- }
- }
- RowLayout {
- visible: nodePage.monitoredNodeId.length > 0
+ contentItem: RowLayout {
+ spacing: 12
+
+ // Column 1: Identifier
+ Label {
+ text: {
+ var idx = node.nodeId.indexOf(";s=");
+ return idx >= 0 ? node.nodeId.substring(idx + 3) : node.nodeId;
+ }
+ Layout.preferredWidth: 160
+ elide: Text.ElideRight
+ }
- TextField {
- id: writeField
- Layout.fillWidth: true
- enabled: nodePage.nodeWritable
- placeholderText: nodePage.metadataLoaded && !nodePage.nodeWritable
- ? "Read-only node" : "Value to write"
- }
- Button {
- text: "Write"
- enabled: nodePage.nodeWritable
- onClicked: {
- node.value = writeField.text;
- writeField.clear();
+ // Column 2: Live value (always visible)
+ Label {
+ text: node.value !== undefined && String(node.value) !== "" ? String(node.value) : "—"
+ Layout.preferredWidth: 160
+ elide: Text.ElideRight
+ }
+
+ // Column 3: Edit area (writable) or READ-ONLY label
+ TextField {
+ id: editField
+ visible: node.writable
+ Layout.fillWidth: true
+ placeholderText: "Enter value..."
+ onAccepted: node.writeValue(text)
+ }
+ Button {
+ id: writeButton
+ visible: node.writable
+ text: "Write"
+ onClicked: node.writeValue(editField.text)
+ }
+ Label {
+ visible: !node.writable
+ text: "(READ-ONLY)"
+ color: "gray"
+ font.italic: true
+ Layout.fillWidth: true
+ }
}
- }
- }
- Label {
- visible: nodePage.monitoredNodeId.length > 0
- && nodePage.metadataLoaded && !nodePage.nodeWritable
- text: "Read-only node"
- font.italic: true
- color: "gray"
+ 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 }
- 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
+ })
}
}
}