Minimal Open62541 OPC-UA server encapsulated in a C++ class

The following code snippet demonstrates how to create a minimal OPC-UA server using the open62541 library, encapsulated in a C++ class. This example includes basic server functionality such as starting the server, handling requests, and stopping the server.

OPCUAServer.hpp

#pragma once

#include <open62541/server.h>
#include <thread>
#include <atomic>
#include <memory>

class OPCUAServer {
public:
    OPCUAServer();
    ~OPCUAServer();
    
    bool start();
    void stop();
    bool isRunning() const;

private:
    void serverLoop();
    void setupVariableNode();
    
    UA_Server* server_;
    std::unique_ptr<std::thread> serverThread_;
    std::atomic<bool> running_;
    std::atomic<bool> shouldStop_;
};

OPCUAServer.cpp

#include "Configuration/OPCUAServer.hpp"
#include <cstdlib>

OPCUAServer::OPCUAServer() 
    : server_(nullptr), running_(false), shouldStop_(false) {
}

OPCUAServer::~OPCUAServer() {
    stop();
}

bool OPCUAServer::start() {
    if (running_) {
        return false; // Already running
    }
    
    // Create a server listening on port 4840 (default)
    server_ = UA_Server_new();
    if (!server_) {
        return false;
    }
    
    // Setup the variable node
    setupVariableNode();
    
    // Start the server thread
    shouldStop_ = false;
    serverThread_ = std::make_unique<std::thread>(&OPCUAServer::serverLoop, this);
    
    return true;
}

void OPCUAServer::stop() {
    if (running_) {
        shouldStop_ = true;
        
        if (serverThread_ && serverThread_->joinable()) {
            serverThread_->join();
        }
        
        if (server_) {
            UA_Server_delete(server_);
            server_ = nullptr;
        }
        
        running_ = false;
    }
}

bool OPCUAServer::isRunning() const {
    return running_;
}

void OPCUAServer::serverLoop() {
    running_ = true;
    
    // Start the server
    UA_StatusCode retval = UA_Server_run_startup(server_);
    if (retval != UA_STATUSCODE_GOOD) {
        running_ = false;
        return;
    }
    
    // Run the server until stop is requested
    while (!shouldStop_) {
        UA_Server_run_iterate(server_, true);
    }
    
    // Shutdown the server
    UA_Server_run_shutdown(server_);
    running_ = false;
}

void OPCUAServer::setupVariableNode() {
    // Add a variable node to the server
    
    // 1) Define the variable attributes
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en-US", "the answer");
    UA_Int32 myInteger = 42;
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);

    // 2) Define where the node shall be added with which browsename
    UA_NodeId newNodeId = UA_NODEID_STRING(1, "the.answer");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId variableType = UA_NODEID_NULL; // take the default variable type
    UA_QualifiedName browseName = UA_QUALIFIEDNAME(1, "the answer");

    // 3) Add the node
    UA_Server_addVariableNode(server_, newNodeId, parentNodeId,
                              parentReferenceNodeId, browseName,
                              variableType, attr, NULL, NULL);
}

Usage Example

#include <iostream>
#include "OPCUAServer.hpp"

int main() {
    OPCUAServer opcuaServer;

    if (opcuaServer.start()) {
        std::cout << "OPC UA Server started successfully." << std::endl;
    } else {
        std::cerr << "Failed to start OPC UA Server." << std::endl;
        return EXIT_FAILURE;
    }

    // Run the server for a while
    while(true) {
        std::this_thread::sleep_for(std::chrono::seconds(10));
    }

    opcuaServer.stop();
    std::cout << "OPC UA Server stopped." << std::endl;

    return EXIT_SUCCESS;
}

How to compile

g++ -std=gnu++17 -o opcua_server main.cpp OPCUAServer.cpp -lopen62541

Example output

[2025-05-25 23:24:10.345 (UTC+0200)] info/eventloop     Starting the EventLoop
[2025-05-25 23:24:10.345 (UTC+0200)] warn/server        AccessControl: Unconfigured AccessControl. Users have all permissions.
[2025-05-25 23:24:10.345 (UTC+0200)] info/server        AccessControl: Anonymous login is enabled
[2025-05-25 23:24:10.345 (UTC+0200)] warn/server        x509 Certificate Authentication configured, but no encrypting SecurityPolicy. This can leak credentials on the network.
[2025-05-25 23:24:10.346 (UTC+0200)] info/session       TCP 0   | SC 0  | Session "Administrator"       | AddNode (i=15303): No TypeDefinition. Use the default TypeDefinition for the Variable/Object
[2025-05-25 23:24:10.347 (UTC+0200)] info/session       TCP 0   | SC 0  | Session "Administrator"       | AddNode (i=25451): No TypeDefinition. Use the default TypeDefinition for the Variable/Object
[2025-05-25 23:24:10.352 (UTC+0200)] info/session       TCP 0   | SC 0  | Session "Administrator"       | AddNode (ns=1;s=the.answer): No TypeDefinition. Use the default TypeDefinition for the Variable/Object
OPC UA Server started successfully.
[2025-05-25 23:24:10.352 (UTC+0200)] info/network       TCP     | Listening on all interfaces
[2025-05-25 23:24:10.353 (UTC+0200)] info/network       TCP 4   | Creating listen socket for "0.0.0.0" (with local hostname "mycomputer") on port 4840
[2025-05-25 23:24:10.353 (UTC+0200)] info/server        New DiscoveryUrl added: opc.tcp://mycomputer:4840
[2025-05-25 23:24:10.353 (UTC+0200)] info/network       TCP 5   | Creating listen socket for "::" (with local hostname "mycomputer") on port 4840

How it looks like in opcua-client

OPC UA Open62541 minimal example