diff options
Diffstat (limited to 'demo')
| -rw-r--r-- | demo/Main.qml | 29 | ||||
| -rw-r--r-- | demo/NodePage.qml | 94 | ||||
| -rw-r--r-- | demo/main.cpp | 81 |
3 files changed, 177 insertions, 27 deletions
diff --git a/demo/Main.qml b/demo/Main.qml index ca0a419..361e3bd 100644 --- a/demo/Main.qml +++ b/demo/Main.qml @@ -153,15 +153,31 @@ ApplicationWindow { clip: true model: Bobink.servers delegate: ItemDelegate { + id: serverDelegate required property var modelData width: ListView.view.width - text: modelData.serverName + " — " + modelData.applicationUri + 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 {} + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AsNeeded + } } RowLayout { @@ -178,8 +194,7 @@ ApplicationWindow { Layout.fillWidth: true } Button { - text: root.showPkiSettings ? "Hide" : "Configure..." - flat: true + text: root.showPkiSettings ? "Hide" : "Configure" onClicked: root.showPkiSettings = !root.showPkiSettings } } @@ -199,7 +214,7 @@ ApplicationWindow { placeholderText: "Client certificate (.der)" } Button { - text: "Browse..." + text: "Browse" onClicked: certFileDialog.open() } @@ -213,7 +228,7 @@ ApplicationWindow { placeholderText: "Private key (.pem, .crt)" } Button { - text: "Browse..." + text: "Browse" onClicked: keyFileDialog.open() } @@ -226,7 +241,7 @@ ApplicationWindow { text: Bobink.pkiDir } Button { - text: "Browse..." + text: "Browse" onClicked: trustFolderDialog.open() } } diff --git a/demo/NodePage.qml b/demo/NodePage.qml index 442a6fd..e677232 100644 --- a/demo/NodePage.qml +++ b/demo/NodePage.qml @@ -14,7 +14,20 @@ Page { readonly property var pages: [ { + title: "Server Info", + description: "Standard OPC UA server nodes (namespace 0)." + + " CurrentTime updates live via monitoring.", + nodes: [ + "ns=0;i=2258", // CurrentTime + "ns=0;i=2257", // StartTime + "ns=0;i=2259", // State + "ns=0;i=2261", // ProductName + "ns=0;i=2264" // SoftwareVersion + ] + }, + { title: "Read-Write Scalars", + description: "Single-value nodes with read and write access.", nodes: [ "ns=1;s=bool_rw_scalar", "ns=1;s=int16_rw_scalar", @@ -25,11 +38,17 @@ Page { "ns=1;s=uint64_rw_scalar", "ns=1;s=float_rw_scalar", "ns=1;s=double_rw_scalar", - "ns=1;s=string_rw_scalar" + "ns=1;s=string_rw_scalar", + "ns=1;s=sbyte_rw_scalar", + "ns=1;s=byte_rw_scalar", + "ns=1;s=datetime_rw_scalar", + "ns=1;s=guid_rw_scalar", + "ns=1;s=bytestring_rw_scalar" ] }, { title: "Read-Only Scalars", + description: "Single-value nodes with read-only access.", nodes: [ "ns=1;s=bool_ro_scalar", "ns=1;s=int16_ro_scalar", @@ -40,11 +59,19 @@ Page { "ns=1;s=uint64_ro_scalar", "ns=1;s=float_ro_scalar", "ns=1;s=double_ro_scalar", - "ns=1;s=string_ro_scalar" + "ns=1;s=string_ro_scalar", + "ns=1;s=sbyte_ro_scalar", + "ns=1;s=byte_ro_scalar", + "ns=1;s=datetime_ro_scalar", + "ns=1;s=guid_ro_scalar", + "ns=1;s=bytestring_ro_scalar" ] }, { title: "Read-Write Arrays", + description: "Array nodes. Values are displayed comma-separated." + + " To write, enter comma-separated values (e.g. \"1, 2, 3\")." + + " Commas cannot appear inside individual values.", nodes: [ "ns=1;s=bool_rw_array", "ns=1;s=int16_rw_array", @@ -55,13 +82,50 @@ Page { "ns=1;s=uint64_rw_array", "ns=1;s=float_rw_array", "ns=1;s=double_rw_array", - "ns=1;s=string_rw_array" + "ns=1;s=string_rw_array", + "ns=1;s=sbyte_rw_array", + "ns=1;s=byte_rw_array", + "ns=1;s=datetime_rw_array", + "ns=1;s=guid_rw_array", + "ns=1;s=bytestring_rw_array" + ] + }, + { + title: "Non-Existent Nodes", + description: "These node IDs do not exist on the server." + + " The row should show no value and no metadata in the tooltip.", + nodes: [ + "ns=1;s=does_not_exist", + "ns=99;i=12345", + "ns=1;s=also_missing" ] + }, + { + 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: [] } ] readonly property var currentPage: pages[pageNumber - 1] + // OPC UA ServerState enum (Part 4, Table 120). + readonly property var serverStates: [ + "Running", "Failed", "NoConfiguration", "Suspended", + "Shutdown", "Test", "CommunicationFault", "Unknown" + ] + + function formatValue(node) { + var v = node.value; + if (v === undefined || String(v) === "") + return "—"; + // Map ServerState enum to human-readable string. + if (node.nodeId === "ns=0;i=2259" && !isNaN(v)) + return serverStates[v] || String(v); + return String(v); + } + Component.onCompleted: nodePage.logFunction( currentPage.title + " page loaded (" + currentPage.nodes.length + " nodes)") @@ -78,7 +142,7 @@ Page { font.pointSize: 14 } Label { - text: "(" + nodePage.pageNumber + "/3)" + text: "(" + nodePage.pageNumber + "/" + nodePage.pages.length + ")" color: "gray" } Item { Layout.fillWidth: true } @@ -88,6 +152,16 @@ 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 { Layout.fillWidth: true @@ -102,7 +176,7 @@ Page { Label { text: "Value" font.bold: true - Layout.preferredWidth: 160 + Layout.preferredWidth: 300 } Label { text: "Write" @@ -149,9 +223,11 @@ Page { contentItem: RowLayout { spacing: 12 - // Column 1: Identifier + // Column 1: Display name if available, otherwise short ID. Label { text: { + if (node.info.displayName) + return node.info.displayName; var idx = node.nodeId.indexOf(";s="); return idx >= 0 ? node.nodeId.substring(idx + 3) : node.nodeId; } @@ -161,8 +237,8 @@ Page { // Column 2: Live value (always visible) Label { - text: node.value !== undefined && String(node.value) !== "" ? String(node.value) : "—" - Layout.preferredWidth: 160 + text: nodePage.formatValue(node) + Layout.preferredWidth: 300 elide: Text.ElideRight } @@ -216,7 +292,7 @@ Page { Item { Layout.fillWidth: true } Button { text: "Next →" - visible: nodePage.pageNumber < 3 + visible: nodePage.pageNumber < nodePage.pages.length onClicked: nodePage.stackRef.push("NodePage.qml", { stackRef: nodePage.stackRef, pageNumber: nodePage.pageNumber + 1, diff --git a/demo/main.cpp b/demo/main.cpp index 6d0b9be..689b998 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -2,24 +2,83 @@ * @file main.cpp * @brief Entry point for the Bobink demo application. */ +#include <QDateTime> #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QtQml/QQmlExtensionPlugin> -Q_IMPORT_QML_PLUGIN(BobinkPlugin) +Q_IMPORT_QML_PLUGIN (BobinkPlugin) -int main(int argc, char *argv[]) +/** @brief Custom log handler matching open62541 server log format. */ +static void +logHandler (QtMsgType type, const QMessageLogContext &ctx, const QString &msg) { - // Load the locally-built OpcUa backend plugin (open62541). - QCoreApplication::addLibraryPath(QStringLiteral(QTOPCUA_PLUGIN_PATH)); + // Color only the type/category tag. + const char *color = ""; + const char *label = "debug"; + switch (type) + { + case QtDebugMsg: + label = "debug"; + break; + case QtInfoMsg: + color = "\x1b[32m"; + label = "info"; + break; + case QtWarningMsg: + color = "\x1b[33m"; + label = "warning"; + break; + case QtCriticalMsg: + color = "\x1b[31m"; + label = "critical"; + break; + case QtFatalMsg: + color = "\x1b[1;31m"; + label = "fatal"; + break; + } - QGuiApplication app(argc, argv); + // Shorten "qt.opcua.plugins.open62541.sdk.client" → "client". + QLatin1StringView cat (ctx.category ? ctx.category : "default"); + qsizetype dot = cat.lastIndexOf (QLatin1Char ('.')); + if (dot >= 0) + cat = cat.sliced (dot + 1); - QQmlApplicationEngine engine; - QObject::connect ( - &engine, &QQmlApplicationEngine::objectCreationFailed, &app, - [] () { QCoreApplication::exit (1); }, Qt::QueuedConnection); + // "debug/client", "warning/network", etc. — padded to 20 chars. + QByteArray tag + = QByteArray (label) + '/' + QByteArray (cat.data (), cat.size ()); - engine.loadFromModule("BobinkDemo", "Main"); - return app.exec(); + // Format UTC offset as "(UTC+0100)" to match open62541 server logs. + QDateTime now = QDateTime::currentDateTime (); + qint32 offset = now.offsetFromUtc (); + QChar sign = offset >= 0 ? u'+' : u'-'; + offset = qAbs (offset); + QString ts = now.toString (u"yyyy-MM-dd HH:mm:ss.zzz") + + QStringLiteral (" (UTC%1%2%3)") + .arg (sign) + .arg (offset / 3600, 2, 10, QLatin1Char ('0')) + .arg ((offset % 3600) / 60, 2, 10, QLatin1Char ('0')); + + fprintf (stderr, "[%s] %s%-20.*s\x1b[0m %s\n", qPrintable (ts), color, + static_cast<int> (tag.size ()), tag.data (), qPrintable (msg)); +} + +int +main (int argc, char *argv[]) +{ + // Load the locally-built OpcUa backend plugin (open62541). + QCoreApplication::addLibraryPath (QStringLiteral (QTOPCUA_PLUGIN_PATH)); + + qInstallMessageHandler (logHandler); + + QGuiApplication app (argc, argv); + + QQmlApplicationEngine engine; + QObject::connect ( + &engine, &QQmlApplicationEngine::objectCreationFailed, &app, + [] () { QCoreApplication::exit (1); }, Qt::QueuedConnection); + + engine.loadFromModule ("BobinkDemo", "Main"); + return app.exec (); } |
