High Performance OPC UA Server SDK  1.1.1.177
Lesson 3: Creating Methods

This lesson explains how to implement own methods.

Files used in this lesson:

Preliminary Note

The server from the previous lesson is extended by a node called “Object1” containing a method named “MultiplyMethod”.

Step 1: Implement and Set Custom Call Handler

A custom call handler is necessary to process own methods.

Implement Custom Call Handler

void custom_provider_method_call(struct uaprovider_call_ctx *ctx)
{
int i;
struct ua_callrequest *req = ctx->req;
struct ua_callresponse *res = ctx->res;
ua_node_t method;
uint16_t ns_index;
unsigned int method_index;
fctMethodCall fctmethod;
for (i = 0; i < req->num_methods_to_call; i++) {
ns_index = req->methods_to_call[i].method_id.nsindex;
/* skip node from other namespace */
continue;
}
/* find the node handle */
if (method == UA_NODE_INVALID) {
continue;
}
#ifdef UA_AUTHORIZATION_SUPPORT
/* check if the user is allowed to execute the method */
res->results[i].status_code = ua_authorization_is_executable(method, &ctx->session->user_ctx);
if (res->results[i].status_code != 0) continue;
#endif
/* find the function to call */
if (ua_method_get_index(method, &method_index) == 0 && method_index < countof(g_methods)) {
fctmethod = g_methods[method_index];
if (fctmethod) {
/* call the function */
res->results[i].status_code = fctmethod(&req->methods_to_call[i], &res->results[i]);
} else {
}
} else {
}
}
}

Set up Custom Call Handler

The call handler has to be registered as service handler for the call handler at provider initialization in custom_provider.c.

#ifdef ENABLE_SERVICE_CALL
ctx->call = custom_provider_method_call;
#endif

Step 2: Create Nodes Necessary for multiply Method

We need additional nodes for this lesson, so we’re extending the node creation from the previous lesson.

Create Method Node

Create the actual node of the method as child of “Object1”.

ua_nodeid_set_numeric(&nodeid, g_custom_provider_nsidx, identifier);
&nodeid, /* nodeid for the new node */
UA_NODECLASS_METHOD, /* nodeclass of the new node */
nodeid.nsindex, /* ns index for browsename is same as nodeid */
method_name, /* browsename */
NULL, /* displayname, NULL for same as browsename */
UA_NODE_INVALID, /* the node has no typedefinition */
parent, /* parent node of the new node */
UA_NODE_HASCOMPONENT); /* new node is referenced with hascomponent by parent */
if (method_node == UA_NODE_INVALID) return -1;

Create Input Argument Nodes

/* input nodes */
ua_nodeid_set_numeric(&nodeid, g_custom_provider_nsidx, MULTIPLY_INPUT_ARGUMENTS_ID);
input_argument_node = ua_node_create_with_attributes(
&nodeid, /* nodeid for the new node */
UA_NODECLASS_VARIABLE, /* nodeclass of properties is variable */
0, /* ns index for browsename is namespace zero */
"InputArguments", /* browsename */
NULL, /* displayname, NULL is same as browsename */
UA_NODE_PROPERTYTYPE, /* the node has the typedefinition propertytype */
method_node, /* parent of the new node is the method node */
UA_NODE_HASPROPERTY); /* new node is referenced with hasproperty by parent */
if (input_argument_node == UA_NODE_INVALID) return -1;
ua_nodeid_set_numeric(&datatype_id, 0, UA_ID_ARGUMENT);
ret = ua_variable_set_attributes(input_argument_node, &datatype_id, UA_VALUERANK_ONEDIMENSION, UA_ACCESSLEVEL_CURRENTREAD, false);
if (ret != 0) return ret;

Create Output Argument Nodes

/* output node */
ua_nodeid_set_numeric(&nodeid, g_custom_provider_nsidx, MULTIPLY_OUTPUT_ARGUMENTS_ID);
output_argument_node = ua_node_create_with_attributes(
&nodeid, /* nodeid for the new node */
UA_NODECLASS_VARIABLE, /* nodeclass of properties is variable */
0, /* ns index for browsename is namespace zero */
"OutputArguments", /* browsename */
NULL, /* displayname, NULL is same as browsename */
UA_NODE_PROPERTYTYPE, /* the node has the typedefinition propertytype */
method_node, /* parent of the new node is the method node */
UA_NODE_HASPROPERTY); /* new node is referenced with hasproperty by parent */
if (output_argument_node == UA_NODE_INVALID) return -1;
ua_nodeid_set_numeric(&datatype_id, 0, UA_ID_ARGUMENT);
ret = ua_variable_set_attributes(output_argument_node, &datatype_id, UA_VALUERANK_ONEDIMENSION, UA_ACCESSLEVEL_CURRENTREAD, false);
if (ret != 0) return ret;

Step 3: Initialize Method Arguments

In this step, we set the value of the input and output argument properties. The value includes the name, desciption, data type and value rank of the arguments and is persisted in the memorystore.

Initialize Input Arguments

/* find input arguments node */
input_argument_node = ua_node_find_numeric(g_custom_provider_nsidx, MULTIPLY_INPUT_ARGUMENTS_ID);
if (input_argument_node == UA_NODE_INVALID) return -1;
/* create value of input arguments */
ua_nodeid_set_numeric(&type_id, 0, UA_ID_ARGUMENT);
ua_argument_init(&args[0]);
ua_string_attach_const(&args[0].name, "a");
ua_string_attach_const(&args[0].description.text, "factor a");
ua_string_attach_const(&args[0].description.locale, "en-US");
ua_nodeid_set_numeric(&args[0].data_type, 0, UA_VT_FLOAT);
args[0].value_rank = UA_VALUERANK_SCALAR;
ua_argument_init(&args[1]);
ua_string_attach_const(&args[1].name, "b");
ua_string_attach_const(&args[1].description.text, "factor b");
ua_string_attach_const(&args[1].description.locale, "en-US");
ua_nodeid_set_numeric(&args[1].data_type, 0, UA_VT_FLOAT);
args[1].value_rank = UA_VALUERANK_SCALAR;
ret = ua_variant_set_extensionobject_array(&v, args, 2, &type_id);
if (ret != 0)
return -1;
/* attach the value to the node */
ret = ua_memorystore_attach_new_value(&g_memorystore, &v, input_argument_node);
if (ret != 0) {
return -1;
}

Initialize Output Arguments

/* find output arguments node */
output_argument_node = ua_node_find_numeric(g_custom_provider_nsidx, MULTIPLY_OUTPUT_ARGUMENTS_ID);
if (output_argument_node == UA_NODE_INVALID) return -1;
/* create value of output arguments */
ua_argument_init(&args[0]);
ua_string_attach_const(&args[0].name, "product");
ua_string_attach_const(&args[0].description.text, "product = a * b");
ua_string_attach_const(&args[0].description.locale, "en-US");
ua_nodeid_set_numeric(&args[0].data_type, 0, UA_VT_FLOAT);
args[0].value_rank = UA_VALUERANK_SCALAR;
ret = ua_variant_set_extensionobject_array(&v, args, 1, &type_id);
if (ret != 0)
return -1;
/* attach the value to the node */
ret = ua_memorystore_attach_new_value(&g_memorystore, &v, output_argument_node);
if (ret != 0) {
return -1;
}

Step 4: Add multiply Method

The actual function to be executed when the method is called must still be implemented and added to our small array of method functions during provider initialization.

Implement Function

static ua_statuscode custom_provider_method_multiply(const struct ua_callmethodrequest *req, struct ua_callmethodresult *res)
{
float a, b, product;
/* input validation */
if (req->input_arguments[0].type != UA_VT_FLOAT) return UA_SCBADINVALIDARGUMENT;
if (req->input_arguments[1].type != UA_VT_FLOAT) return UA_SCBADINVALIDARGUMENT;
/* do calculation */
a = req->input_arguments[0].value.f;
b = req->input_arguments[1].value.f;
product = a * b;
/* output result */
if (res->output_arguments == NULL) return UA_SCBADOUTOFMEMORY;
return 0;
}

Add Function to Method Array

int custom_provider_method_init(void)
{
int ret;
ua_node_t node;
/* find the method node */
node = ua_node_find_numeric(g_custom_provider_nsidx, MULTIPLY_METHOD_ID);
if (node == UA_NODE_INVALID) return -1;
/* there is only one function in this provider, it gets the index 0 */
ret = ua_method_set_index(node, 0);
if (ret != 0) return -1;
g_methods[0] = custom_provider_method_multiply;
return 0;
}