open62541/examples/tutorial_server_method.c
Jonas Green 7a58e6c0a5 Corrected format of LocaleId in LocalizedText attributes
The LocaleIds was formatted with an underscore as separator between
the language and region. According to the OPC UA specification the
language and the region shall be separated by a hyphen.
Also removed LocaleId in those cases where a specific language is not
addressed, i.e. when presenting a type name.
2017-08-29 10:08:15 +02:00

183 lines
7.8 KiB
C

/* 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 <signal.h>
#include "open62541.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_deleteMembers(&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 = -1; /* 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 = -1; /* 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 = 1;
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 = -1; /* 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 = 1;
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. */
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_ServerConfig *config = UA_ServerConfig_new_default();
UA_Server *server = UA_Server_new(config);
addHellWorldMethod(server);
addIncInt32ArrayMethod(server);
UA_StatusCode retval = UA_Server_run(server, &running);
UA_Server_delete(server);
UA_ServerConfig_delete(config);
return (int)retval;
}