学习open62541 --- [8] Server添加方法

80 篇文章 475 订阅

本文主要讲述如何在OPC UA Server里添加方法,以及如何调用添加的方法。


一 添加HelloWorld方法

现在我们来添加一个HelloWorld方法,其有一个输入参数和一个输出参数,代码如下,

// server.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. */

#include <signal.h>
#include <stdlib.h>

#include "open62541.h"

UA_Boolean running = true;

static void stopHandler(int sign) 
{
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}

/*
 * 该方法的功能是当输入一个字符串XX,就回复“Hello XX”
*/
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);
}

int main(void) 
{
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

	addHellWorldMethod(server);

    UA_StatusCode retval = UA_Server_run(server, &running);
    
    UA_Server_delete(server);
    
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

代码中只有2个函数,helloWorldMethodCallback()和addHellWorldMethod(),下面简单解析下,
helloWorldMethodCallback()解析:

  1. 函数功能是当输入一个字符串“XX”,就返回“Hello XX”。所以需要一个输入参数和一个输出参数。
  2. 参数列表里的inputSize和input是一对,input可以看做是指向输入参数数组的指针,inputSize则表示输入参数数组中元素的个数,outputSize和output是用于存放输出参数。
  3. OPC UA Server中方法的回调函数,其参数列表必须和本函数一样。

addHellWorldMethod()解析:

  1. 定义了一个输入参数对象和一个输出参数对象,并设置了其名字、数据类型和取值范围等
  2. 定义HelloWorld方法的属性,例如设置其显示名称为“Hello World”以及是否可执行等
  3. 使用UA_Server_addMethodNode()添加HelloWorld方法,传入了输入参数对象和输出参数对象以及helloWorldMethodCallback,并设置该方法的节点id是62541
  4. HelloWorld方法添加到Objects目录下,它与Objects目录的关系是HasOrderedComponent

当调用HelloWorld方法时,会把实参传给这个输入参数对象,然后执行helloWorldMethodCallback,并把结果存放到输出参数对象里返回

编译运行,并使用UaExpert去连接,显示如下,
在这里插入图片描述
表示OPC UA Server添加方法成功,可见方法在OPC UA Server中也是一个Node。


二 调用HelloWorld方法

下面看下如何调用上节添加的HelloWorld方法

1. 使用UaExpert调用

右击该方法,选择“Call”
在这里插入图片描述
弹出如下函数调用界面,界面上的显示很多都是之前代码里设置的名称,如MyInput,MyOutput等
在这里插入图片描述
在MyInput里随意输入一个字符串(MyInput是输入参数名字,是前面代码里设置的),例如“everyone”,然后点击Call按钮,
在这里插入图片描述
可以看到输出参数MyOutput里出现了“Hello everyone”,说明HelloWorld方法调用成功。

2. 编写OPC UA Client代码去调用

HelloWorld方法的NodeId是62541,所以我们可以使用UA_Client_call()来调用这个HelloWorld方法,代码如下,

#include <stdlib.h>
#include "open62541.h"

int main(void) 
{
    UA_Client *client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
    if(retval != UA_STATUSCODE_GOOD) 
    {
        UA_Client_delete(client);
        return (int)retval;
    }
    
    /* Call a remote method */
    UA_Variant input;
    UA_String argString = UA_STRING("everyone");
    UA_Variant_init(&input);
    UA_Variant_setScalarCopy(&input, &argString, &UA_TYPES[UA_TYPES_STRING]);
    size_t outputSize;
    UA_Variant *output;
    retval = UA_Client_call(client, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                            UA_NODEID_NUMERIC(1, 62541), 1, &input, &outputSize, &output);
    if(retval == UA_STATUSCODE_GOOD) 
    {
        printf("Method call was successful, and %lu returned values available.\n",
               (unsigned long)outputSize);
        
        UA_String outString = *(UA_String*)output->data;
        printf("%.*s\n", outString.length, outString.data);
        
        UA_Array_delete(output, outputSize, &UA_TYPES[UA_TYPES_VARIANT]);
    } 
    else 
    {
        printf("Method call was unsuccessful, and %x returned values available.\n", retval);
    }
    UA_Variant_clear(&input);
    
    /* Clean up */
    UA_Client_delete(client); /* Disconnects the client internally */
    
    return EXIT_SUCCESS;
}

代码中的printf()第一个参数是“%.*s”,这是一个小技巧,意思是打印指定长度的字符串,printf的第2个参数就是字符串长度。

编译并运行(注意,先要运行server),得到结果如下,
在这里插入图片描述


三 方法和对象之间的关系

在OPC UA信息模型中,有些对象会包含方法,这个和面向对象编程中对象类型中包含方法是类似的。

如果一个OPC UA对象类型里包含方法,那么当使用这个对象类型去实例化对象时就会给这个对象添加该方法的引用,而不是把这个方法拷贝给对象,使用相同类型创建的所有对象,它们的方法都会指向同一个方法,例子可以参考这篇文章


四 总结

本文主要讲述如何在OPC UA Server中添加方法,这个功能很重要也非常有用。

另外本文添加的方法只有一个输入参数和一个输出参数,如果想要多个输入参数和多个输出参数,可以根据本文例子去修改。

如果有写的不对的地方,希望能留言指正,谢谢阅读。

在使用open62541写数据时,需要进行以下步骤: 1. 首先,需要在CMakeLists.txt中添加相关的依赖库和头文件路径。例如,使用`include_directories`指令包含open62541的头文件路径,使用`find_library`指令找到open62541的库文件。 2. 然后,在代码中创建一个UA_DataSource结构体实例,该结构体包含了读和写两个函数指针。 3. 对于写数据,可以使用`UA_Server_writeValue`函数来写入指定的节点的值。该函数需要传入UA_Server实例、节点ID、写入的值等参数。 4. 在写入数据之前,需要先创建一个UA_VariableAttributes结构体实例来设置节点的属性,包括数据类型、访问级别等。 5. 然后,可以使用`UA_Server_addVariableNode`函数来创建一个变量节点,并将其添加到服务器中。该函数需要传入UA_Server实例、节点ID、父节点ID、节点属性等参数。 6. 接下来,可以使用写入函数指针writeForVariable来写入数据。该函数指针需要自定义实现,用于将数据写入到指定的节点中。 这样,就可以使用open62541来写入数据了。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [open62541简单的封装类](https://download.csdn.net/download/weixin_43829959/12920766)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [学习open62541 --- [29] 数据源的使用细节](https://blog.csdn.net/whahu1989/article/details/106298733)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值