EDSDisp
unread,May 23, 2024, 11:20:49 AMMay 23Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
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);