How to access just-written-by-client value from Datasource.write function?

26 views
Skip to first unread message

EDSDisp

unread,
May 23, 2024, 11:20:49 AMMay 23
to open62541
How to access just-written-by-client value from Datasource.write function?
Using open62541 version 1.3.9
Server Runtime env: CentOS7, Xenomai RTOS
Client Runtime env: Ubuntu 20 with UaExpert

Apologies in advance as I am a mediocro C++ coder and had never used C before open62541.

CONTEXT --------------------------
I am using open62541 to integrate an OPC Server into an existing Java/C++ software project. The overall software is basically a PLC replacement, utilizing Xenomai RTOS in the C++ backend and a JavaFX GUI. So far I have a running server with a few dozen variable nodes. All of the nodes utilize datasource.read functions to update the variables before read-time. I can successfully read all of them using UAExpert over a small network (Just the runtime PC, a DHCP router and UAExpert running on another laptop.

The problem is that a handful of the variables need to be writable by the client, and I need to use that data in other parts of my software. I created a test UA_Double variable node to try out the datasource.write functionality. I can read my test variable all day long, but AS SOON as I try to write to it (for example, set a value of 10), with UaExpert (Data Access View) the entire C++ program crashes.

SYMPTOMS -------------------------
On the client PC, in UaExpert I get: "Write failed [ret = BadDisconnect]" in the UaExpert log (probably because my whole C++ code crashed).

On the server PC's terminal output: (Note: Error messages below prefixed by: < [2009-05-20 23:36:09.010 {UTC-0700)] debug/session SecureChannel 1 | Session "urn:<mycompname>:UnifiedAutomation:UaExpert" | >)
Terminal Err msg: Processing WriteRequest
Terminal Err msg: Write attribute 13 of Node ns=2;s=GenTest.DoubleVar
Terminal Err msg: /usr/local/bin/startrtc: line 4: 1919 Segmentation fault (core dumped) /home/user/app/system/MDMC_RTC* //<< this is the overall software crashing

The only open62541 example that I could find where a datasource.write function is actually implemented is in /examples/discovery/server_register.c (if there are other examples please let me know).

For my code, I used the "writeInteger" function in that .c file verbatim, EXCEPT that I substituted UA_Double for UAInt32. To troubleshoot, I added a bunch of debug printouts (std::cout; NOT shown below) between each line to find the offender. The code fails at < *testDouble = *(UA_Double *)value->value.data; >

EVALUATION -----------------------
Now I'd be lying if I said that I fully understand that line, but it seems like it's trying to get a pointer to the recently-written data.
In my tiny brain, value->value.data accesses the member 'data' (which is a void pointer) of the thing that 'value' points to. (UA_Double *) casts 'data' to a pointer-to-a-UA_Double. The initial asterisk ...=*(... dereferences the pointer to a UA_Double. Then the UA_Double is being assigned to a dereferenced (apparently NULL) pointer, testDouble?

It seems odd to me that UA_Double *testDouble (which is "UA_Int32 *myInteger" in server_register.c) is assigned twice. What is the point of assigning it the first time since it is never used before being assigned again? Is there some subtle difference between C and C++ pointers that I am missing here? I printed the address of testDouble just AFTER the first assign and just BEFORE the second...they are both 0 (NULL).
First assign: UA_Double *testDouble = (UA_Double*)nodeContext;
//testDouble address = 0
//testDouble address = 0 here too
Second assign: *testDouble = *(UA_Double *)value->value.data; //This line is dereferencing a null pointer!

QUESTIONS ------------------------
So my overall question is: How do I access the just-written-by-client value so that I can send that value to other parts of my C++ program?

And my side questions are:
Why is the pointer address 0 after the first assignment?
Why in the world is testDouble/myInteger assigned twice?
Is this a memory size issue due to the size of Double?
Is their some difference between Int32 and Double that is in play here?

Notes:
- I can confirm that UaExpert recognizes this var as a Double.
- I can read this test node (and all of my other nodes) just fine with their datasources. Only writing is the problem.

// Here's my datasource.write member method --------------------
// Adapted from /examples/discovery/server_register.c, writeInteger function
UA_StatusCode Task_OPCUAServer::writeTestGenDoubleVar(UA_Server *server, const UA_NodeId *sessionId,
                                    void *sessionContext, const UA_NodeId *nodeId, void *nodeContext,
                                    const UA_NumericRange *range, const UA_DataValue *value) {
        UA_Double *testDouble = (UA_Double*)nodeContext;
        if(value->hasValue
            && UA_Variant_isScalar(&value->value)
            && value->value.type == &UA_TYPES[UA_TYPES_DOUBLE]
            && value->value.data)
            {
                *testDouble = *(UA_Double *)value->value.data; // << FAILS at this line per couts (not shown)
                testGenDoubleVar = *testDouble;
            }
        std::cout << "writeTestGenDoubleVar written, output = " << testGenDoubleVar << endl;
        return UA_STATUSCODE_GOOD;
    }

// Here is the method that creates the node ----------------------
void Task_OPCUAServer::addVarNode(UA_Server *server, UA_UInt16 newNamespace, string displayName,
                                        string nodeIdName, string nodeDescription, string varType,
                                    UA_StatusCode(&readFnctn)(UA_Server *server, const UA_NodeId *sessionId,
                                            void *sessionContext, const UA_NodeId *nodeId, void *nodeContext,
                                            UA_Boolean sourceTimeStamp, const UA_NumericRange *range, UA_DataValue *dataValue),
                                    UA_StatusCode(&writeFnctn)(UA_Server *server, const UA_NodeId *sessionId,
                                            void *sessionContext, const UA_NodeId *nodeId, void *nodeContext,
                                            const UA_NumericRange *range, const UA_DataValue *value)){
    const char* displayNameChar = displayName.c_str();
    const char* nodeIdNameChar = nodeIdName.c_str();
    const char* nodeDescriptionChar = nodeDescription.c_str();

  // Add Node for Program variable under parentNode
    std::cout << "L70: add_Bool setup variable node: " << displayName << std::endl;
    UA_Boolean initValue = false; //starting variable value
   
  // Set Variable Attributes
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.description = UA_LOCALIZEDTEXT_ALLOC("en-US",nodeDescriptionChar);
    attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US",displayNameChar);
if (writeFnctn == NULL) {   // If no write function is passed
attr.accessLevel = UA_ACCESSLEVELMASK_READ; // set as READ only
} else {
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; // Allow Read and Write
    }

std::cout << "L91: add_Bool updateVar attempt setScalar" << std::endl;
// Handle different data types with input string varType arg
        if(varType == "Int32"){
            UA_Int32 initValue = 0;                                                     // Initial value for var node
            UA_Variant_setScalar(&attr.value, &initValue, &UA_TYPES[UA_TYPES_INT32]);   // Set Initial value to var node
        }
        if(varType == "UInt32"){
            UA_UInt32 initValue = 0;
            UA_Variant_setScalar(&attr.value, &initValue, &UA_TYPES[UA_TYPES_UINT32]);
        }
        if(varType == "Double"){
            UA_Double initValue = 0;
            UA_Variant_setScalar(&attr.value, &initValue, &UA_TYPES[UA_TYPES_DOUBLE]);
        }
        if(varType == "String"){
            UA_String initValue = UA_String_fromChars("Startup");
            UA_Variant_setScalar(&attr.value, &initValue, &UA_TYPES[UA_TYPES_STRING]);
        }
        if(varType == "Boolean"){
            UA_Boolean initValue = false;
            UA_Variant_setScalar(&attr.value, &initValue, &UA_TYPES[UA_TYPES_BOOLEAN]);
        }
//        if(varType == "Variant"){
//            UA_Variant initValue = 0; //<< this assignment doesn't work
//            UA_Variant_setScalar(&attr.value, &initValue, &UA_TYPES[UA_TYPES_VARIANT]);
//        }
 
  // Set Node ID info  
    UA_NodeId nodeId = UA_NODEID_STRING_ALLOC(newNamespace, nodeIdNameChar);
    UA_QualifiedName nodeName = UA_QUALIFIEDNAME_ALLOC(newNamespace, displayNameChar);
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);          // << replace this with parameter??
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
   
  //Add Variable Data sources to update variable
    UA_DataSource varDataSource;
    varDataSource.read = readFnctn;          // This static function updates the var; will be called before the variable is read
    varDataSource.write = writeFnctn;

  // Add Node to server  
    std::cout << "L98: add_Bool updateVar Attempt add variable node to the address space." << std::endl;
    UA_Server_addDataSourceVariableNode(server,
                                        nodeId,
                                        parentNodeId,
                                        parentReferenceNodeId,
                                        nodeName,
                                        variableTypeNodeId,
                                        attr,
                                        varDataSource,
                                        NULL,
                                        NULL);
    std::cout << "L104: L98: add_Bool updateVar addVariableNode finished, attempt to clear setup variables..." << std::endl;
}


// Here is how the node creator method is called ----------------------------
Task_OPCUAServer::addVarNode(server, TestDataNamespace, "Gen Double Var", "GenTest.DoubleVar", "Generic Test variable of double type",
                                    "Double", readTestGenDoubleVar, writeTestGenDoubleVar);



EDSDisp

unread,
May 29, 2024, 12:16:57 AMMay 29
to open62541
A bit of trial an error and I answered my own question
In the library example server_register.c, the initial myInteger definition is bogus: UA_Int32 *myInteger = (UA_Int32*)nodeContext;
In my double-flavored version, this line needed to be removed: UA_Double *testDouble = (UA_Double*)nodeContext;

Also, the second/valid myInteger/testDouble assignement should not have a * asterisk (indicating a pointer) on the left side of the assignment.

This write function build and runs for me:
UA_StatusCode Task_OPCUAServer::writeTestGenDoubleVar(UA_Server *server, const UA_NodeId *sessionId,
                                    void *sessionContext, const UA_NodeId *nodeId, void *nodeContext,
                                    const UA_NumericRange *range, const UA_DataValue *value) {
        // This fnctn Used to test datasource.write functionality on generically created addVarNode double variable

        if(value->hasValue
            && UA_Variant_isScalar(&value->value)
            && value->value.type == &UA_TYPES[UA_TYPES_DOUBLE]
            && value->value.data)
            {
                UA_Double testDouble = *(UA_Double *)value->value.data;      
                testGenDoubleVar = testDouble;
            }
        return UA_STATUSCODE_GOOD;
    }
Reply all
Reply to author
Forward
0 new messages