学习open62541 --- [7] Server添加对象和对象类型

80 篇文章 475 订阅
31 篇文章 7 订阅

本节主要讲述如何在OPC UA Server中添加对象并自定义对象类型。


一 构建对象节点

假设有一个学生对象,一般来说一个学生都会有以下公共信息:姓名,性别,年龄,身高,体重。根据面向对象的定义,每个学生都可以用一个对象来表示,其结构如下,
在这里插入图片描述
简单说下这幅图,Student是一个对象节点,Name是变量节点,它俩之间的关系是hasComponent,这个hasComponent是OPC UA信息模型中的一种,简单来说就是包含的关系,Student包含Name,其它变量节点类似。通过hasComponent就可以组成一个Student对象。

下面让我们把这个对象节点添加到OPC UA Server里

手动添加Student对象

代码如下,

// 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;
}


static void manuallyDefineStudent(UA_Server * server)
{
    UA_NodeId studentId; /* get the nodeid assigned by the server */
    UA_ObjectAttributes stuAttr = UA_ObjectAttributes_default;
    stuAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Student (Manual)");
    UA_Server_addObjectNode(server, UA_NODEID_NULL,
                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                            UA_QUALIFIEDNAME(1, "Student (Manual)"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
                            stuAttr, NULL, &studentId);
    
    // 添加姓名
    UA_VariableAttributes nameAttr = UA_VariableAttributes_default;
    UA_String studentName = UA_STRING("Xiao Ming");
    UA_Variant_setScalar(&nameAttr.value, &studentName, &UA_TYPES[UA_TYPES_STRING]);
    nameAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Name");
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "StudentName"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), nameAttr, NULL, NULL);

    // 添加性别
    UA_VariableAttributes genderAttr = UA_VariableAttributes_default;
    UA_String gender = UA_STRING("Male");
    UA_Variant_setScalar(&genderAttr.value, &gender, &UA_TYPES[UA_TYPES_STRING]);
    genderAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Gender");
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "Gender"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), genderAttr, NULL, NULL);
    
    // 添加年龄
    UA_VariableAttributes ageAttr = UA_VariableAttributes_default;
    UA_Byte age = 16;
    UA_Variant_setScalar(&ageAttr.value, &age, &UA_TYPES[UA_TYPES_BYTE]);
    ageAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Age");
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "Age"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), ageAttr, NULL, NULL);

    // 添加身高
    UA_VariableAttributes heightAttr = UA_VariableAttributes_default;
    UA_UInt16 height = 170;
    UA_Variant_setScalar(&heightAttr.value, &height, &UA_TYPES[UA_TYPES_UINT16]);
    heightAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Height (cm)");
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "Height (cm)"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), heightAttr, NULL, NULL);
    
    // 添加体重                          
    UA_VariableAttributes weightAttr = UA_VariableAttributes_default;
    UA_UInt16 weight = 60;
    UA_Variant_setScalar(&weightAttr.value, &weight, &UA_TYPES[UA_TYPES_UINT16]);
    weightAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Weight (kg)");
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "Weight (kg)"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), weightAttr, NULL, NULL);
}

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

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));
    
    manuallyDefineStudent(server);
    
    UA_StatusCode retval = UA_Server_run(server, &running);
    
    UA_Server_delete(server);
    
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}


代码解释:

  • 先用UA_Server_addObjectNode()创建Student对象节点
  • 然后使用UA_Server_addVariableNode()创建5个变量节点
  • 变量节点的parentNodeId是Student对象节点的Id,它们的关系是hasComponent

编译运行后,使用UaExpert连接server,显示如下,
在这里插入图片描述
在Reference窗口中可以看到对象节点和变量节点之间的关系,即HasComponent
在这里插入图片描述


二 自定义对象类型

上一节我们手动添加了一个学生对象节点,可以看出来需要写很多代码,如果有100个学生对象,那么代码量就很大而且都是重复,假如有一个学生对象的类型,就可以直接使用类型去生成对象,无需编写这么多代码了,这是一种OOP的思想。

