| /* 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 <open62541/client_config_default.h> |
| #include <open62541/plugin/log_stdout.h> |
| #include <open62541/server.h> |
| #include <open62541/server_config_default.h> |
| |
| #include <signal.h> |
| #include <stdlib.h> |
| |
| 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; |
| } |