Skip to content
Start free evaluation

Qt client worker

  • Run the native client on a worker thread with qt_client_worker from pdcomm_opcua_qt.hpp.
  • Handle the ready, completed, failed and stopped signals on the receiver’s thread.
  • Drain the worker and check its final report.
  • The PDComm OPC UA SDK qt package (pdcomm-opcua-qt-darwin-arm64.tar.gz). It contains everything in the native package 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 $PKI from PKI and trust setup, and two terminals.

Unpack the qt package in the lab folder and point a variable at it:

터미널 창
tar -xzf pdcomm-opcua-qt-darwin-arm64.tar.gz
export 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 Release

You 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.

examples/opcua/qt/opcua_qt_client.cpp
// 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;
}
  1. Configure. Put the endpoint URL, certificate, private key and trusted server certificate into a qt_client_configuration. Unless anonymous is true, the worker activates with user_name and user_secret. The default timeout is 5000 ms.
  2. Create the worker. The constructor copies the configuration, so wipe your copy of the key right away.
  3. Connect the signals. Connect every signal with a receiver object (receiver) and Qt::QueuedConnection. The handlers run on the receiver’s thread (the main thread here), and Qt disconnects them if the receiver goes away.
  4. ready arrives when the session is active. The example sends a ReadRequest here with qt_client_worker::submit; the second argument is a correlation number that comes back with the response.
  5. completed delivers the correlation, the request ID, the response metadata and the response buffer. The response can be a ServiceFault, so check metadata.service_status and the item results in the body.
  6. Ask to stop. qt_client_worker::request_stop with drain finishes every admitted request, then closes the session and channel. abort drops pending work and ends without a protocol close.
  7. stopped is always the last signal. orderly is true when a drain completed CloseSession.
  8. 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.

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:4840

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=0x00000000
Worker stopped orderly=true

Reading 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" 1003
Read ns=2;i=1003 value=none service=0x00000000 status=0x801f0000
Worker stopped orderly=true

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 CertificateUntrusted
Worker stopped orderly=false

The 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.

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=false