学习open62541 --- [50] 自定义数据类型

80 篇文章 477 订阅

在之前的一篇文章中,讲了如何添加变量类型。本文讲述如何自定义数据类型,这里要弄清楚一个概念:变量类型(Variable Type)和数据类型(Data Type)是不一样的,变量类型基于数据类型,我们平时创建变量时,是使用变量类型来创建的。

一般来说,自定义数据类型都是结构体。本文讲述如何把各种不同种类的结构体类型作为数据类型添加到Server中来,并使用它们,主要参考open62541自带example。


一 所使用的OPC UA类型

主要使用了UA_DataType和UA_DataTypeMember这2种类型,原型定义如下,元素意义参考注释,

typedef struct {
#ifdef UA_ENABLE_TYPEDESCRIPTION
    const char *memberName;
#endif
    UA_UInt16 memberTypeIndex;    /* Index of the member in the array of data
                                     types */
    UA_Byte   padding;            /* How much padding is there before this
                                     member element? For arrays this is the
                                     padding before the size_t length member.
                                     (No padding between size_t and the
                                     following ptr.) */
    UA_Boolean namespaceZero : 1; /* The type of the member is defined in
                                     namespace zero. In this implementation,
                                     types from custom namespace may contain
                                     members from the same namespace or
                                     namespace zero only.*/
    UA_Boolean isArray       : 1; /* The member is an array */
    UA_Boolean isOptional    : 1; /* The member is an optional field */
} UA_DataTypeMember;


struct UA_DataType {
#ifdef UA_ENABLE_TYPEDESCRIPTION
    const char *typeName;
#endif
    UA_NodeId typeId;                /* The nodeid of the type */
    UA_UInt16 memSize;               /* Size of the struct in memory */
    UA_UInt16 typeIndex;             /* Index of the type in the datatypetable */
    UA_UInt32 typeKind         : 6;  /* Dispatch index for the handling routines */
    UA_UInt32 pointerFree      : 1;  /* The type (and its members) contains no
                                      * pointers that need to be freed */
    UA_UInt32 overlayable      : 1;  /* The type has the identical memory layout
                                      * in memory and on the binary stream. */
    UA_UInt32 membersSize      : 8;  /* How many members does the type have? */
    UA_UInt32 binaryEncodingId;      /* NodeId of datatype when encoded as binary */
    //UA_UInt16  xmlEncodingId;      /* NodeId of datatype when encoded as XML */
    UA_DataTypeMember *members;
};

UA_DataType用于描述整体,UA_DataTypeMember用来描述结构体成员,UA_DataTypeMember是UA_DataType的成员。


二 简单例子

假设有个结构体Point定义如下,

typedef struct {
    UA_Float x;
    UA_Float y;
    UA_Float z;
} Point;
1. 描述成员

首先,我们使用UA_DataTypeMember去描述成员x,y和z,如下,

static UA_DataTypeMember Point_members[3] = {
    /* x */
    {
        UA_TYPENAME("x") /* .memberName */
        UA_TYPES_FLOAT,  /* .memberTypeIndex, points into UA_TYPES since namespaceZero is true */
        0,               /* .padding */
        true,            /* .namespaceZero, see .memberTypeIndex */
        false,           /* .isArray */
        false            /* .isOptional */
    },

    /* y */
    {
        UA_TYPENAME("y") /* .memberName */
        UA_TYPES_FLOAT,  /* .memberTypeIndex, points into UA_TYPES since namespaceZero is true */
        Point_padding_y, /* .padding */
        true,            /* .namespaceZero, see .memberTypeIndex */
        false,           /* .isArray */
        false            /* .isOptional */
    },
    /* z */
    {
        UA_TYPENAME("z") /* .memberName */
        UA_TYPES_FLOAT,  /* .memberTypeIndex, points into UA_TYPES since namespaceZero is true */
        Point_padding_z, /* .padding */
        true,            /* .namespaceZero, see .memberTypeIndex */
        false,           /* .isArray */
        false            /* .isOptional */
    }
};

整个描述比较简单,比较难理解的是padding,第二个成员y的padding值为Point_padding_y,其定义如下,

#define Point_padding_y offsetof(Point,y) - offsetof(Point,x) - sizeof(UA_Float)

