之前写了一篇文章讲述如何使用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差不多。
说实在的,这种教程写起来非常累,希望看过的同学能给个赞。
如果有写的不对的地方,希望能留言指正,谢谢阅读。