1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
|
/* 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 <open62541/client_config_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <signal.h>
#include <stdlib.h>
#include "common.h"
#ifndef UA_ARCHITECTURE_WIN32
#include <pthread.h>
#define THREAD_HANDLE pthread_t
#define THREAD_CREATE(handle, callback) do { \
sigset_t set; \
sigemptyset(&set); \
sigaddset(&set, SIGINT); \
pthread_sigmask(SIG_BLOCK, &set, NULL); \
pthread_create(&handle, NULL, callback, &set); \
} while(0)
#define THREAD_JOIN(handle) pthread_join(handle, NULL)
#define THREAD_CALLBACK(name) static void * name(void *_)
#else
#include <windows.h>
#define THREAD_HANDLE HANDLE
#define THREAD_CREATE(handle, callback) { handle = CreateThread( NULL, 0, callback, NULL, 0, NULL); }
#define THREAD_JOIN(handle) WaitForSingleObject(handle, INFINITE)
#define THREAD_CALLBACK(name) static DWORD WINAPI name( LPVOID lpParam )
#endif
static UA_Server* globalServer;
static volatile UA_Boolean running = true;
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_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]);
char* test = (char*)calloc(1,tmp.length+1);
memcpy(test, tmp.data, tmp.length);
UA_String_clear(&tmp);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "'Hello World 1 (async)' was called and will take 3 seconds");
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, " Data 1: %s", test);
free(test);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "'Hello World 1 (async)' has ended");
return UA_STATUSCODE_GOOD;
}
static void
addHelloWorldMethod1(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` 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_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "hello world"),
helloAttr, &helloWorldMethodCallback1,
1, &inputArgument, 1, &outputArgument, NULL, NULL);
/* Get the method node */
UA_NodeId id = UA_NODEID_NUMERIC(1, 62541);
UA_Server_setMethodNodeAsync(server, id, UA_TRUE);
}
static UA_StatusCode
helloWorldMethodCallback2(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]);
char* test = (char*)calloc(1, tmp.length + 1);
memcpy(test, tmp.data, tmp.length);
UA_String_clear(&tmp);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "'Hello World 2 (async)' was called and will take 1 seconds");
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, " Data 2: %s", test);
free(test);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "'Hello World 2 (async)' has ended");
return UA_STATUSCODE_GOOD;
}
static void
addHelloWorldMethod2(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` sync");
helloAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Hello World sync");
helloAttr.executable = true;
helloAttr.userExecutable = true;
UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1, 62542),
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "hello world 2"),
helloAttr, &helloWorldMethodCallback2,
1, &inputArgument, 1, &outputArgument, NULL, NULL);
/* Get the method node */
UA_NodeId id = UA_NODEID_NUMERIC(1, 62542);
UA_Server_setMethodNodeAsync(server, id, UA_TRUE);
}
THREAD_CALLBACK(ThreadWorker) {
while(running) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"Try to dequeue an async operation");
const UA_AsyncOperationRequest* request = NULL;
void *context = NULL;
UA_AsyncOperationType type;
if(UA_Server_getAsyncOperationNonBlocking(globalServer, &type, &request, &context, NULL) == true) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "AsyncMethod_Testing: Got entry: OKAY");
UA_CallMethodResult response = UA_Server_call(globalServer, &request->callMethodRequest);
UA_Server_setAsyncOperationResult(globalServer, (UA_AsyncOperationResponse*)&response,
context);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "AsyncMethod_Testing: Call done: OKAY");
UA_CallMethodResult_clear(&response);
} else {
/* not a good style, but done for simplicity :-) */
sleep_ms(100);
}
}
return 0;
}
/* This callback will be called when a new entry is added to the Callrequest queue */
static void
TestCallback(UA_Server *server) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Dispatched an async method");
}
int main(void) {
globalServer = UA_Server_new();
/* Set the NotifyCallback */
UA_ServerConfig *config = UA_Server_getConfig(globalServer);
config->asyncOperationNotifyCallback = TestCallback;
/* Start the Worker-Thread */
THREAD_HANDLE hThread;
THREAD_CREATE(hThread, ThreadWorker);
/* Add methods */
addHelloWorldMethod1(globalServer);
addHelloWorldMethod2(globalServer);
UA_Server_runUntilInterrupt(globalServer);
/* Shutdown the thread */
running = false;
THREAD_JOIN(hThread);
UA_Server_delete(globalServer);
return 0;
}
|