offsetof用于获取结构体中成员的偏移量,由于x是第一个元素,所以其偏移为0。Point_padding_y就是算出x的结尾到y的开始,这段空间的大小,也就是y开始前的padding大小。

结构体成员所占内存空间由于对齐的缘故,不一定和其实际大小相符合,例如下面这种,

typedef struct {
    int32_t x;
    int8_t y;
    int32_t z;
} Temp;

由于对齐,y实际所占的内存空间不是1个字节,而是4个字节,其前面补了3个字节的padding。

所以,对于这种由于对齐可能会产生padding的结构体,需要去计算一下padding,然后赋值给UA_DataTypeMember中的pdding变量。

2. 描述整体

接下来是描述结构体的整体信息,

static const UA_DataType PointType = {
        UA_TYPENAME("Point")                /* .tyspeName */
        {1, UA_NODEIDTYPE_NUMERIC, {4242}}, /* .typeId */
        sizeof(Point),                      /* .memSize */
        0,                                  /* .typeIndex, in the array of custom types */
        UA_DATATYPEKIND_STRUCTURE,          /* .typeKind */
        true,                               /* .pointerFree */
        false,                              /* .overlayable (depends on endianness and
                                            the absence of padding) */
        3,                                  /* .membersSize */
        Point_binary_encoding_id,           /* .binaryEncodingId, the numeric
                                            identifier used on the wire (the
                                            namespaceindex is from .typeId) */
        Point_members                       /* .members */
};

其中几个元素比较关键,需要解释一下,

  • typeId:数据类型的NodeId
  • memSize:结构体的实际大小
  • typeIndex:自定义类型数组里的位置,这个数组就是struct UA_ServerConfig里的成员customDataTypes
  • typeKind:这里使用的是UA_DATATYPEKIND_STRUCTURE,表示是一个结构体
  • pointerFree:这里给的true,表示没有需要释放的指针,如果结构体里有指针,就需要设置为false
  • membersSize:成员数量,这里是3
  • binaryEncodingId:Point_binary_encoding_id实际值是1,不太清楚这是干嘛的…
  • members:成员描述,即上节定义的变量Point_members
3. 添加到地址空间

这一步很关键。首先把数据类型添加到Server的地址空间,


static void add3DPointDataType(UA_Server* server)
{
    UA_DataTypeAttributes attr = UA_DataTypeAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en-US", "3D Point Type");

    UA_Server_addDataTypeNode(
        server, PointType.typeId, UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, "3D.Point"), attr, NULL, NULL);
}

UA_Server_addDataTypeNode()的第二个参数,就是先前定义的数据类型的NodeId。数据类型的display名称是"3D Point Type",browse名称是"3D.Point"。

然后是使用这个数据类型去添加变量类型,变量类型的display名称是"3D Point",browse名称是"3D.Point",

static void
add3DPointVariableType(UA_Server *server) {
    UA_VariableTypeAttributes dattr = UA_VariableTypeAttributes_default;
    dattr.description = UA_LOCALIZEDTEXT("en-US", "3D Point");
    dattr.displayName = UA_LOCALIZEDTEXT("en-US", "3D Point");
    dattr.dataType = PointType.typeId;
    dattr.valueRank = UA_VALUERANK_SCALAR;

    Point p;
    p.x = 0.0;
    p.y = 0.0;
    p.z = 0.0;
    UA_Variant_setScalar(&dattr.value, &p, &PointType);

    UA_Server_addVariableTypeNode(server, pointVariableTypeId,
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, "3D.Point"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        dattr, NULL, NULL);

}

关键点是dattr.dataType传递的是PointType.typeId,即定义变量类型的数据类型。dattr.valueRank选择UA_VALUERANK_SCALAR,因为结构体里的成员都是单变量。
pointVariableTypeId定义如下,

const UA_NodeId pointVariableTypeId = {
    1, UA_NODEIDTYPE_NUMERIC,{ 4243 } };

特别注意:变量类型的NodeId和数据类型的NodeId是不一样的,都是独立存在的。

然后是给dattr.value赋值,可以设置结构体初始值,最后一个参数传递PointType的地址。
PS:后续使用这个变量类型创建变量时,p会重新生成的,类似于class里的成员变量

最后是使用变量类型创建变量,变量的display名称是"3D Point",browse名称是"3D.Point",