对于OPC UA来说类型也是一个节点,所以类型是节点,对象是节点,变量也是节点,节点间使用信息模型的各种关系来互相连接,如前面的hasComponent。

定义类型之前,先规划一下Student类型节点,如下,
在这里插入图片描述
注意:Student现在是ObjectTypeNode,而对于姓名、性别和年龄,这三项的ModellingRule是mandatory的,意思是创建对象时这几个变量必须生成。关于ModellingRule可以参考OPC UA文档Part3

下面是相关代码,

// 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;
}

/* predefined identifier for later use */
UA_NodeId studentTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};

static void defineObjectType(UA_Server *server) 
{
    /* Define the object type for "Student" */
    UA_ObjectTypeAttributes stuAttr = UA_ObjectTypeAttributes_default;
    stuAttr.displayName = UA_LOCALIZEDTEXT("en-US", "StudentType");
    UA_Server_addObjectTypeNode(server, studentTypeId,
                                UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
                                UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
                                UA_QUALIFIEDNAME(1, "StudentType"), stuAttr,
                                NULL, NULL);
    
    // 添加姓名
    UA_VariableAttributes nameAttr = UA_VariableAttributes_default;
    UA_String studentName = UA_STRING("Unknown");
    UA_Variant_setScalar(&nameAttr.value, &studentName, &UA_TYPES[UA_TYPES_STRING]); // 初始化为Unknown,后续再修改
    nameAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Name");
    nameAttr.accessLevel |= UA_ACCESSLEVELMASK_WRITE; // default是只读,添加写权限
    UA_NodeId nameId;
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentTypeId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "Name"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), nameAttr, NULL, &nameId);
    /* Make the student name mandatory */
    UA_Server_addReference(server, nameId,
                           UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
                           UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    // 添加性别
    UA_VariableAttributes genderAttr = UA_VariableAttributes_default;
    UA_String gender = UA_STRING("Unknown");
    UA_Variant_setScalar(&genderAttr.value, &gender, &UA_TYPES[UA_TYPES_STRING]); // 初始化为Unknown,后续再修改
    genderAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Gender");
    genderAttr.accessLevel |= UA_ACCESSLEVELMASK_WRITE; // default是只读,添加写权限
    UA_NodeId genderId;
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentTypeId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "Gender"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), genderAttr, NULL, &genderId);
    /* Make the student gender mandatory */
    UA_Server_addReference(server, genderId,
                           UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
                           UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    // 添加年龄
    UA_VariableAttributes ageAttr = UA_VariableAttributes_default;
    UA_Byte age = 0;
    UA_Variant_setScalar(&ageAttr.value, &age, &UA_TYPES[UA_TYPES_BYTE]); // 初始化为0,后续再修改
    ageAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Age");
    ageAttr.accessLevel |= UA_ACCESSLEVELMASK_WRITE; // default是只读,添加写权限
    ageAttr.valueRank = UA_VALUERANK_SCALAR;
    UA_NodeId ageId;
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentTypeId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "Age"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), ageAttr, NULL, &ageId);
    /* Make the student age mandatory */ 
    UA_Server_addReference(server, ageId,
                           UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
                           UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    // 添加身高
    UA_VariableAttributes heightAttr = UA_VariableAttributes_default;
    UA_UInt16 height = 0;
    UA_Variant_setScalar(&heightAttr.value, &height, &UA_TYPES[UA_TYPES_UINT16]); // 初始化为0,后续再修改
    heightAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Height (cm)");
    heightAttr.accessLevel |= UA_ACCESSLEVELMASK_WRITE; // default是只读,添加写权限
    heightAttr.valueRank = UA_VALUERANK_SCALAR;
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentTypeId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "Height (cm)"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), heightAttr, NULL, NULL);

    // 添加体重
    UA_VariableAttributes weightAttr = UA_VariableAttributes_default;
    UA_UInt16 weight = 0;
    UA_Variant_setScalar(&weightAttr.value, &weight, &UA_TYPES[UA_TYPES_UINT16]); // 初始化为0,后续再修改
    weightAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Weight (kg)");
    weightAttr.accessLevel |= UA_ACCESSLEVELMASK_WRITE; // default是只读,添加写权限
    weightAttr.valueRank = UA_VALUERANK_SCALAR;
    UA_Server_addVariableNode(server, UA_NODEID_NULL, studentTypeId,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                              UA_QUALIFIEDNAME(1, "Weight (kg)"),
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), weightAttr, NULL, NULL);
}



