OPC UA建模并生成代码___open62541生成代码


使用open62541处理XML
1. 配置open62541
首先,需要对open62541进行配置,先打开dos窗口或shell窗口,cd到open62541源码目录下,执行下面的命令,
1 git submodule update --init

会下载一些必须的子模块,用于代码生成。

然后,打开open62541源码目录下的CMakeLists.txt,找到UA_ENABLE_AMALGAMATION设置为ON,接着找到下面这段设置,
# Namespace Zero
set(UA_NAMESPACE_ZERO "REDUCED" CACHE STRING "Completeness of the generated namespace zero (minimal/reduced/full)")
SET_PROPERTY(CACHE UA_NAMESPACE_ZERO PROPERTY STRINGS "MINIMAL" "REDUCED" "FULL")

把UA_NAMESPACE_ZERO的值由REDUCED改为FULL,然后执行以下操作,

    在open62541源码目录下新建build目录,并cd进入
    执行cmake .. && make,会比较耗时
OK后把open62541.h和libopen62541.a拷贝到自定义工程目录,例如如下,
 
 
myNS是本次的工程目录,也可以根据需要自定义任意目录
PS:由于UA_NAMESPACE_ZERO变成FULL,所以libopen62541.a也变大了很多

2. 生成自定义信息模型代码
这一步就使用到了之前生成的example.xml,先把该xml文件拷贝到tools/nodeset_compiler下,然后执行下面的命令,最后一个参数myNS用来指示生成的代码文件名称,
1 python ./nodeset_compiler.py --types-array=UA_TYPES --existing ../../deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml --xml example.xml myNS

打印如下,表示生成成功

在当前路径下输入ls,可以看到生成了myNS.c和myNS.h,这2个文件就是我们需要的,
把myNS.c和myNS.h拷贝到如下src目录,
打开myNS.h,其中有段编译控制,
1 #ifdef UA_ENABLE_AMALGAMATION
2 # include "open62541.h"
3 #else
4 # include <open62541/server.h>
5 #endif

直接改成如下,因为我们使用的是open62541.h

1 # include "open62541.h"

 

3. 编写OPC UA Server代码
在src目录下添加文件server.c,
其内容如下,创建了2个对象,分别叫myNSObject和myNSObject2,
 1 /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
 2  * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
 3 #include <signal.h>
 4 #include <stdio.h>
 5 #include "open62541.h"
 6 /* Files myNS.h and myNS.c are created from myNS.xml */
 7 #include "myNS.h"
 8 UA_Boolean running = true;
 9 static void stopHandler(int sign) {
10     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
11     running = false;
12 }
13 // 这个方法的功能是把输入参数累加,传给输出参数
14 static UA_StatusCode helloWorldMethodCallback(UA_Server *server,
15                             const UA_NodeId *sessionId, void *sessionHandle,
16                             const UA_NodeId *methodId, void *methodContext,
17                             const UA_NodeId *objectId, void *objectContext,
18                             size_t inputSize, const UA_Variant *input,
19                             size_t outputSize, UA_Variant *output) 
20 {
21     UA_Int32 value = 0;
22     for (size_t i = 0; i < inputSize; ++i)
23     {
24      UA_Int32 * ptr = (UA_Int32 *)input[i].data;
25      value += (*ptr);
26     }
27     UA_Variant_setScalarCopy(output, &value, &UA_TYPES[UA_TYPES_INT32]);
28     
29     UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");
30     
31     return UA_STATUSCODE_GOOD;
32 }
33 
34 int main(int argc, char **argv) 
35 {
36     signal(SIGINT, stopHandler);
37     signal(SIGTERM, stopHandler);
38     UA_Server *server = UA_Server_new();
39     UA_ServerConfig_setDefault(UA_Server_getConfig(server));
40     UA_StatusCode retval;
41     /* create nodes from nodeset */
42     if (myNS(server) != UA_STATUSCODE_GOOD) {
43         UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
44             "Check previous output for any error.");
45         retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
46     } else {
47         
48         // 方法节点的NodeId是UA_NODEID_NUMERIC(2, 7001)
49         UA_Server_setMethodNode_callback(server, UA_NODEID_NUMERIC(2, 7001), &helloWorldMethodCallback);
50         
51         
52         UA_NodeId createdNodeId;
53         UA_ObjectAttributes object_attr = UA_ObjectAttributes_default;
54         object_attr.description = UA_LOCALIZEDTEXT("en-US", "myNSObject");
55         object_attr.displayName = UA_LOCALIZEDTEXT("en-US", "myNSObject");
56         // we assume that the myNS nodeset was added in namespace 2.
57         // You should always use UA_Server_addNamespace to check what the
58         // namespace index is for a given namespace URI. UA_Server_addNamespace
59         // will just return the index if it is already added.
60         UA_Server_addObjectNode(server, UA_NODEID_NULL,
61                                 UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
62                                 UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
63                                 UA_QUALIFIEDNAME(1, "myNSObject"),
64                                 UA_NODEID_NUMERIC(2, 1002),
65                                 object_attr, NULL, &createdNodeId);
66         
67         
68         
69         UA_NodeId createdNodeId2;
70         UA_ObjectAttributes object_attr2 = UA_ObjectAttributes_default;
71         object_attr2.description = UA_LOCALIZEDTEXT("en-US", "myNSObject2");
72         object_attr2.displayName = UA_LOCALIZEDTEXT("en-US", "myNSObject2");
73         // we assume that the myNS nodeset was added in namespace 2.
74         // You should always use UA_Server_addNamespace to check what the
75         // namespace index is for a given namespace URI. UA_Server_addNamespace
76         // will just return the index if it is already added.
77         UA_Server_addObjectNode(server, UA_NODEID_NULL,
78                                 UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
79                                 UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
80                                 UA_QUALIFIEDNAME(1, "myNSObject2"),
81                                 UA_NODEID_NUMERIC(2, 1002),
82                                 object_attr2, NULL, &createdNodeId2);
83         
84         
85         retval = UA_Server_run(server, &running);
86     }
87     UA_Server_delete(server);
88     return (int) retval;
89 }