static void
add3DPointVariable(UA_Server *server) {
    Point p;
    p.x = 3.0;
    p.y = 4.0;
    p.z = 5.0;
    UA_VariableAttributes vattr = UA_VariableAttributes_default;
    vattr.description = UA_LOCALIZEDTEXT("en-US", "3D Point");
    vattr.displayName = UA_LOCALIZEDTEXT("en-US", "3D Point");
    vattr.dataType = PointType.typeId;
    vattr.valueRank = UA_VALUERANK_SCALAR;
    UA_Variant_setScalar(&vattr.value, &p, &PointType);

    UA_Server_addVariableNode(server, UA_NODEID_STRING(1, "3D.Point"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, "3D.Point"),
        pointVariableTypeId, vattr, NULL, NULL);
}

vattr的初始化用于确定数据类型和初始值。UA_Server_addVariableNode()倒数第4个参数是我们添加的变量类型的NodeId,即pointVariableTypeId

4. 使用

首先创建server,并获取其配置,

UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);

然后把自定义数据类型的信息添加到server的配置里去,本例只有一个数据类型,所以创建只有一个元素的数组,

/* Make your custom datatype known to the stack */
UA_DataType *types = (UA_DataType*)UA_malloc(1 * sizeof(UA_DataType));
UA_DataTypeMember *pointMembers = (UA_DataTypeMember*)UA_malloc(sizeof(UA_DataTypeMember) * 3);
pointMembers[0] = Point_members[0];
pointMembers[1] = Point_members[1];
pointMembers[2] = Point_members[2];
types[0] = PointType;
types[0].members = pointMembers;


/* Attention! Here the custom datatypes are allocated on the stack. So they
* cannot be accessed from parallel (worker) threads. */
UA_DataTypeArray customDataTypes = { config->customDataTypes, 1, types };
config->customDataTypes = &customDataTypes;

UA_DataTypeArray定义如下,

/* Datatype arrays with custom type definitions can be added in a linked list to
 * the client or server configuration. Datatype members can point to types in
 * the same array via the ``memberTypeIndex``. If ``namespaceZero`` is set to
 * true, the member datatype is looked up in the array of builtin datatypes
 * instead. */
typedef struct UA_DataTypeArray {
    const struct UA_DataTypeArray *next;
    const size_t typesSize;
    const UA_DataType *types;
} UA_DataTypeArray;

根据注释,可以知道config->customDataTypes是个单链表,因为目前这个链表只有一个元素,所以next指向了自己。

最后是调用第3步的函数去添加数据类型、变量类型和变量,

add3DPointDataType(server);
add3DPointVariableType(server);
add3DPointVariable(server);
5. 运行

运行后,使用UaExpert去连接,
在这里插入图片描述
可以看到变量已经创建,再看看这个变量的属性,也是和期望的一样,
在这里插入图片描述
添加的数据类型在以下位置,
在这里插入图片描述
添加的变量类型在以下位置,
在这里插入图片描述

6. 整体代码

main.c代码如下,

#include <iostream>
#include <signal.h>
#include <stdlib.h>

#include "open62541.h"
#include "custom_datatype.h"

UA_Boolean running = true;
const UA_NodeId pointVariableTypeId = {
    1, UA_NODEIDTYPE_NUMERIC,{ 4243 } };


static void stopHandler(int sig) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
    running = false;
}


static void add3DPointDataType(UA_Server* server)
{
    UA_DataTypeAttributes attr = UA_DataTypeAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en-US", "3D Point Type");

    UA_Server_addDataTypeNode(
        server, PointType.typeId, UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, "3D.Point"), attr, NULL, NULL);
}

