学习open62541 --- [50] 自定义数据类型
自定义数据类型
在之前的一篇文章中,讲了如何添加变量类型。本文讲述如何自定义数据类型,这里要弄清楚一个概念:变量类型(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,只要弄懂了一个例子,剩下的都很简单了。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)