// NodePage.qml — Monitors a single OPC UA node. // Two instances demonstrate visibility lifecycle: switching pages // stops monitoring on the hidden page automatically. import QtQuick import QtQuick.Controls import QtQuick.Layouts import Bobink Page { id: nodePage required property StackView stackRef 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; } onWriteError: (message) => nodePage.logFunction("WRITE: " + message) } ColumnLayout { anchors.fill: parent anchors.margins: 20 spacing: 12 RowLayout { Label { text: "Node Page " + nodePage.pageNumber font.bold: true font.pointSize: 14 } Item { Layout.fillWidth: true } Button { text: "Disconnect" onClicked: Bobink.disconnectFromServer() } } RowLayout { TextField { id: nodeIdField Layout.fillWidth: true placeholderText: "ns=2;s=Temperature" enabled: !nodePage.monitoredNodeId } 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"); } } } } GroupBox { 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 } 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" } Label { text: "Source TS:" } Label { text: node.sourceTimestamp.toLocaleString( Qt.locale(), "HH:mm:ss.zzz") font.family: "monospace" } Label { text: "Server TS:" } Label { text: node.serverTimestamp.toLocaleString( Qt.locale(), "HH:mm:ss.zzz") font.family: "monospace" } } } RowLayout { visible: nodePage.monitoredNodeId.length > 0 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(); } } } Label { visible: nodePage.monitoredNodeId.length > 0 && nodePage.metadataLoaded && !nodePage.nodeWritable text: "Read-only node" font.italic: true color: "gray" } Item { Layout.fillHeight: true } Button { 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(); } } } }