概述
平常我们客户端都是读取服务器中的标准数据类型,比如UA_Float、UA_String、UA_Int32…本章主要讲述客户端如何去读取服务器中的自定义数据类型。从open62541中提供的例程来看,在服务器和客户端中都添加了自定义的数据类型,这样操作很方便可以根据对应的数据类型直接获取其值。但实际上在大部分的使用场景中服务器和客户端都不是同一个人写的,所以如果服务端自定了某种数据类型,我们这边是没办法通过这种方式去获取的。在使用UaExpert时我们可以发现,无论我们定义了啥类型,在它那里都能够正确的解析出来。接下来将讲述如何去像UaExpert一样在未知类型时获取对应的值。
服务端
我们使用其例程server_types_custom.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 "open62541.h"
#include <signal.h>
#include <stdlib.h>
#include "custom_datatype.h"
UA_Boolean running = true;
const UA_NodeId pointVariableTypeId = {
1, UA_NODEIDTYPE_NUMERIC, {4243}};
const UA_NodeId measurementVariableTypeId = {
1, UA_NODEIDTYPE_NUMERIC, {4444}};
const UA_NodeId optstructVariableTypeId = {
1, UA_NODEIDTYPE_NUMERIC, {4645}};
const UA_NodeId unionVariableTypeId = {
1, UA_NODEIDTYPE_NUMERIC, {4846}};
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 = 10.0;
p.y = 20.0;
p.z = 30.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 = 97;
p.y = 97;
p.z = 97;
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);
}
static void addMeasurementSeriesDataType(UA_Server* server)
{
UA_DataTypeAttributes attr = UA_DataTypeAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US", "Measurement Series (Array) Type");
UA_Server_addDataTypeNode(
server, MeasurementType.typeId, UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, "Measurement Series"), attr, NULL, NULL);
}
static void
addMeasurementSeriesVariableType(UA_Server *server) {
UA_VariableTypeAttributes dattr = UA_VariableTypeAttributes_default;
dattr.description = UA_LOCALIZEDTEXT("en-US", "Measurement Series");
dattr.displayName = UA_LOCALIZEDTEXT("en-US", "Measurement Series");
dattr.dataType = MeasurementType.typeId;
dattr.valueRank = UA_VALUERANK_ANY;
Measurements m;
memset(&m, 0, sizeof(Measurements));
m.description = UA_STRING("");
m.measurementSize = 0;
m.measurement = (UA_Float *) UA_Array_new(m.measurementSize, &MeasurementType);
UA_Variant_setScalar(&dattr.value, &m, &MeasurementType);
UA_Server_addVariableTypeNode(server, measurementVariableTypeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME(1, "Measurement Series"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
dattr, NULL, NULL);
}
static void
addMeasurementSeriesVariable(UA_Server *server) {
Measurements m;
m.description = UA_STRING_ALLOC("TestDesc");
m.measurementSize = 3;
m.measurement = (UA_Float *) UA_Array_new(m.measurementSize, &MeasurementType);
m.measurement[0] = (UA_Float) 19.1;
m.measurement[1] = (UA_Float) 20.2;
m.measurement[2] = (UA_Float) 19.7;
UA_VariableAttributes vattr = UA_VariableAttributes_default;
vattr.description = UA_LOCALIZEDTEXT("en-US", "Temp Measurement (Array Example)");
vattr.displayName = UA_LOCALIZEDTEXT("en-US", "Temp Measurement (Array Example)");
vattr.dataType = MeasurementType.typeId;
vattr.valueRank = UA_VALUERANK_ANY;
UA_Variant_setScalar(&vattr.value, &m, &MeasurementType);
UA_Server_addVariableNode(server, UA_NODEID_STRING(1, "Temp.Measurement"),
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "Temp Measurement (Array Example)"),
measurementVariableTypeId, vattr, NULL, NULL);
UA_clear(&m, &MeasurementType);
}
static void addOptStructDataType(UA_Server* server)
{
UA_DataTypeAttributes attr = UA_DataTypeAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US", "OptStruct Example Type");
UA_Server_addDataTypeNode(
server, OptType.typeId, UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, "OptStruct Example"), attr, NULL, NULL);
}
static void
addOptStructVariableType(UA_Server *server) {
UA_VariableTypeAttributes dattr = UA_VariableTypeAttributes_default;
dattr.description = UA_LOCALIZEDTEXT("en-US", "OptStruct Example");
dattr.displayName = UA_LOCALIZEDTEXT("en-US", "OptStruct Example");
dattr.dataType = OptType.typeId;
dattr.valueRank = UA_VALUERANK_SCALAR;
Opt o;
memset(&o, 0, sizeof(Opt));
UA_Variant_setScalar(&dattr.value, &o, &OptType);
UA_Server_addVariableTypeNode(server, optstructVariableTypeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME(1, "OptStruct Example"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
dattr, NULL, NULL);
}
static void
addOptStructVariable(UA_Server *server) {
Opt o;
memset(&o, 0, sizeof(Opt));
o.a = 3;
o.b = NULL;
o.c = UA_Float_new();
*o.c = (UA_Float) 10.10;
UA_VariableAttributes vattr = UA_VariableAttributes_default;
vattr.description = UA_LOCALIZEDTEXT("en-US", "OptStruct Example");
vattr.displayName = UA_LOCALIZEDTEXT("en-US", "OptStruct Example");
vattr.dataType = OptType.typeId;
vattr.valueRank = UA_VALUERANK_SCALAR;
UA_Variant_setScalar(&vattr.value, &o, &OptType);
UA_Server_addVariableNode(server, UA_NODEID_STRING(1, "Optstruct.Value"),
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "OptStruct Example"),
optstructVariableTypeId, vattr, NULL, NULL);
UA_clear(&o, &OptType);
}
static void addUnionExampleDataType(UA_Server* server)
{
UA_DataTypeAttributes attr = UA_DataTypeAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT("en-US", "Union Example Type");
UA_Server_addDataTypeNode(
server, UniType.typeId, UA_NODEID_NUMERIC(0, UA_NS0ID_UNION),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, "Union Example"), attr, NULL, NULL);
}
static void
addUnionExampleVariableType(UA_Server *server) {
UA_VariableTypeAttributes dattr = UA_VariableTypeAttributes_default;
dattr.description = UA_LOCALIZEDTEXT("en-US", "Union Example");
dattr.displayName = UA_LOCALIZEDTEXT("en-US", "Union Example");
dattr.dataType = UniType.typeId;
dattr.valueRank = UA_VALUERANK_SCALAR;
Uni u;
memset(&u, 0, sizeof(Uni));
UA_Variant_setScalar(&dattr.value, &u, &UniType);
UA_Server_addVariableTypeNode(server, unionVariableTypeId,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
UA_QUALIFIEDNAME(1, "Union Example"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
dattr, NULL, NULL);
}
static void
addUnionExampleVariable(UA_Server *server) {
Uni u;
u.switchField = UA_UNISWITCH_OPTIONB;
u.fields.optionB = UA_STRING("test string");
UA_VariableAttributes vattr = UA_VariableAttributes_default;
vattr.description = UA_LOCALIZEDTEXT("en-US", "Union Example");
vattr.displayName = UA_LOCALIZEDTEXT("en-US", "Union Example");
vattr.dataType = UniType.typeId;
vattr.valueRank = UA_VALUERANK_SCALAR;
UA_Variant_setScalar(&vattr.value, &u, &UniType);
UA_Server_addVariableNode(server, UA_NODEID_STRING(1, "Union.Value"),
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "Union Example"),
unionVariableTypeId, 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(4 * 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;
types[1] = MeasurementType;
UA_DataTypeMember *measurementMembers = (UA_DataTypeMember*)UA_malloc(sizeof(UA_DataTypeMember) * 2);
measurementMembers[0] = Measurements_members[0];
measurementMembers[1] = Measurements_members[1];
types[1].members = measurementMembers;
types[2] = OptType;
UA_DataTypeMember *optMembers = (UA_DataTypeMember*)UA_malloc(sizeof(UA_DataTypeMember) * 3);
optMembers[0] = Opt_members[0];
optMembers[1] = Opt_members[1];
optMembers[2] = Opt_members[2];
types[2].members = optMembers;
types[3] = UniType;
UA_DataTypeMember *uniMembers = (UA_DataTypeMember*)UA_malloc(sizeof(UA_DataTypeMember) * 2);
uniMembers[0] = Uni_members[0];
uniMembers[1] = Uni_members[1];
types[3].members = uniMembers;
/* Attention! Here the custom datatypes are allocated on the stack. So they
* cannot be accessed from parallel (worker) threads. */
UA_DataTypeArray customDataTypes = {config->customDataTypes, 4, types};
config->customDataTypes = &customDataTypes;
add3DPointDataType(server);
add3DPointVariableType(server);
add3DPointVariable(server);
addMeasurementSeriesDataType(server);
addMeasurementSeriesVariableType(server);
addMeasurementSeriesVariable(server);
addOptStructDataType(server);
addOptStructVariableType(server);
addOptStructVariable(server);
addUnionExampleDataType(server);
addUnionExampleVariableType(server);
addUnionExampleVariable(server);
UA_Server_run(server, &running);
UA_Server_delete(server);
UA_free(pointMembers);
UA_free(measurementMembers);
UA_free(optMembers);
UA_free(uniMembers);
UA_free(types);
return EXIT_SUCCESS;
}
服务器这部分代码就不进行说明了,可见自定义数据类型一文。我们使用Uaexpert去连接改服务器可以看到对应的自定义变量已经成功添加。
客户端
这里我们已3D Point这个自定义变量为例讲述如何去获取其值。我们通过UaExpert查看其NodeId为ns=1;s=3D.Point,也可以通过浏览服务器中的节点进行查找。由于这里只是讲述读取自定义数据类型值,这里就不多赘述,整体代码如下,
/* 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 "open62541.h"
int main(int argc, char *argv[])
{
UA_Client *client = UA_Client_new();
UA_ClientConfig* config = UA_Client_getConfig(client);
UA_ClientConfig_setDefault(config);
UA_StatusCode retval;
/* Connect to a server */
retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
if(retval != UA_STATUSCODE_GOOD) {
printf("UA_Client_connect failed, retval = %d\n", retval);
UA_Client_delete(client);
system("pause");
return EXIT_FAILURE;
}
UA_NodeId varNode = UA_NODEID_STRING(1, "3D.Point");
UA_NodeId dataTypeNodeId = UA_NODEID_NULL;
UA_Variant variant;
UA_Variant_init(&variant);
// 获取节点的数据类型节点ID
retval = UA_Client_readDataTypeAttribute(client, varNode, &dataTypeNodeId);
if (retval != UA_STATUSCODE_GOOD) {
printf("UA_Client_readDataTypeAttribute failed, retval = %d\n", retval);
UA_Client_disconnect(client);
UA_Client_delete(client);
system("pause");
return EXIT_FAILURE;
}
// 获取该数据类型的浏览名称
UA_LocalizedText dataTypeName;
UA_LocalizedText_init(&dataTypeName);
retval = UA_Client_readDisplayNameAttribute(client, dataTypeNodeId, &dataTypeName);
if (retval != UA_STATUSCODE_GOOD) {
printf("UA_Client_readDisplayNameAttribute failed, retval = %d\n", retval);
UA_Client_disconnect(client);
UA_Client_delete(client);
system("pause");
return EXIT_FAILURE;
}
printf("borowse name = %.*s\n", dataTypeName.text.length, dataTypeName.text.data);
// 获取该数据类型的描述信息
UA_StructureDefinition structureDefinition;
retval = __UA_Client_readAttribute(client, &dataTypeNodeId, UA_ATTRIBUTEID_DATATYPEDEFINITION, &structureDefinition, &UA_TYPES[UA_TYPES_STRUCTUREDEFINITION]);
if (retval != UA_STATUSCODE_GOOD) {
printf("__UA_Client_readAttribute failed, retval = %d\n", retval);
UA_Client_disconnect(client);
UA_Client_delete(client);
system("pause");
return EXIT_FAILURE;
}
// 根据读取到server端的数据类型构造对应的数据类型
UA_DataType* dataType = (UA_DataType * )malloc(sizeof(UA_DataType));
UA_DataTypeMember* dataTypeMembers = (UA_DataTypeMember*)malloc(sizeof(UA_DataTypeMember) * structureDefinition.fieldsSize);
for (int ii = 0; ii < structureDefinition.fieldsSize; ii++)
{
UA_DataTypeMember* member = &dataTypeMembers[ii];
UA_StructureField* field = &structureDefinition.fields[ii];
member->memberName = (char*)malloc(sizeof(char) * (field->name.length + 1));
memcpy_s((void*)member->memberName, field->name.length, field->name.data, field->name.length);
member->memberType = &UA_TYPES[UA_TYPES_FLOAT]; // field字段中有类型的NodeId,这里就不多判断了
member->padding = 0; // 成员之间的padding可以通过计算获得
member->isOptional = false;// field->isOptional;
member->isArray = false;// 可通过读取ValueRank属性去进行判断
}
dataType->typeName = "unknow";
dataType->binaryEncodingId = structureDefinition.defaultEncodingId;
dataType->typeId = dataTypeNodeId;
dataType->memSize = 12;
dataType->typeKind = UA_DATATYPEKIND_STRUCTURE;// 可根据structureDefinition.structureType判定;
dataType->pointerFree = true;
dataType->overlayable = false;
dataType->membersSize = structureDefinition.fieldsSize;
dataType->members = dataTypeMembers;
// 将该类型添加进client自定义数据类型中
UA_DataTypeArray customDataTypes = { NULL, 1, dataType };
config->customDataTypes = &customDataTypes;
retval = UA_Client_readValueAttribute(client, varNode, &variant);
if (retval != UA_STATUSCODE_GOOD) {
printf("UA_Client_readValueAttribute failed, retval = %d\n", retval);
UA_Client_disconnect(client);
UA_Client_delete(client);
system("pause");
return EXIT_FAILURE;
}
printf("value type name = %s\n", variant.type->typeName);
// 输出对应的值
for (int ii = 0; ii < structureDefinition.fieldsSize; ii++)
{
unsigned int offset = 0;
UA_StructureField* field = &structureDefinition.fields[ii];
UA_NodeId floatNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_FLOAT);
if (UA_NodeId_equal(&field->dataType, &floatNodeId)) {
UA_Float* ptr = (UA_Float*)variant.data;
printf("value %d = %f, ", ii, *(ptr+offset));
offset += sizeof(UA_Float);
}
else
{
// ....处理其他类型数据
}
}
printf("\n");
UA_Client_disconnect(client);
UA_Client_delete(client);
system("pause");
return EXIT_SUCCESS;
}
这里主要是通过找到该变量的数据类型(DataType),然后通过获取该数据类型的信息去构造对应的数据类型并添加进客户端自定义数据类型中去。在structureDefinition中有服务器端添加该自定义数据类型带有的所有信息,调试时看到的相关信息如下
相关字段的描述都非常详细,与UaExpert中的相关信息一致
这是举例比较简单的类型,更复杂的类型padding以及结构大小等一系列值需要进行动态计算。在这个例程中没有进行处理。值的获取通过指针的偏移来进行实现。运行该客户端,结果如下
可见对应的值以及获取成功了,与UaExpert中的结果一致。
说一说
获取自定义数据类型节点值还是比较麻烦,因为我们这边没有特定的数据结构去转换对应的值,还需将其结构解析出来并添加到自身的自定义数据类型中。刚开始花了很久时间去直接根据类型解析对应的值,发现里面的值都是存在问题的,于是查看了处理消息的部分源码如下
/* Processes the received service response. Either with an async callback or by
* decoding the message and returning it "upwards" in the
* SyncResponseDescription. */
static UA_StatusCode
processServiceResponse(void *application, UA_SecureChannel *channel,UA_MessageType messageType, UA_UInt32 requestId, UA_ByteString *message)
{
......
/* Decode the response */
retval = UA_decodeBinaryInternal(message, &offset, rd->response, rd->responseType,
rd->client->config.customDataTypes);
......
}
发现客户端在解析收到的二进制数据时会根据自定义的数据类型去进行解析,如果解析不成功的话返回的值是存在问题的。客户端在没有添加对应的数据类型时获取到的UA_Variant值类型是ExtensionObject的
在添加构造的数据类型后,我们可以看到它的结构发生了变化
可以看出后者是解析成功了。我们可以在UA_decodeBinaryInternal处查看message对应的内存信息,会发现从服务器端过来的信息是以一定格式存储并且包含了值的相关信息,客户端这边没有对应格式的数据类型定义的话无法正常进行解析。