/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ /** * Adding Methods to Objects * ------------------------- * * An object in an OPC UA information model may contain methods similar to * objects in a programming language. Methods are represented by a MethodNode. * Note that several objects may reference the same MethodNode. When an object * type is instantiated, a reference to the method is added instead of copying * the MethodNode. Therefore, the identifier of the context object is always * explicitly stated when a method is called. * * The method callback takes as input a custom data pointer attached to the * method node, the identifier of the object from which the method is called, * and two arrays for the input and output arguments. The input and output * arguments are all of type :ref:`variant`. Each variant may in turn contain a * (multi-dimensional) array or scalar of any data type. * * Constraints for the method arguments are defined in terms of data type, value * rank and array dimension (similar to variable definitions). The argument * definitions are stored in child VariableNodes of the MethodNode with the * respective BrowseNames ``(0, "InputArguments")`` and ``(0, * "OutputArguments")``. * * Example: Hello World Method * ^^^^^^^^^^^^^^^^^^^^^^^^^^^ * The method takes a string scalar and returns a string scalar with "Hello " * prepended. The type and length of the input arguments is checked internally * by the SDK, so that we don't have to verify the arguments in the callback. */ #include #include #include #include #include #include static UA_StatusCode helloWorldMethodCallback(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_String *inputStr = (UA_String*)input->data; UA_String tmp = UA_STRING_ALLOC("Hello "); if(inputStr->length > 0) { tmp.data = (UA_Byte *)UA_realloc(tmp.data, tmp.length + inputStr->length); memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length); tmp.length += inputStr->length; } UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]); UA_String_clear(&tmp); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called"); return UA_STATUSCODE_GOOD; } static void addHellWorldMethod(UA_Server *server) { UA_Argument inputArgument; UA_Argument_init(&inputArgument); inputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String"); inputArgument.name = UA_STRING("MyInput"); inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId; inputArgument.valueRank = UA_VALUERANK_SCALAR; UA_Argument outputArgument; UA_Argument_init(&outputArgument); outputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String"); outputArgument.name = UA_STRING("MyOutput"); outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId; outputArgument.valueRank = UA_VALUERANK_SCALAR; UA_MethodAttributes helloAttr = UA_MethodAttributes_default; helloAttr.description = UA_LOCALIZEDTEXT("en-US","Say `Hello World`"); helloAttr.displayName = UA_LOCALIZEDTEXT("en-US","Hello World"); helloAttr.executable = true; helloAttr.userExecutable = true; UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541), UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_HASORDEREDCOMPONENT), UA_QUALIFIEDNAME(1, "hello world"), helloAttr, &helloWorldMethodCallback, 1, &inputArgument, 1, &outputArgument, NULL, NULL); } /** * Increase Array Values Method * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * The method takes an array of 5 integers and a scalar as input. It returns a * copy of the array with every entry increased by the scalar. */ static UA_StatusCode IncInt32ArrayMethodCallback(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, 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_Int32 *inputArray = (UA_Int32*)input[0].data; UA_Int32 delta = *(UA_Int32*)input[1].data; /* Copy the input array */ UA_StatusCode retval = UA_Variant_setArrayCopy(output, inputArray, 5, &UA_TYPES[UA_TYPES_INT32]); if(retval != UA_STATUSCODE_GOOD) return retval; /* Increate the elements */ UA_Int32 *outputArray = (UA_Int32*)output->data; for(size_t i = 0; i < input->arrayLength; i++) outputArray[i] = inputArray[i] + delta; return UA_STATUSCODE_GOOD; } static void addIncInt32ArrayMethod(UA_Server *server) { /* Two input arguments */ UA_Argument inputArguments[2]; UA_Argument_init(&inputArguments[0]); inputArguments[0].description = UA_LOCALIZEDTEXT("en-US", "int32[5] array"); inputArguments[0].name = UA_STRING("int32 array"); inputArguments[0].dataType = UA_TYPES[UA_TYPES_INT32].typeId; inputArguments[0].valueRank = UA_VALUERANK_ONE_DIMENSION; UA_UInt32 pInputDimension = 5; inputArguments[0].arrayDimensionsSize = 1; inputArguments[0].arrayDimensions = &pInputDimension; UA_Argument_init(&inputArguments[1]); inputArguments[1].description = UA_LOCALIZEDTEXT("en-US", "int32 delta"); inputArguments[1].name = UA_STRING("int32 delta"); inputArguments[1].dataType = UA_TYPES[UA_TYPES_INT32].typeId; inputArguments[1].valueRank = UA_VALUERANK_SCALAR; /* One output argument */ UA_Argument outputArgument; UA_Argument_init(&outputArgument); outputArgument.description = UA_LOCALIZEDTEXT("en-US", "int32[5] array"); outputArgument.name = UA_STRING("each entry is incremented by the delta"); outputArgument.dataType = UA_TYPES[UA_TYPES_INT32].typeId; outputArgument.valueRank = UA_VALUERANK_ONE_DIMENSION; UA_UInt32 pOutputDimension = 5; outputArgument.arrayDimensionsSize = 1; outputArgument.arrayDimensions = &pOutputDimension; /* Add the method node */ UA_MethodAttributes incAttr = UA_MethodAttributes_default; incAttr.description = UA_LOCALIZEDTEXT("en-US", "IncInt32ArrayValues"); incAttr.displayName = UA_LOCALIZEDTEXT("en-US", "IncInt32ArrayValues"); incAttr.executable = true; incAttr.userExecutable = true; UA_Server_addMethodNode(server, UA_NODEID_STRING(1, "IncInt32ArrayValues"), UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "IncInt32ArrayValues"), incAttr, &IncInt32ArrayMethodCallback, 2, inputArguments, 1, &outputArgument, NULL, NULL); } /** It follows the main server code, making use of the above definitions. */ static volatile UA_Boolean running = true; static void stopHandler(int sign) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); running = false; } int main(void) { signal(SIGINT, stopHandler); signal(SIGTERM, stopHandler); UA_Server *server = UA_Server_new(); UA_ServerConfig_setDefault(UA_Server_getConfig(server)); addHellWorldMethod(server); addIncInt32ArrayMethod(server); UA_StatusCode retval = UA_Server_run(server, &running); UA_Server_delete(server); return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; }