static void
add3DPointVariableType(UA_Server *server) {
    UA_VariableTypeAttributes dattr = UA_VariableTypeAttributes_default;
    dattr.description = UA_LOCALIZEDTEXT("en-US", "3D Point");
    dattr.displayName = UA_LOCALIZEDTEXT("en-US", "3D Point");
    dattr.dataType = PointType.typeId;
    dattr.valueRank = UA_VALUERANK_SCALAR;

    Point p;
    p.x = 0.0;
    p.y = 0.0;
    p.z = 0.0;
    UA_Variant_setScalar(&dattr.value, &p, &PointType);

    UA_Server_addVariableTypeNode(server, pointVariableTypeId,
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
        UA_QUALIFIEDNAME(1, "3D.Point"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
        dattr, NULL, NULL);

}

static void
add3DPointVariable(UA_Server *server) {
    Point p;
    p.x = 3.0;
    p.y = 4.0;
    p.z = 5.0;
    UA_VariableAttributes vattr = UA_VariableAttributes_default;
    vattr.description = UA_LOCALIZEDTEXT("en-US", "3D Point");
    vattr.displayName = UA_LOCALIZEDTEXT("en-US", "3D Point");
    vattr.dataType = PointType.typeId;
    vattr.valueRank = UA_VALUERANK_SCALAR;
    UA_Variant_setScalar(&vattr.value, &p, &PointType);

    UA_Server_addVariableNode(server, UA_NODEID_STRING(1, "3D.Point"),
        UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
        UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, "3D.Point"),
        pointVariableTypeId, vattr, NULL, NULL);
}



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

    UA_Server *server = UA_Server_new();
    UA_ServerConfig *config = UA_Server_getConfig(server);
    UA_ServerConfig_setDefault(config);

    /* Make your custom datatype known to the stack */
    UA_DataType *types = (UA_DataType*)UA_malloc(1 * sizeof(UA_DataType));
    UA_DataTypeMember *pointMembers = (UA_DataTypeMember*)UA_malloc(sizeof(UA_DataTypeMember) * 3);
    pointMembers[0] = Point_members[0];
    pointMembers[1] = Point_members[1];
    pointMembers[2] = Point_members[2];
    types[0] = PointType;
    types[0].members = pointMembers;


    /* Attention! Here the custom datatypes are allocated on the stack. So they
    * cannot be accessed from parallel (worker) threads. */
    UA_DataTypeArray customDataTypes = { config->customDataTypes, 1, types };
    config->customDataTypes = &customDataTypes;

    add3DPointDataType(server);
    add3DPointVariableType(server);
    add3DPointVariable(server);


    UA_Server_run(server, &running);

    UA_Server_delete(server);
    UA_free(pointMembers);
    UA_free(types);
    return EXIT_SUCCESS;
}

custom_datatype.h如下,

/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
 * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */

typedef struct {
    UA_Float x;
    UA_Float y;
    UA_Float z;
} Point;

/* The datatype description for the Point datatype */
#define Point_padding_y offsetof(Point,y) - offsetof(Point,x) - sizeof(UA_Float)
#define Point_padding_z offsetof(Point,z) - offsetof(Point,y) - sizeof(UA_Float)

/* The binary encoding id's for the datatypes */
#define Point_binary_encoding_id        1


static UA_DataTypeMember Point_members[3] = {
    /* x */
    {
        UA_TYPENAME("x") /* .memberName */
        UA_TYPES_FLOAT,  /* .memberTypeIndex, points into UA_TYPES since namespaceZero is true */
        0,               /* .padding */
        true,            /* .namespaceZero, see .memberTypeIndex */
        false,           /* .isArray */
        false            /* .isOptional */
    },

    /* y */
    {
        UA_TYPENAME("y") /* .memberName */
        UA_TYPES_FLOAT,  /* .memberTypeIndex, points into UA_TYPES since namespaceZero is true */
        Point_padding_y, /* .padding */
        true,            /* .namespaceZero, see .memberTypeIndex */
        false,           /* .isArray */
        false            /* .isOptional */
    },
    /* z */
    {
        UA_TYPENAME("z") /* .memberName */
        UA_TYPES_FLOAT,  /* .memberTypeIndex, points into UA_TYPES since namespaceZero is true */
        Point_padding_z, /* .padding */
        true,            /* .namespaceZero, see .memberTypeIndex */
        false,           /* .isArray */
        false            /* .isOptional */
    }
};

static const UA_DataType PointType = {
        UA_TYPENAME("Point")                /* .tyspeName */
        {1, UA_NODEIDTYPE_NUMERIC, {4242}}, /* .typeId */
        sizeof(Point),                      /* .memSize */
        0,                                  /* .typeIndex, in the array of custom types */
        UA_DATATYPEKIND_STRUCTURE,          /* .typeKind */
        true,                               /* .pointerFree */
        false,                              /* .overlayable (depends on endianness and
                                            the absence of padding) */
        3,                                  /* .membersSize */
        Point_binary_encoding_id,           /* .binaryEncodingId, the numeric
                                            identifier used on the wire (the
                                            namespaceindex is from .typeId) */
        Point_members
};

