使用open62541處理XML
1. 配置open62541
首先,需要對open62541進行配置,先打開dos窗口或shell窗口,cd到open62541源碼目錄下,執行下面的命令,
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,會比較耗時
執行cmake .. && make,會比較耗時
OK后把open62541.h和libopen62541.a拷貝到自定義工程目錄,例如如下,

myNS是本次的工程目錄,也可以根據需要自定義任意目錄
PS:由於UA_NAMESPACE_ZERO變成FULL,所以libopen62541.a也變大了很多
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,其中有段編譯控制,
在src目錄下添加文件server.c,
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創建的對象類型
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個對象都成功生成了。
連接OK后,可以看到創建的2個對象都成功生成了。

展開這2個對象,可以看到它們的方法Func的NodeId都是一樣的,而變量的則是不同的,這也印證了前面的說法,

可以執行一下這個方法來測試一下,右擊Func,點擊Call,

在彈出的界面里輸入2個參數值,然后點擊Call,

最后會在輸出參數里得到300,和期望的一樣

驗證OK!