diff options
Diffstat (limited to 'demo/NodePage.qml')
| -rw-r--r-- | demo/NodePage.qml | 298 |
1 files changed, 189 insertions, 109 deletions
diff --git a/demo/NodePage.qml b/demo/NodePage.qml index fd81db5..a8e07d5 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,81 @@ 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" + ] + }, + { + title: "Empty (Monitoring Test)", + description: "No nodes on this page. All previous pages are inactive," + + " so the server log should show zero active monitored items.", + nodes: [] } - 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 + "/" + nodePage.pages.length + ")" + color: "gray" + } Item { Layout.fillWidth: true } Button { text: "Disconnect" @@ -50,114 +94,150 @@ Page { } } + Label { + id: pageDescription + visible: currentPage.description !== undefined + text: currentPage.description || "" + wrapMode: Text.WordWrap + color: "gray" + font.italic: true + Layout.fillWidth: true + } + + // 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 < nodePage.pages.length + onClicked: nodePage.stackRef.push("NodePage.qml", { + stackRef: nodePage.stackRef, + pageNumber: nodePage.pageNumber + 1, + logFunction: nodePage.logFunction + }) } } } |
