summaryrefslogtreecommitdiffstats
path: root/demo/NodePage.qml
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-02-19 06:18:29 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-02-19 06:18:29 +0100
commit0c1df583acba434e2d7f6905a30fdefe288d0f9d (patch)
treee485fb1510ce2441622c4b29b8762633849f6fd2 /demo/NodePage.qml
parenta0c7f2a7ef04dbe2e7491eabf828e26423d1bd10 (diff)
downloadBobinkQtOpcUa-0c1df583acba434e2d7f6905a30fdefe288d0f9d.tar.gz
BobinkQtOpcUa-0c1df583acba434e2d7f6905a30fdefe288d0f9d.zip
Add BobinkNode QML type and two-page demo for node monitoring
BobinkNode (QQuickItem) monitors a single OPC UA node with automatic lifecycle: monitoring starts/stops based on item visibility (StackView page switches, Loader, etc.). Properties: nodeId, value (r/w), status, sourceTimestamp, serverTimestamp. On-demand readAttribute() for metadata. writeError signal for failed writes. Demo restructured with StackView: connection page → two node pages demonstrating the visibility-based monitoring lifecycle.
Diffstat (limited to 'demo/NodePage.qml')
-rw-r--r--demo/NodePage.qml164
1 files changed, 164 insertions, 0 deletions
diff --git a/demo/NodePage.qml b/demo/NodePage.qml
new file mode 100644
index 0000000..e4862d8
--- /dev/null
+++ b/demo/NodePage.qml
@@ -0,0 +1,164 @@
+// 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: BobinkClient.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();
+ }
+ }
+ }
+}