Andreas, I couldn't compile your example (some symbols seem to be missing), so I created a different one; hope this helps; please see below.
The enumeration is used as input value for a method. Tested with Open62541 version 1.4.14, Windows 11, UaExpert client.
- I'm afraid that setting cleanup to false in my UA_DataTypeArray might lead to a memory leak, but setting it to true causes null-pointer dereferencing on server cleanup. What is the correct way of handling that?
- Under Windows, are UA_LOCALIZEDTEXT_ALLOC, UA_NODEID_STRING_ALLOC, etc. the appropriate replacements for
UA_LOCALIZEDTEXT,
UA_NODEID_STRING, ...? I'm asking due to implicit conversion from const char* to char* which does not compile with MSVC, being again afraid of memory leaks.
Regards,
Marco
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Iphlpapi.lib")
#include <open62541/server.h>
#include <open62541/plugin/log_stdout.h>
#include <vector>
/*
* Create a fatal error log message if the UA_StatusCode passed to this function
* is different from UA_STATUSCODE_GOOD.
* In debug mode, the program will stop in that case.
*/
static void checkStatus(UA_StatusCode statusCode)
{
if (statusCode != UA_STATUSCODE_GOOD)
{
UA_LOG_FATAL(
UA_Log_Stdout,
UA_LOGCATEGORY_USERLAND,
"An error occured! Status code: %s",
UA_StatusCode_name(statusCode));
}
assert(statusCode == UA_STATUSCODE_GOOD);
}
/*
* Create a new type and an object of that type.
* Both are added to the server.
*/
static void createTypeAndObject(UA_Server* server)
{
UA_NodeId m_parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE);
const char typeName[] = "MachineStateType";
UA_NodeId typeNodeId = UA_NODEID_STRING_ALLOC(1, typeName);
UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en", typeName);
UA_StatusCode statusCode = UA_Server_addObjectTypeNode(
server,
typeNodeId,
m_parentNodeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME_ALLOC(1, typeName),
attr,
NULL,
NULL
);
checkStatus(statusCode);
const char objectName[] = "MachineState";
UA_ObjectAttributes attrObject = UA_ObjectAttributes_default;
attrObject.displayName = UA_LOCALIZEDTEXT_ALLOC("en", objectName);
statusCode = UA_Server_addObjectNode(
server,
UA_NODEID_STRING_ALLOC(1, objectName),
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME_ALLOC(1, objectName),
typeNodeId,
attrObject,
NULL,
NULL
);
checkStatus(statusCode);
}
/*
* Create a new user-defined enumerator.
*
* At first, a data-type node ID is created, using the enumeration-type ID as parent-node ID.
*
* Then, a new UA_DataType is created and added to the server configuration, making use of the new data-type node ID.
*
* For the enumerator values, multiple instances of UA_EnumValueType are created
* and stored as UA_VariableAttributes.
*
* Finally, a variable node is added, containing the abovementioned UA_EnumValueType instances.
*
*/
static void createEnum(UA_Server* server) {
const char name[] = "MachineStateEnum";
UA_NodeId dataTypeNodeId = UA_NODEID_STRING_ALLOC(1, name);
UA_DataTypeAttributes dataTypeAttr = UA_DataTypeAttributes_default;
dataTypeAttr.displayName = UA_LOCALIZEDTEXT_ALLOC("en", "name");
UA_StatusCode statusCode = UA_Server_addDataTypeNode(
server,
dataTypeNodeId,
UA_TYPES[UA_TYPES_ENUMERATION].typeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME_ALLOC(1, name),
dataTypeAttr,
NULL,
NULL
);
checkStatus(statusCode);
UA_DataType* newEnumDataType = nullptr;
newEnumDataType = (UA_DataType*)UA_malloc(sizeof(UA_DataType));
*newEnumDataType = UA_TYPES[UA_TYPES_ENUMERATION];
newEnumDataType->typeId = dataTypeNodeId;
newEnumDataType->binaryEncodingId = dataTypeNodeId;
newEnumDataType->membersSize = 1;
newEnumDataType->pointerFree = true;
newEnumDataType->typeName = (char*)UA_malloc(strlen(name) + 1);
strcpy_s((char*)newEnumDataType->typeName, strlen(name) + 1, name);
UA_ServerConfig* serverConfig = UA_Server_getConfig(server);
const UA_DataTypeArray* m_prevCustomDataTypes = serverConfig->customDataTypes;
UA_DataTypeArray tempCustomDataTypes = {
m_prevCustomDataTypes,
1,
newEnumDataType,
false
};
UA_DataTypeArray* m_customDataTypeArray = (UA_DataTypeArray*)UA_malloc(sizeof(UA_DataTypeArray));
memcpy(m_customDataTypeArray, &tempCustomDataTypes, sizeof(UA_DataTypeArray));
serverConfig->customDataTypes = m_customDataTypeArray;
std::vector<const char*> enumEntries = { "on", "auto", "off" };
std::vector<UA_EnumValueType> enumValueObjects(enumEntries.size());
for (UA_Int32 i = 0; i < enumEntries.size(); i++)
{
UA_EnumValueType_init(&(enumValueObjects[i]));
enumValueObjects[i].value = i;
enumValueObjects[i].displayName = UA_LOCALIZEDTEXT_ALLOC("en", enumEntries[i]);
enumValueObjects[i].description = UA_LOCALIZEDTEXT_ALLOC("en", enumEntries[i]);
}
UA_VariableAttributes variableAttr = UA_VariableAttributes_default;
UA_Variant_setArray(&variableAttr.value, enumValueObjects.data(), enumValueObjects.size(), &UA_TYPES[UA_TYPES_ENUMVALUETYPE]);
UA_NodeId enumVariableNodeId = UA_NODEID_STRING_ALLOC(1, "MachineStateEnumValues");
statusCode = UA_Server_addVariableNode(
server,
enumVariableNodeId,
dataTypeNodeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
UA_QUALIFIEDNAME_ALLOC(0, "EnumValues"),
UA_NODEID_NUMERIC(0, UA_NS0ID_PROPERTYTYPE),
variableAttr,
NULL,
NULL
);
checkStatus(statusCode);
statusCode = UA_Server_addReference(server,
enumVariableNodeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY),
true);
checkStatus(statusCode);
}
/*
* This function is invoked, when the OPC UA method "setMachineState" is called.
*
* It takes an enumerator value as input, which is converted to a UA_UInt32.
* The function sets a UA_Int32 as output parameter and returns UA_STATUSCODE_GOOD.
*
* (The creation of "setMachineState" happens in a different function below.)
*/
static UA_StatusCode myCallbackFunction(
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)
{
UA_UInt32 enumValue = ((UA_EnumValueType*)(input[0].data))->value;
UA_Int32 result = 42;
if (enumValue == 1) {
result = 4242;
}
else if (enumValue == 2) {
result = 424242;
}
UA_Variant_setScalarCopy(&(output[0]), &result , &UA_TYPES[UA_TYPES_INT32]);
return UA_STATUSCODE_GOOD;
}
/*
* Creation of OPC UA method "setMachineState".
*
* The method takes an enumerator value as input and gives a UA_Int32 as output.
*/
static void createMethod(UA_Server* server)
{
std::vector<UA_Argument> inputArguments(1);
UA_Argument_init(&(inputArguments[0]));
inputArguments[0].description = UA_LOCALIZEDTEXT_ALLOC("en", "The state of the machine.");
inputArguments[0].name = UA_STRING_ALLOC("newStateOfTheMachine");
inputArguments[0].dataType = UA_NODEID_STRING_ALLOC(1, "MachineStateEnum");
inputArguments[0].valueRank = UA_VALUERANK_SCALAR;
std::vector<UA_Argument> outputArguments(1);
UA_Argument_init(&(outputArguments[0]));
outputArguments[0].description = UA_LOCALIZEDTEXT_ALLOC("en", "The answer to life, the universe and everything.");
outputArguments[0].name = UA_STRING_ALLOC("answerToEverything");
outputArguments[0].dataType = UA_TYPES[UA_TYPES_INT32].typeId;
outputArguments[0].valueRank = UA_VALUERANK_SCALAR;
UA_MethodAttributes attr = UA_MethodAttributes_default;
attr.description = UA_LOCALIZEDTEXT_ALLOC("en", "Set the state of the machine.");
attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en", "setMachineState");
attr.executable = true;
attr.userExecutable = true;
UA_NodeId methodNodeId = UA_NODEID_STRING_ALLOC(1, "setMachineState");
UA_StatusCode statusCode = UA_Server_addMethodNode(
server,
methodNodeId,
UA_NODEID_STRING_ALLOC(1, "MachineState"),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME_ALLOC(1, "setMachineState"),
attr,
myCallbackFunction,
inputArguments.size(),
inputArguments.data(),
outputArguments.size(),
outputArguments.data(),
NULL,
NULL
);
checkStatus(statusCode);
statusCode = UA_Server_addReference(
server,
methodNodeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY),
true
);
checkStatus(statusCode);
}
/*
* The main function, which creates, runs and deletes the server.
*
* To the server, an object with a single method is added,
* making use of all the above-defined functions.
*
* The method takes a value of a user-defined enumerator as input
* and gives a UA_Int32 back as output.
*/
int main(void)
{
UA_Server* server = UA_Server_new();
createTypeAndObject(server);
createEnum(server);
createMethod(server);
UA_Server_runUntilInterrupt(server);
UA_Server_delete(server);
return 0;
}