static int getChildIdSimplified(UA_Server *server, 
                        UA_NodeId parentNode, 
                        const int relativePathCnt, 
                        const UA_QualifiedName targetNameArr[], 
                        UA_NodeId *result) 
{
    int ret = 0;

    UA_BrowsePathResult bpr = UA_Server_browseSimplifiedBrowsePath(server, 
                parentNode, relativePathCnt, targetNameArr);

    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1)
    {
        printf("error: %s\n", UA_StatusCode_name(bpr.statusCode));
        ret = -1;
    }
    else
    {
        UA_NodeId_copy(&bpr.targets[0].targetId.nodeId, result);
    }

    
    UA_BrowsePathResult_deleteMembers(&bpr);

    return ret;
}


struct stuInitInfo 
{
	UA_String name;
	UA_String gender;
	UA_Byte age;
};


static void createStudentObjectInstance(UA_Server *server, char *name, struct stuInitInfo *info) 
{
    UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);

    UA_NodeId retId;
    UA_Server_addObjectNode(server, UA_NODEID_NULL,
                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                            UA_QUALIFIEDNAME(1, name),
                            studentTypeId, /* this refers to the object type identifier */
                            oAttr, NULL, &retId);

    UA_NodeId itemId;
	UA_Variant value;
	UA_QualifiedName targetNameArr[1];

	targetNameArr[0] = UA_QUALIFIEDNAME(1, "Name");
    if (getChildIdSimplified(server, retId, 1, targetNameArr, &itemId) == 0)
    {
        UA_Variant_init(&value);
        UA_Variant_setScalar(&value, &info->name, &UA_TYPES[UA_TYPES_STRING]);
    	UA_Server_writeValue(server, itemId, value);
    }


    targetNameArr[0] = UA_QUALIFIEDNAME(1, "Gender");
    if (getChildIdSimplified(server, retId, 1, targetNameArr, &itemId) == 0)
    {
        UA_Variant_init(&value);
        UA_Variant_setScalar(&value, &info->gender, &UA_TYPES[UA_TYPES_STRING]);
    	UA_Server_writeValue(server, itemId, value);
    }


    targetNameArr[0] = UA_QUALIFIEDNAME(1, "Age");
    if (getChildIdSimplified(server, retId, 1, targetNameArr, &itemId) == 0)
    {
        UA_Variant_init(&value);
        UA_Variant_setScalar(&value, &info->age, &UA_TYPES[UA_TYPES_BYTE]);
    	UA_Server_writeValue(server, itemId, value);
    }

}


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

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

    struct stuInitInfo initInfo = {};

    initInfo.name = UA_STRING("Xiao Ming");
    initInfo.gender = UA_STRING("Male");
    initInfo.age = 20;
    createStudentObjectInstance(server, "Xiao Ming", &initInfo);

    initInfo.name = UA_STRING("Xiao Hong");
    initInfo.gender = UA_STRING("Female");
    initInfo.age = 22;
    createStudentObjectInstance(server, "Xiao Hong", &initInfo);

    initInfo.name = UA_STRING("Zhang San");
    initInfo.gender = UA_STRING("Male");
    initInfo.age = 18;
    createStudentObjectInstance(server, "Zhang San", &initInfo);

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

代码解释:

  1. defineObjectType()定义了学生类型节点,其Id是我们预先定义好的,即studentTypeId
  2. 使用UA_Server_addReference()来设置变量的ModellingRule
  3. 在createStudentObjectInstance()里使用了UA_Server_addObjectNode()创建对象,在对象类型的参数里传递了studentTypeId,具体可以去看UA_Server_addObjectNode()的参数定义
  4. 在createStudentObjectInstance()里使用getChildIdSimplified()来获取姓名,性别和年龄的NodeId,然后根据这个去设置其初始值
  5. 变量的accessLevel可以根据需要决定是否添加写权限,否则默认是只读的