代码解析:

  1 调用自定义信息模型中提供的myNS()函数来添加新建的信息模型,这样在OPC UA Server里就可以看到我们定义的对象类型节点了,即MyObjectType
  2 对象类型中的方法比较特殊,与变量不一样,类似于C++类中的静态函数,不管用对象类型生成多少对象,其包含的方法都只会指向同一个方法,而变量则会与对象一起生成,对象之间互不干扰
  3 使用UA_Server_setMethodNode_callback()给方法节点设置方法,注意不能使用UA_Server_addMethodNode(),因为方法已经在信息模型中添加好了,只不过是一个空壳
  4 多次调用UA_Server_setMethodNode_callback(),只会使用最后一次调用所添加的方法
  5 使用UA_Server_addObjectNode()来创建对象节点,参数中对象类型的NodeId是UA_NODEID_NUMERIC(2, 1002),就是使用UaModeler创建的对象类型
 
可能会问:我怎么知道对象类型的NodeId以及其方法的NodeId呢?有2种方法:
  1 先用代码测试一下,代码中只调用myNS(),不去创建对象,编译后运行server,然后使用UaExpert去连接,连接后去地址空间窗口中去查看,
          
          在ObjectTypes里找到MyObjectType并展开,在右侧的属性窗口中就可以看到NodeId了
    
 
  2  使用路径搜索,因为我们知道对象类型的名称,所以使用路径Root->Types->ObjectTypes->MyObjectType就可以搜到了,路径搜索可参照这篇文章https://blog.csdn.net/whahu1989/article/details/104029500
 
如果是正式应用,推荐第2种方法去获得NodeId

整体工程结构如下,
CMakeLists.txt内容如下,
 1 cmake_minimum_required(VERSION 3.5)
 2 project(myNamespace)
 3 set(EXECUTABLE_OUTPUT_PATH  ${PROJECT_SOURCE_DIR}/bin)
 4 add_definitions(-std=c99)
 5 include_directories(${PROJECT_SOURCE_DIR}/open62541)
 6 include_directories(${PROJECT_SOURCE_DIR}/src)
 7 link_directories(${PROJECT_SOURCE_DIR}/open62541)
 8 
 9 add_executable(server ${PROJECT_SOURCE_DIR}/src/server.c ${PROJECT_SOURCE_DIR}/src/myNS.c)
10 target_link_libraries(server libopen62541.a

cd到build目录下执行cmake .. && make,生成的elf文件在bin目录下,由于libopen62541.a变大了,所以链接时会比较慢。

4. 使用UaExpert进行连接
连接OK后,可以看到创建的2个对象都成功生成了。
展开这2个对象,可以看到它们的方法Func的NodeId都是一样的,而变量的则是不同的,这也印证了前面的说法,
可以执行一下这个方法来测试一下,右击Func,点击Call,
在弹出的界面里输入2个参数值,然后点击Call,
最后会在输出参数里得到300,和期望的一样
验证OK!
 
 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM