/* 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 Async 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 /* The example mocks up a work queue for worker threads with a single slot. The * asyncLock is introduced to show how locking can be used for independent * worker threads. Notably the method result pointer must not be used after the * asyncOperationCancelCallback signals the cancellation of the operation. */ #if UA_MULTITHREADING >= 100 UA_Lock asyncLock; #endif UA_Variant * workQueue[1]; /* The currently active async callback */ static void asyncOperationCancelCallback(UA_Server *server, const void *out) { /* This blocks if a worker thread currently processes the async callback */ UA_LOCK(&asyncLock); if((void*)workQueue[0] == out) workQueue[0] = NULL; /* Disable active async callback */ UA_UNLOCK(&asyncLock); } static void asyncCall(UA_Server *server, void *data) { UA_LOG_INFO(UA_Server_getConfig(server)->logging, UA_LOGCATEGORY_APPLICATION, "call"); /* Process async result if still active */ UA_LOCK(&asyncLock); if((void*)workQueue[0] == data) UA_Server_setAsyncCallMethodResult(server, workQueue[0], UA_STATUSCODE_GOOD); workQueue[0] = NULL; UA_UNLOCK(&asyncLock); } static UA_StatusCode helloWorldMethodCallback1(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_LOG_INFO(UA_Server_getConfig(server)->logging, UA_LOGCATEGORY_APPLICATION, "async"); UA_LOCK(&asyncLock); /* The work queue is full */ if(workQueue[0]) { UA_LOG_WARNING(UA_Server_getConfig(server)->logging, UA_LOGCATEGORY_APPLICATION, "async queue full"); UA_UNLOCK(&asyncLock); return UA_STATUSCODE_BADTOOMANYOPERATIONS; } /* Prepare the output */ UA_String *inputStr = (UA_String*)input->data; UA_String out = UA_STRING_NULL; UA_String_format(&out, "Hello %S", *inputStr); UA_Variant_setScalarCopy(output, &out, &UA_TYPES[UA_TYPES_STRING]); UA_String_clear(&out); /* Return the output with a five second delay */ UA_DateTime callTime = UA_DateTime_nowMonotonic() + (2 * UA_DATETIME_SEC); UA_Server_addTimedCallback(server, asyncCall, output, callTime, NULL); /* Store the pointer to the active async operation. * So it can be cancelled. */ workQueue[0] = output; UA_UNLOCK(&asyncLock); /* Signal async processing to the server. Will be completed later. */ return UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY; } int main(void) { /* Create the server */ UA_LOCK_INIT(&asyncLock); UA_Server *server = UA_Server_new(); /* Set the cancel callback */ UA_ServerConfig *sc = UA_Server_getConfig(server); sc->asyncOperationCancelCallback = asyncOperationCancelCallback; /* Add method */ 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` async"); helloAttr.displayName = UA_LOCALIZEDTEXT("en-US","Hello World async"); helloAttr.executable = true; helloAttr.userExecutable = true; UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541), UA_NS0ID(OBJECTSFOLDER), UA_NS0ID(HASCOMPONENT), UA_QUALIFIEDNAME(1, "hello world"), helloAttr, &helloWorldMethodCallback1, 1, &inputArgument, 1, &outputArgument, NULL, NULL); /* Run the server */ UA_Server_runUntilInterrupt(server); /* Clean up */ UA_Server_delete(server); UA_LOCK_DESTROY(&asyncLock); return 0; }