编译运行,然后使用UaExpert来连接查看,如下,
在这里插入图片描述
注意,创建对象时只会创建ModellingRule为mandatory的变量节点,UaExpert这里只显示了Age,Gender和Name

我们定义的学生类型节点在哪里呢?根据代码,学生类型节点是在UA_NS0ID_BASEOBJECTTYPE之下的,和其关系是UA_NS0ID_HASSUBTYPE,即学生类型是其子类型。

我们先找到BaseObjectType,如下,
在这里插入图片描述
然后展开就可以看到我们定义的学生类型了,
在这里插入图片描述
在学生类型下再点击Age、Gender等,在右侧References窗口里可以查看它们之间的关系,可以看到ModellingRule是否是mandatory的,如下图
在这里插入图片描述

这样我们就自定义了学生类型,并用这个类型创建了三个学生对象,然后添加到OPC UA Server里。

还有一点需要注意,生成的对象及其包含的变量,它们的NodeId都是由OPC UA Server运行时随机分配的,这样在编写client端代码时就无法确切的获取NodeId,可以参照这篇文章来解决这个问题,本文代码使用的函数getChildIdSimplified()也是来源于该文章。

对于不是Mandatory的成员变量如何实例化,可以参考这篇文章


三 总结

本文主要讲述了如何创建对象节点以及如何自定义对象类型并用来创建对象。这样我们就可以随意的在OPC UA Server端创建各种对象了。

本文主要参考open62541的官网文档,如果有写的不对的地方,希望能留言指正,谢谢阅读。

Open-Falcon 是一款开源的监控系统,可以监控各种资源的状态,包括服务器负载、网络状态、应用程序指标等。它提供了丰富的监控指标和灵活的告警机制,可以帮助运维人员及时发现和解决问题。下面是 Spring Boot 整合 Open-Falcon 的简单示例。 1. 引入依赖 在 pom.xml 文件中添加 Open-Falcon 的客户端依赖: ```xml <dependency> <groupId>com.github.open-falcon</groupId> <artifactId>falcon-sdk-java</artifactId> <version>0.1.0</version> </dependency> ``` 2. 配置 Open-Falcon 客户端 在 application.properties 文件中配置 Open-Falcon 客户端相关属性: ```properties # Open-Falcon 服务地址 falcon.server=http://localhost:6060/api/push # 应用程序名称 falcon.endpoint=my-application ``` 3. 编写监控指标 在代码中编写需要监控的指标,如 CPU 使用率、内存使用率等。可以使用 Open-Falcon 客户端提供的 API 将指标发送到 Open-Falcon 服务端: ```java import com.github.openfalcon.push.PushClient; import com.github.openfalcon.push.PushEntity; public class MyMonitor { private PushClient pushClient; public MyMonitor(String falconServer, String endpoint) { pushClient = new PushClient(falconServer, endpoint); } public void reportCpuUsage(float usage) { PushEntity entity = new PushEntity("cpu.usage", String.valueOf(usage), "", ""); pushClient.push(entity); } public void reportMemoryUsage(float usage) { PushEntity entity = new PushEntity("memory.usage", String.valueOf(usage), "", ""); pushClient.push(entity); } // 其他监控指标 } ``` 4. 启动应用程序 在应用程序启动时,创建监控对象,并在需要监控的地方调用相应的监控方法。 ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); MyMonitor monitor = new MyMonitor("http://localhost:6060/api/push", "my-application"); while (true) { float cpuUsage = getCpuUsage(); float memoryUsage = getMemoryUsage(); monitor.reportCpuUsage(cpuUsage); monitor.reportMemoryUsage(memoryUsage); Thread.sleep(1000); } } private static float getCpuUsage() { // 获取 CPU 使用率 return 0.5f; } private static float getMemoryUsage() { // 获取内存使用率 return 0.6f; } } ``` 以上是一个简单的 Spring Boot 整合 Open-Falcon 的示例,具体的监控指标和告警机制可以根据实际需求进行调整。
评论 60
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值