Skip to content

Step 8: Connecting an OPC UA Variable with a Physical Process using open62541

Last updated on April 10, 2022

Connect the variables of the application code with the UANodeSet. This step can be skipped if you want to review the custom UANodeSet on an open62541 server without any meaningful data or functionality attached.

This Post is part of the OPC UA Information Model Tutorial.

This post is based on and the code snippets shown are part of

In summary, open62541 offers three approaches for connecting a process variable to an OPC UA variable:

  • manually writing new values using the Server API
  • callbacks that copy values between process variable and OPC UA representation (bidirectional)
    • When a value changes continuously, such as the system time, updating the value in a tight loop would take up a lot of resources. Value callbacks allow to synchronize a variable value with an external representation. They attach callbacks to the variable that are executed before every read and after every write operation.
  • With value callbacks, the value is still stored in the variable node. So-called data sources go one step further. The server redirects every read and write request to a callback function. Upon reading, the callback provides copy of the current value. Internally, the data source needs to implement its own memory management.

open62541 provides a ready-to-go tutorial example, that demonstrates all three approaches side by side in open62541/examples/tutorial_server_datasource.c .

See Setup open62541 on Debian & Build first server for how to build/compile this .c file.

Quick command (it will take a few seconds/minutes to compile the full NS0 nodeset):

cd ~/myServer
cp ~/open62541/examples/tutorial_server_datasource.c ~/myServer/tutorial_server_datasource.c
gcc -std=c99 -I$HOME/install/include -L$HOME/install/lib tutorial_server_datasource.c -lopen62541 -lmbedtls -lmbedx509 -lmbedcrypto -o myServer

Practical Implementation

Independent of the chosen approach, the mapping between process variables and UA variable is done in C. At best, the C code may be generated with a (python) script.

Otherwise you need to manually write the C code which is assigning a data source to a specific variable with a specific node id. To use defines instead of the numerical node ID, you can include the corresponding generated node ID header generated by the open62541 nodeset compiler (see Step 9 of this tutorial).

Enforcing EURange

AnalogItemType nodes may have EURange optional information that describes the maximum and minimum valid values. The range must be enforced in the read and write callback functions of process variables. There are three rules that may be applied to an EURange violation:

  • ignore the new out-of-range input and stay on the current value
  • change the value to maximum/minimum in-range value
  • force the out-of-range value
Published inopen62541


  1. Klemens Klemens

    This works quite well with variables, but how do I link the logic of a method?

    Unfortunately, the input and output arguments in the source code are initialised with NULL.
    Therefore my first attempt to link a callback resulted in

    UA_NODEID_NUMERIC(nsIdx, 2012),

    an error.
    Now I thought I could get further with the following code

    UA_Variant *varInput;
    UA_Server_readValue(server, UA_NODEID_NUMERIC(nsIdx, 2014), varInput);
    UA_Variant *varOutput;
    UA_Server_readValue(server, UA_NODEID_NUMERIC(nsIdx, 2015), varOutput);
    UA_Argument input = *(UA_Argument*)varInput->data;
    UA_Argument output = *(UA_Argument*)varOutput->data;
    UA_Server_addMethodNode_finish(server, UA_NODEID_NUMERIC(nsIdx, 2012),
    3, &input,
    1, &output);


    Unfortunately I can start the server but shortly after the call it crashes with a segmentation fault.

    Here is the callback of my method

    static UA_StatusCode
    ControlMethodCallback(UA_Server *server,
    const UA_NodeId *sessionId, void *sessionHandle,
    const UA_NodeId *methodId, void *methodContext,
    const UA_NodeId *objectId, void *objectContext,
    size_t inputSize, const UA_Variant *input,
    size_t outputSize, UA_Variant *output) {
    //Handle Input
    UA_Int16 speed_in = *(UA_Int16*)input[0].data;
    UA_Int16 distance_in = *(UA_Int16*)input[1].data;
    UA_Boolean direction_in = *(UA_Boolean*)input[2].data;

    //Handle Direction and set for test the return Value to direction value
    UA_Boolean ret_val = direction_in;
    UA_Variant value;
    UA_Variant_setScalar(&value, &ret_val, &UA_TYPES[UA_TYPES_BOOLEAN]);
    UA_Server_writeValue(server, UA_NODEID_NUMERIC(nsIdx, 2012), value);

    //Return the Value to Methode Output
    UA_Variant_setScalarCopy(output, &ret_val, &UA_TYPES[UA_TYPES_BOOLEAN]);

    //End Methode
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, “Control was called”);

    I would be happy if you could help me 🙂
    Best regards

  2. Klemens Klemens

    Sometimes is it just a litle number
    I just use now the right NodeID and it works 😉

    So I use now
    UA_NODEID_NUMERIC(nsIdx, 2013),
    and this work

Leave a Reply

Your email address will not be published.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.