三 其它例子

其它类型的结构体都是类似,可以参考下example,只要弄懂了一个例子,剩下的都很简单了。

### 回答1: openacs-bin-0.5是一种开放源代码的轻量级应用程序,用于构建和管理网络应用程序的基础架构。它是一个基于Web的应用程序服务器,可以协调许多不同的组件和模块来实现一个完整的网站或在线应用程序。 openacs-bin-0.5提供了一套功能强大的工具和库,用于开发和部署具有动态内容的网站。它支持多种数据库和操作系统,并提供了一套完整的工作流程管理方法。它的设计目标是提供一个灵活且可扩展的平台,可以满足不同类型的应用程序需求。 与其他开源应用程序服务器相比,openacs-bin-0.5具有许多优势。首先,它具有强大的安全性功能,可以确保应用程序的数据和用户信息受到保护。其次,它提供了一套完整的开发工具,使开发人员能够轻松创建和维护复杂的应用程序。此外,它还有一个活跃的开发社区,可以提供技术支持和交流。 总之,openacs-bin-0.5是一个强大而灵活的应用程序服务器,可以帮助开发人员快速构建并管理各种类型的网络应用程序。它在安全性、功能性和可扩展性方面具有明显优势,是一个值得尝试的开源软件。 ### 回答2: openacs-bin-0.5是一个开源项目,它是基于OpenACS(Open Architecture Community System)平台的一个二进制软件包。OpenACS是一个用于构建基于Web的应用程序的框架和工具集。openacs-bin-0.5提供了一系列功能和模块,用于快速搭建和管理网站、社区和在线应用。 openacs-bin-0.5包含了许多常用的开发工具和模板,可以帮助开发人员快速开始项目,并提供了一些常用功能的实现,例如用户管理、权限控制、内容管理、论坛、电子商务等。开发人员可以使用openacs-bin-0.5来加速项目的开发和部署过程,同时还可以根据需要自定义和扩展。 openacs-bin-0.5还提供了一些简单易用的管理工具,用于配置和管理网站、用户和内容。管理员可以使用这些工具来管理用户权限、发布和管理内容、监控网站性能等。这些工具可以帮助管理员轻松地管理和维护网站,提高效率和用户体验。 openacs-bin-0.5还具有良好的可扩展性和可定制性。开发人员可以根据自己的需求进行二次开发和定制,添加新功能和模块,以满足特定的业务需求。同时,openacs-bin-0.5也拥有活跃的开发社区和用户群体,提供技术支持和共享资源。 总而言之,openacs-bin-0.5是一个基于OpenACS平台的二进制软件包,它提供了许多有用的开发工具和模块,用于快速构建和管理Web应用程序。它具有简单易用的管理工具和良好的可扩展性,可以帮助开发人员和管理员快速部署和维护网站和在线应用。 ### 回答3: openacs-bin-0.5是一个开源软件包,用于构建基于网络的应用程序和管理网站内容的工具集。 openacs-bin-0.5提供了一组功能强大的工具和API,使开发人员能够轻松地创建和管理复杂的Web应用程序。它采用了模块化的架构,可以根据需求自由选择和集成不同的模块,如论坛、用户管理、电子商务等,以构建定制化的网站。 该软件包还提供了丰富的管理工具,使网站管理员能够方便地管理网站内容和用户。管理员可以轻松创建、编辑和删除内容,管理用户权限和角色,并监控网站的性能和统计信息。 openacs-bin-0.5是使用Tcl编程语言开发的,它采用了面向对象的编程范式,使开发过程更加高效和灵活。它还支持SQL数据库,并提供了强大的数据库访问接口,以便于应用程序与数据库之间的数据交互。 该软件包还使用了一些安全机制来保护网站和用户的隐私。它支持用户身份验证和授权,以确保只有授权的用户才能访问敏感信息。此外,它还提供了强大的数据安全功能,包括数据加密和访问控制,以保护数据库中的数据。 总而言之,openacs-bin-0.5是一个强大的开源软件包,为开发人员和网站管理员提供了一套丰富的工具和功能,帮助他们构建、管理和保护复杂的Web应用程序和网站。
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值