之前寫了一篇文章講述如何使用UaModeler,後來經一個讀者朋友提醒,才發現它是商業軟件,不交錢只能創建有限數量的節點…
後來本人去查找了一下相關的免費軟件,找到2款:一個是freeOPCUA下的opcua-modeler,網址是https://github.com/FreeOpcUa/opcua-modeler;另外一款是西門子公司的SiOME,網址是這裏。
這2款軟件都是隻生成XML文件,UaModeler不僅可以生成XML文件,也可以自動生成代碼(不過我們不需要生成的代碼),但是要收費。經對比使用之後,它們的操作基本相同,但是感覺SiOME更勝一籌,界面也更加簡潔美觀。
下面就講述如何使用SiOME
一 下載SiOME
打開SiOME的下載網址,點擊下圖中黃色標記的地方進行下載
如果沒有註冊,就需要註冊一下,後面就可以下載了。
下載好了之後,直接解壓,裏面有個exe文件,直接點擊就可以使用
雙擊打開後如下,
二 使用
這裏創建一個對象類型,這個對象類型裏有2個變量和1個方法,該方法有2個輸入參數和一個輸出參數。下面是操作步驟
1. 添加新的Namespace
首先,要添加新的Namespace,有了自定義的Namespace後,才能在這個Namespace下添加對象類型。
在頂部Namespaces欄點擊右邊黃色標記處,
會彈出Add New Namespace,點擊之,
彈出如下界面,在Namespace URI下填入值就可以了,然後點擊OK
可能會問:填什麼呢?
可以參考已有的Namespace,SiOME打開時,就已經有了一個Namespace,我們點擊如下黃色標記地方,
就能看到已有的Namespace,如下,這是個基礎的Namespace
所以就可以模仿着寫,如:http://myexample.org/UA/
2. 添加自定義對象類型
在左側的Information model窗口中找到BaseObjectType,因爲任意自定義對象類型都要繼承BaseObjectType,如下,
右擊BaseObjectType,選擇Add New ObjectType,
在彈出的窗口中的Name欄裏填入myObjectType,注意NodeClass是ObjectType,Namespace也是我們新添加的那個,然後點擊OK
這樣,我們添加的對象類型就已經出現了,
3. 添加變量
這裏給myObjectType添加2個變量var1和var2,右擊myObjectType,彈出界面如下,選擇Add Child
彈出如下界面,注意默認NodeClass是Object
修改後如下,注意黃色標記的地方
ReferenceType選的是HasComponent,點擊右側倒三角後選擇路徑如下,
DataType選擇Int32,點擊右側倒三角後選擇路徑如下,
同樣的操作,添加變量var2,添加完成後如下,
4. 添加方法
添加一個方法叫func,同樣右擊myObjectType選擇Add Child,然後在彈出界面裏進行設置,如下,
點擊OK,然後展開添加的方法,如下,
func下有2個屬性,InputArguments和OutputArguments,即輸入參數和輸出參數,這也可以在右側Reference窗口裏看到它們的關係,
右擊InputArguments,選擇Add New Argument,
添加後如下,
選中Arg1後在右側Argument Attributes裏修改一下,如下黃色標記,
同樣添加另外一個輸入參數叫Data2和輸出參數returnVal,它們屬性如下
這樣函數func就添加完畢了,如下所示,
5. 設置ModellingRule
ModellingRule是個Reference,決定了使用對象類型創建對象時如何生成其child,點擊myObjectType,在右側References窗口中展開Hierarchical References,下圖紅圈
如下
在ModellingRule欄下勾選框中進行勾選,如下,
這樣var1,var2和func的ModellingRule就設置爲Mandatory了,在創建對象時一定會生成這些child。
6. 生成XML
這個很簡單,點擊工具欄的Save as按鈕
或者直接按ctrl+s,就會彈出保存對話框,保存爲example.xml就可以了。
如果以後需要修改之前創建的Namespace,可以點擊工具欄的open按鈕去打開保存的xml文件就ok了。
三 生成代碼及使用
生成代碼及使用步驟請參照這篇文章的第三節,不再贅述,最終整體代碼如下,
/* 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 <stdio.h>
#include "open62541.h"
/* Files myNS.h and myNS.c are created from example.xml */
#include "myNS.h"
UA_Boolean running = true;
static void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = false;
}
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_Int32 value = 0;
for (size_t i = 0; i < inputSize; ++i)
{
UA_Int32 * ptr = (UA_Int32 *)input[i].data;
value += (*ptr);
}
UA_Variant_setScalarCopy(output, &value, &UA_TYPES[UA_TYPES_INT32]);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");
return UA_STATUSCODE_GOOD;
}
int main(int argc, char **argv)
{
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Server *server = UA_Server_new();
UA_ServerConfig_setDefault(UA_Server_getConfig(server));
UA_StatusCode retval;
/* create nodes from nodeset */
if (myNS(server) != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
"Check previous output for any error.");
retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
} else {
UA_UInt16 myNSIndex = UA_Server_addNamespace(server, "http://myexample.org/UA/");
// 方法節點的NodeId是UA_NODEID_NUMERIC(myNSIndex, 1012)
UA_Server_setMethodNode_callback(server, UA_NODEID_NUMERIC(myNSIndex, 1012), &helloWorldMethodCallback);
UA_NodeId createdNodeId;
UA_ObjectAttributes object_attr = UA_ObjectAttributes_default;
object_attr.description = UA_LOCALIZEDTEXT("en-US", "myNSObject");
object_attr.displayName = UA_LOCALIZEDTEXT("en-US", "myNSObject");
// myObjectType的NodeId是UA_NODEID_NUMERIC(myNSIndex, 1009)
UA_Server_addObjectNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "myNSObject"),
UA_NODEID_NUMERIC(myNSIndex, 1009),
object_attr, NULL, &createdNodeId);
UA_NodeId createdNodeId2;
UA_ObjectAttributes object_attr2 = UA_ObjectAttributes_default;
object_attr2.description = UA_LOCALIZEDTEXT("en-US", "myNSObject2");
object_attr2.displayName = UA_LOCALIZEDTEXT("en-US", "myNSObject2");
// myObjectType的NodeId是UA_NODEID_NUMERIC(myNSIndex, 1009)
UA_Server_addObjectNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "myNSObject2"),
UA_NODEID_NUMERIC(myNSIndex, 1009),
object_attr2, NULL, &createdNodeId2);
retval = UA_Server_run(server, &running);
}
UA_Server_delete(server);
return (int) retval;
}
代碼關鍵點:
- 使用UA_Server_addNamespace()獲取我們新加的Namespace index值,這個很重要,因爲這個值不一定是使用SiOME創建Namespace時生成的那個值,使用SiOME創建時是1,但是運行server後變成了2
- 對象類型節點和方法節點的NodeId在SiOME創建時會自動生成
關於對象類型節點和方法節點的NodeId,推薦使用路徑搜索去查找,這個是最靠譜的方法,可以查看這篇文章
最終編譯OK後,使用UaExpert進行連接,成功後如下
關於爲什麼新建的Namespace的index值變成了2,可以使用UaExpert看出來,點擊Address Space下的No Highlight右側的倒三角進行展開,如下,
可以看到1被urn:open62541.server.application給用了。
四 總結
本文主要講述如何使用SiOME來進行建模,以及後續生成對應代碼並集成到OPC UA Server裏來,操作上和UaModeler以及opcua-modeler差不多。
說實在的,這種教程寫起來非常累,希望看過的同學能給個贊。
如果有寫的不對的地方,希望能留言指正,謝謝閱讀。