Qt client worker
- Run the native client on a worker thread with
qt_client_workerfrompdcomm_opcua_qt.hpp. - Handle the
ready,completed,failedandstoppedsignals on the receiver’s thread. - Drain the worker and check its final report.
What you need
Section titled “What you need”- The PDComm OPC UA SDK
qtpackage (pdcomm-opcua-qt-darwin-arm64.tar.gz). It contains everything in thenativepackage plus the Qt helper header and the Qt example. - Qt 6 Core. Qt isn’t part of the package; you build with your own Qt installation under your Qt license. This page was run with Homebrew Qt 6.11.2.
- The certificates in
$PKIfrom PKI and trust setup, and two terminals.
1. Unpack and build
Section titled “1. Unpack and build”Unpack the qt package in the lab folder and point a variable at it:
tar -xzf pdcomm-opcua-qt-darwin-arm64.tar.gzexport OPCUA_QT_SDK="$PWD/pdcomm-opcua-qt-darwin-arm64"Turn on the Qt example and pass your Qt 6 installation in CMAKE_PREFIX_PATH:
cd "$OPCUA_QT_SDK"cmake -S examples/opcua -B build -DCMAKE_BUILD_TYPE=Release -DPDCOMM_OPCUA_EXAMPLES_QT=ON \ -DCMAKE_PREFIX_PATH="$(qmake -query QT_INSTALL_PREFIX)"cmake --build build --config ReleaseYou get build/opcua_qt_client along with the C and C++ examples. Because pdcomm_opcua_qt.hpp declares a QObject, list it among the target’s sources so AUTOMOC processes it; the example’s CMakeLists.txt does this.
2. Read the code
Section titled “2. Read the code”// PDComm OPC UA QtCore client example.// The worker thread owns the native client; results arrive in the main thread// through queued signals. Usage:// opcua_qt_client ENDPOINT CLIENT_CERT_DER CLIENT_KEY_PEM TRUSTED_SERVER_DER [NODE_ID]// Exit codes: 0 success, 1 a Bad OPC UA status, 2 invalid arguments, 3 failure.#include <pdcomm/opcua/pdcomm_opcua_qt.hpp>#include <QCoreApplication>#include <QFile>#include <QTimer>#include <cstdio>#include <memory>#include "../ua_binary.h"
using namespace pdcomm::opcua;
static bool load(const QString& path, QByteArray& data) { QFile file(path); if (!file.open(QIODevice::ReadOnly) || file.size() <= 0 || file.size() > 64 * 1024) return false; data = file.readAll(); return !data.isEmpty();}
int main(int argc, char** argv) { QCoreApplication application(argc, argv); const auto arguments = application.arguments(); bool numeric = true; const uint32_t node = arguments.size() == 6 ? arguments[5].toUInt(&numeric) : 1001; if ((arguments.size() != 5 && arguments.size() != 6) || !numeric || node == 0) { std::fputs("Usage: opcua_qt_client ENDPOINT CLIENT_CERT_DER CLIENT_KEY_PEM TRUSTED_SERVER_DER [NODE_ID]\n", stderr); return 2; } qt_client_configuration configuration; configuration.endpoint = arguments[1].toUtf8(); configuration.anonymous = true; if (!load(arguments[2], configuration.certificate_der) || !load(arguments[3], configuration.private_key_pem) || !load(arguments[4], configuration.trusted_server_der)) { std::fputs("Error: cannot read the certificate, private key or trusted server certificate\n", stderr); return 3; } int result = 3; QObject receiver; // Stable receiver context for every queued connection. auto worker = std::make_unique<qt_client_worker>(configuration); // The worker copied the configuration; wipe the caller copy of the key. configuration.private_key_pem.fill('\0'); configuration.private_key_pem.clear(); QObject::connect(worker.get(), &qt_client_worker::ready, &receiver, [&](quintptr) { uint8_t request[256]; const auto size = ua_encode_read(request, sizeof(request), 2, node); const auto status = worker->submit(QByteArray(reinterpret_cast<const char*>(request), static_cast<qsizetype>(size)), 1); if (status != PDCOMM_OPCUA_OK) { std::fprintf(stderr, "Error: submit rejected (SDK status %d)\n", static_cast<int>(status)); worker->request_stop(qt_client_stop::abort); } }, Qt::QueuedConnection); QObject::connect(worker.get(), &qt_client_worker::completed, &receiver, [&](qt_client_response_ptr response) { const auto body = response->value->copy(); uint32_t service = 0; ua_value value; if (ua_decode_read(body.data(), body.size(), &service, &value)) { std::fputs("Error: malformed ReadResponse\n", stderr); } else { std::printf("Read ns=2;i=%u value=%s service=0x%08x status=0x%08x\n", static_cast<unsigned>(node), value.has_int32 ? QByteArray::number(value.int32).constData() : "none", static_cast<unsigned>(service), static_cast<unsigned>(value.status)); result = ((service | value.status) & 0x80000000u) ? 1 : 0; } worker->request_stop(qt_client_stop::drain); }, Qt::QueuedConnection); QObject::connect(worker.get(), &qt_client_worker::failed, &receiver, [&](qt_client_failure failure) { std::fprintf(stderr, "Error: %s failed (SDK status %d): %s\n", failure.phase.toUtf8().constData(), static_cast<int>(failure.status), failure.text.toUtf8().constData()); result = 3; worker->request_stop(qt_client_stop::abort); }, Qt::QueuedConnection); QObject::connect(worker.get(), &qt_client_worker::stopped, &receiver, [&](qt_client_terminal terminal) { std::printf("Worker stopped orderly=%s\n", terminal.orderly ? "true" : "false"); std::fflush(stdout); application.quit(); }, Qt::QueuedConnection); worker->start(); application.exec(); worker->wait(); worker.reset(); return result;}- Configure. Put the endpoint URL, certificate, private key and trusted server certificate into a
qt_client_configuration. Unlessanonymousis true, the worker activates withuser_nameanduser_secret. The default timeout is 5000 ms. - Create the worker. The constructor copies the configuration, so wipe your copy of the key right away.
- Connect the signals. Connect every signal with a receiver object (
receiver) andQt::QueuedConnection. The handlers run on the receiver’s thread (the main thread here), and Qt disconnects them if the receiver goes away. readyarrives when the session is active. The example sends a ReadRequest here withqt_client_worker::submit; the second argument is a correlation number that comes back with the response.completeddelivers the correlation, the request ID, the response metadata and the response buffer. The response can be a ServiceFault, so checkmetadata.service_statusand the item results in the body.- Ask to stop.
qt_client_worker::request_stopwithdrainfinishes every admitted request, then closes the session and channel.abortdrops pending work and ends without a protocol close. stoppedis always the last signal.orderlyis true when a drain completed CloseSession.- Clean up. Wait for the worker with
wait(), then destroy it. Never destroy the worker from its own thread.
You can call submit and request_stop from any thread; call the other member functions only from the thread that owns the worker.
3. Start a server
Section titled “3. Start a server”In the first terminal, run the C++ server from the same package:
cd "$OPCUA_QT_SDK"build/opcua_server_cpp 127.0.0.1:4840 opc.tcp://localhost:4840/pdcomm "$PKI/server.der" "$PKI/server.key.pem" "$PKI/client.der"Listening on 127.0.0.1:48404. Run the Qt client
Section titled “4. Run the Qt client”In the second terminal, run the Qt client. An optional last argument is a numeric NodeId in namespace 2 (default 1001).
cd "$OPCUA_QT_SDK"build/opcua_qt_client opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der"Read ns=2;i=1001 value=42 service=0x00000000 status=0x00000000Worker stopped orderly=trueReading a variable for authenticated users only returns a normal response whose item result is BadUserAccessDenied, and the exit code is 1:
build/opcua_qt_client opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/server.der" 1003Read ns=2;i=1003 value=none service=0x00000000 status=0x801f0000Worker stopped orderly=true5. See a failure
Section titled “5. See a failure”Pass the wrong trusted server certificate and the worker fails while connecting:
build/opcua_qt_client opc.tcp://localhost:4840/pdcomm "$PKI/client.der" "$PKI/client.key.pem" "$PKI/client.der"Error: worker failed (SDK status 5): OPC UA security CertificateUntrustedWorker stopped orderly=falseThe phase of the failed signal tells you where the failure happened:
phase |
Meaning | Worker |
|---|---|---|
begin |
begin rejected the request, so it wasn’t sent. |
With PDCOMM_OPCUA_INVALID_INPUT or PDCOMM_OPCUA_LIMIT, only that request fails and the worker keeps running. |
response |
The request was sent, but the server aborted the response or the response was invalid. | Only that request fails. An invalid response also closes the connection, so a worker failure follows. |
abort |
The worker stopped before the request completed. | Sent once per remaining request. |
worker |
Connecting, activation, I/O or closing failed; the correlation is 0. | Ends; the reason in stopped is native-error. |
6. Clean up
Section titled “6. Clean up”Press Ctrl+C in the server’s terminal. If you run the client after the server has stopped, the connection is refused:
Error: worker failed (SDK status 5): OPC UA I/O: connection refused (os error 61)Worker stopped orderly=falseNext steps
Section titled “Next steps”- .NET
- MFC worker: the same design in Windows MFC.