diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-24 18:23:32 +0100 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-25 10:12:36 +0100 |
| commit | 38e3a2e587796df133f50363e65a68ec7989cb9b (patch) | |
| tree | aa0642e4f82ee0ba9a88ef8a0e56c527a643e0c7 /Main.qml | |
| download | BobinkQtOpcUaAppTemplate-38e3a2e587796df133f50363e65a68ec7989cb9b.tar.gz BobinkQtOpcUaAppTemplate-38e3a2e587796df133f50363e65a68ec7989cb9b.zip | |
Initial commit: BobinkQtOpcUa app template with demo UI
Qt 6 QML application template using BobinkQtOpcUa as a submodule for
OPC UA connectivity. Includes discovery, PKI, auth, node monitoring
with read/write, and a multi-page demo UI.
Diffstat (limited to 'Main.qml')
| -rw-r--r-- | Main.qml | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/Main.qml b/Main.qml new file mode 100644 index 0000000..361e3bd --- /dev/null +++ b/Main.qml @@ -0,0 +1,438 @@ +// Main.qml — Demo app for Bobink library. +// Connects to an OPC UA server, then pushes NodePage for node interaction. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs +import Bobink + +ApplicationWindow { + id: root + width: 800 + height: 900 + visible: true + title: "Bobink Demo" + + property bool autoConnectFailed: false + property bool showPkiSettings: false + + Connections { + target: Bobink + function onServersChanged() { + debugConsole.appendLog("Discovered server list updated"); + } + function onConnectedChanged() { + debugConsole.appendLog("Connected: " + Bobink.connected); + if (Bobink.connected) { + root.autoConnectFailed = false; + Bobink.stopDiscovery(); + stack.push("NodePage.qml", { + stackRef: stack, + pageNumber: 1, + logFunction: debugConsole.appendLog + }); + } else { + stack.pop(null); + } + } + function onConnectionError(message) { + debugConsole.appendLog("Connection error: " + message); + root.autoConnectFailed = true; + } + function onStatusMessage(message) { + debugConsole.appendLog(message); + } + function onDiscoveringChanged() { + debugConsole.appendLog("Discovering: " + Bobink.discovering); + } + function onCertificateTrustRequested(certInfo) { + certTrustDialog.certInfo = certInfo; + certTrustDialog.open(); + } + } + + Dialog { + id: certTrustDialog + property string certInfo + anchors.centerIn: parent + title: "Certificate Trust" + modal: true + standardButtons: Dialog.Yes | Dialog.No + Label { + text: certTrustDialog.certInfo + } + onAccepted: Bobink.acceptCertificate() + onRejected: Bobink.rejectCertificate() + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + StackView { + id: stack + Layout.fillWidth: true + Layout.fillHeight: true + + initialItem: Page { + id: connectionPage + Component.onCompleted: { + Bobink.discoveryUrl = discoveryUrlField.text; + Bobink.startDiscovery(); + } + + OpcUaAuth { + id: auth + mode: authModeCombo.currentValue + username: usernameField.text + password: passwordField.text + certPath: Bobink.certFile + keyPath: Bobink.keyFile + } + + FileDialog { + id: certFileDialog + title: "Select Certificate" + currentFolder: "file://" + trustFolderField.text + nameFilters: ["DER certificates (*.der)", "All files (*)"] + onAccepted: certFileField.text = selectedFile.toString().replace("file://", "") + } + FileDialog { + id: keyFileDialog + title: "Select Private Key" + currentFolder: "file://" + trustFolderField.text + nameFilters: ["Key files (*.pem *.crt)", "All files (*)"] + onAccepted: keyFileField.text = selectedFile.toString().replace("file://", "") + } + FolderDialog { + id: trustFolderDialog + title: "Select Trust Folder" + currentFolder: "file://" + trustFolderField.text + onAccepted: trustFolderField.text = selectedFolder.toString().replace("file://", "") + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 20 + spacing: 12 + + Label { + text: "Discovery URL" + font.bold: true + } + + RowLayout { + TextField { + id: discoveryUrlField + Layout.fillWidth: true + text: "opc.tcp://localhost:4840" + } + Button { + text: Bobink.discovering ? "Stop" : "Discover" + onClicked: { + if (Bobink.discovering) { + Bobink.stopDiscovery(); + } else { + Bobink.discoveryUrl = discoveryUrlField.text; + Bobink.startDiscovery(); + } + } + } + } + + Label { + text: Bobink.discovering ? "Discovering... (" + Bobink.servers.length + " found)" : Bobink.servers.length + " server(s)" + font.italic: true + } + + ListView { + id: serverListView + Layout.fillWidth: true + Layout.preferredHeight: 100 + clip: true + model: Bobink.servers + delegate: ItemDelegate { + id: serverDelegate + required property var modelData + width: ListView.view.width + contentItem: ColumnLayout { + spacing: 2 + Label { + text: serverDelegate.modelData.serverName + } + Label { + text: serverDelegate.modelData.applicationUri + color: "gray" + font.italic: true + font.pointSize: 8 + elide: Text.ElideRight + Layout.fillWidth: true + } + } + onClicked: { + if (modelData.discoveryUrls.length > 0) + serverUrlField.text = modelData.discoveryUrls[0]; + } + } + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AsNeeded + } + } + + RowLayout { + Label { + text: "PKI" + font.bold: true + } + Label { + text: Bobink.certFile ? " (" + Bobink.certFile.split("/").pop() + ")" : " (no certificate found)" + font.italic: true + color: Bobink.certFile ? "green" : "gray" + } + Item { + Layout.fillWidth: true + } + Button { + text: root.showPkiSettings ? "Hide" : "Configure" + onClicked: root.showPkiSettings = !root.showPkiSettings + } + } + + GridLayout { + columns: 3 + Layout.fillWidth: true + visible: root.showPkiSettings + + Label { + text: "Certificate:" + } + TextField { + id: certFileField + Layout.fillWidth: true + text: Bobink.certFile + placeholderText: "Client certificate (.der)" + } + Button { + text: "Browse" + onClicked: certFileDialog.open() + } + + Label { + text: "Private key:" + } + TextField { + id: keyFileField + Layout.fillWidth: true + text: Bobink.keyFile + placeholderText: "Private key (.pem, .crt)" + } + Button { + text: "Browse" + onClicked: keyFileDialog.open() + } + + Label { + text: "Trust folder:" + } + TextField { + id: trustFolderField + Layout.fillWidth: true + text: Bobink.pkiDir + } + Button { + text: "Browse" + onClicked: trustFolderDialog.open() + } + } + + Button { + text: "Apply PKI" + Layout.fillWidth: true + visible: root.showPkiSettings + onClicked: { + Bobink.pkiDir = trustFolderField.text; + Bobink.certFile = certFileField.text; + Bobink.keyFile = keyFileField.text; + Bobink.applyPki(); + } + } + + Label { + text: "Server URL" + font.bold: true + } + + TextField { + id: serverUrlField + Layout.fillWidth: true + placeholderText: "opc.tcp://..." + } + + Label { + text: "Authentication" + font.bold: true + } + + ComboBox { + id: authModeCombo + Layout.fillWidth: true + textRole: "text" + valueRole: "mode" + model: [ + { + text: "Anonymous", + mode: OpcUaAuth.Anonymous + }, + { + text: "Username / Password", + mode: OpcUaAuth.UserPass + }, + { + text: "Certificate", + mode: OpcUaAuth.Certificate + } + ] + } + + GridLayout { + columns: 2 + visible: authModeCombo.currentValue === OpcUaAuth.UserPass + Layout.fillWidth: true + + Label { + text: "Username:" + } + TextField { + id: usernameField + Layout.fillWidth: true + } + Label { + text: "Password:" + } + TextField { + id: passwordField + Layout.fillWidth: true + echoMode: TextInput.Password + } + } + + Button { + text: "Connect" + Layout.fillWidth: true + onClicked: { + root.autoConnectFailed = false; + Bobink.auth = auth; + Bobink.serverUrl = serverUrlField.text; + Bobink.connectToServer(); + } + } + + Label { + text: "Direct Connect" + font.bold: true + visible: root.autoConnectFailed + } + + GridLayout { + columns: 2 + Layout.fillWidth: true + visible: root.autoConnectFailed + + Label { + text: "Security policy:" + } + ComboBox { + id: securityPolicyCombo + Layout.fillWidth: true + textRole: "text" + valueRole: "policy" + model: [ + { + text: "Basic256Sha256", + policy: Bobink.Basic256Sha256 + }, + { + text: "Aes128-Sha256-RsaOaep", + policy: Bobink.Aes128_Sha256_RsaOaep + }, + { + text: "Aes256-Sha256-RsaPss", + policy: Bobink.Aes256_Sha256_RsaPss + } + ] + } + + Label { + text: "Security mode:" + } + ComboBox { + id: securityModeCombo + Layout.fillWidth: true + textRole: "text" + valueRole: "mode" + model: [ + { + text: "Sign & Encrypt", + mode: Bobink.SignAndEncrypt + }, + { + text: "Sign", + mode: Bobink.Sign + }, + { + text: "None", + mode: Bobink.None + } + ] + } + } + + Button { + text: "Direct Connect" + Layout.fillWidth: true + visible: root.autoConnectFailed + onClicked: { + Bobink.auth = auth; + Bobink.serverUrl = serverUrlField.text; + Bobink.connectDirect(securityPolicyCombo.currentValue, securityModeCombo.currentValue); + } + } + + Item { + Layout.fillHeight: true + } + } + } + } + + Rectangle { + id: debugConsole + Layout.fillWidth: true + Layout.preferredHeight: 120 + color: "#1e1e1e" + border.color: "#444" + radius: 4 + + function appendLog(msg) { + let ts = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss"); + debugLog.text += "[" + ts + "] " + msg + "\n"; + debugLog.cursorPosition = debugLog.text.length; + } + + ScrollView { + anchors.fill: parent + anchors.margins: 4 + TextArea { + id: debugLog + readOnly: true + color: "#cccccc" + font.family: "monospace" + font.pointSize: 9 + wrapMode: TextEdit.Wrap + background: null + } + } + } + } +} |
