[14] open62541 use _serverto add custom data types and use

brief introduction

The data type is different from the variable type. The variable type is based on the data type. We usually create variables using the variable type. Generally speaking, custom data types are structures. Mainly refer to open62541's own example.

target

Add custom structure types to the Server as data types and use them.

Fundamentals of reading

  • Understand Qt programming
  • Understand the use of QtCreator
  • Understand openua protocol
  • Learn about open62541

Fast reading

If you are familiar with the use of Qt, skip the basic engineering construction.

Environmental description

  • Operating system: Windows-10-64bit

  • QT version: Qt5 12.6+QtCreator4. ten

  • Open62541: V1.2

Related software

Uaexpert is a fully functional OPCUA client, which can provide multiple OPCUA configuration files and functions.

Designing custom structures

Types of OPCUA involved

UA is mainly used_ Datatype and UA_DataTypeMember, UA_DataType is used to describe the whole, UA_DataTypeMember is used to describe structure members, UA_DataTypeMember is UA_ Member of datatype. The prototype is defined as follows. Refer to the notes for the meaning of the elements,

typedef struct {
#ifdef UA_ENABLE_TYPEDESCRIPTION
    const char *memberName;
#endif
    UA_UInt16 memberTypeIndex;    /*Index of members in data type array member index in data type array */
    UA_Byte padding;              /*How many padding are there before this member element?
                                     How much padding is there before the member element? For arrays, this is
                                     For arrays, this is in size_ padding before t length member.
                                     (In size_ (no padding between T and ptr below) */
    UA_Boolean namespaceZero : 1; /*The type of the member is defined in
                                     Namespace is zero. In this implementation.
                                     The types of custom namespaces can contain
                                     Or contains only members from the same namespace or
                                     Member of namespace zero.
    UA_Boolean isArray : 1;       /*The member is an array *//
    UA_Boolean isOptional : 1;    /*Member is an optional field */
} UA_DataTypeMember;

struct UA_DataType {
#ifdef UA_ENABLE_TYPEDESCRIPTION
    const char *typeName;
#endif
    UA_NodeId typeId;             /*Node ID of this type */
    UA_UInt16 memSize;            /* The size of the structure in memory */
    UA_UInt16 typeIndex;          /*Type index in data type table */
    UA_UInt32 typeKind : 6;       /*Scheduling index of processing routine */
    UA_UInt32 pointerFree : 1;    /* The type (and its members) does not contain pointers that need to be released. Pointers that need to be released. The type has the same memory layout in memory and binary streams.*/
    UA_UInt32 membersSize : 8;   /*How many members does this type have?*/
    UA_UInt32 binaryEncodingId;  /*NodeId of the data type when encoded as binary */*
    //UA_UInt16 xmlEncodingId;   /* When encoded as XML, the NodeId * / of the data type.
    UA_DataTypeMember *members;
};


Simple example

Suppose there is a structure, Point is defined as follows

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

Describe structure members

First, we use UA_DataTypeMember to describe members x, y and z, as follows:,

static state UA_DataTypeMember Point_members[3] = {
    /* x */
    {
        UA_TYPENAME("x") /* .memberName */
        UA_TYPES_FLOAT,  /* .memberTypeIndex, Point to UA_TYPES, because namespaceZero is true */ . 
        0,               /* .padding */
        true,            /* .namespaceZero, See you memberTypeIndex */
        false,           /* .isArray */
        false            /* .isOptional */
    },

    /* y */
    {
        UA_TYPENAME("y") /* .memberName */ *.
        UA_TYPES_FLOAT,  /* .memberTypeIndex, Point to UA_TYPES, because namespaceZero is true */. 
        Point_padding_y, /* .padding */
        true,            /* .namespaceZero, See you memberTypeIndex */
        false,           /* .isArray */
        false            /* .isOptional */
    },
    /* z */
    {
        UA_TYPENAME("z") /* .memberName */
        UA_TYPES_FLOAT,  /* .memberTypeIndex, Point to UA_TYPES, because namespaceZero is true */ . 
        Point_padding_z, /* .padding */
        true,            /* .namespaceZero, See you memberTypeIndex */
        false,           /* .isArray */
        false            /* .isOptional */
    }

};

The whole description is relatively simple and difficult to understand. The padding value of the second member y is Point_padding_y. It is defined as follows,

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

offsetof is used to obtain the offset of the member in the structure. Since x is the first element, its offset is 0. Point_padding_y is the size of the space from the end of X to the beginning of y, that is, the padding size before the beginning of y.

The memory space occupied by structure members may not be consistent with their actual size due to alignment, such as the following,

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

Due to alignment, the memory space occupied by y is not 1 byte, but 4 bytes, preceded by 3 bytes of padding.

Therefore, for structures that may generate padding due to alignment, you need to calculate padding and assign it to UA_ pdding variable in datatypemember.

Describe the entire structure

Next is the overall information describing the structure,

static UA_DataType PointType = {
        UA_TYPENAME("spot")                   /* .tyspeName */
        {1, UA_NODEIDTYPE_NUMERIC, {4242}}, /* .typeId */
        sizeof(Point),                      /* .memSize */
        0,                                  /* .typeIndex, In arrays of custom types */
        UA_DATATYPEKIND_STRUCTURE,          /* .typeKind */
        true,                               /* .pointerFree */
        false,                              /* .overlayable (It depends on the number of bytes and whether there is a filler*/
        3,                                  /* .membersSize */
        Point_binary_encoding_id,           /* .binaryEncodingId, A numeric identifier used on a wire. The namespace index comes from typeId) */
        Point_members                       /* .members */
};

Some of the key elements need to be explained,

  • typeId: NodeId of the data type
  • memSize: the actual size of the structure
  • typeIndex: the position in the custom type array, which is struct UA_ Member customDataTypes in serverconfig
  • typeKind: UA is used here_ DATATYPEKIND_ Structure indicates a structure
  • pointerFree: true given here means that there is no pointer to release. If there is a pointer in the structure, it needs to be set to false
  • membersSize: number of members. Here is 3
  • binaryEncodingId: Point_ binary_ encoding_ The actual value of ID is 1. I don't know what this is
  • Members: member description, that is, the variable point defined in the previous section_ members

Create Qt project

project name

Server_Data_Type

Create a common Qt project

reference resources: [5] open62541 use two servers to interact Xinfei blog - CSDN blog Chapter with the same name

reference resources: [5] opcua62541 realizes the interaction between two servers Xinfei's personal website Chapter with the same name

Add opcua62541 library to Qt project

reference resources: [5] open62541 use two servers to interact Xinfei blog - CSDN blog Chapter with the same name

reference resources: [5] opcua62541 realizes the interaction between two servers Xinfei's personal website Chapter with the same name

Add a custom data type to the Server

Add related code

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QDebug>

extern "C"{
#include "open62541/open62541.h"
}

#include "custom_datatype.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
public:
    static void add3DPointDataType(UA_Server* server);
    static void add3DPointVariableType(UA_Server* server);
    static void add3DPointVariable(UA_Server* server);

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H


mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

UA_Boolean running = true;

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

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

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    signal(SIGINT, stopHandler);
        signal(SIGTERM, stopHandler);

        //All servers require server configuration and lifecycle management


        //Create server
        UA_Server *server = UA_Server_new();
        UA_ServerConfig_setDefault(UA_Server_getConfig(server));

        //Add application

        /* Get server settings */

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

        /* Let the stack know your custom data type */

        UA_DataType *types = (UA_DataType*)UA_malloc(1 * sizeof(UA_DataType));

        UA_DataTypeMember *pointMembers = (UA_DataTypeMember*)UA_malloc(3 * sizeof(UA_DataType));
        pointMembers[0] = Point_members[0];
        pointMembers[1] = Point_members[1];
        pointMembers[2] = Point_members[2];

        types[0] = PointType;
        types[0].members = pointMembers;

        /* be careful! Here the custom data type is assigned to the stack. Therefore, they cannot be accessed from parallel (working) threads.*/

        UA_DataTypeArray customDataTypes = {config->customDataTypes, 1, types};
        config->customDataTypes = &customDataTypes;

        /* Add a variable node of custom type */

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


        //Run server
        UA_StatusCode retval = UA_Server_run(server, &running);

        //Delete server
        UA_Server_delete(server);

        retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
        qDebug()<<retval;

}

MainWindow::~MainWindow()
{
    delete ui;
}

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

    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0,UA_NS0ID_STRUCTURE);
    UA_NodeId referenceNodeId = UA_NODEID_NUMERIC(0,UA_NS0ID_HASSUBTYPE);
    UA_QualifiedName browsName = UA_QUALIFIEDNAME(1,"the.answer");

    UA_Server_addDataTypeNode(server,
                              PointType.typeId,
                              parentNodeId,
                              referenceNodeId,
                              browsName,
                              attr, NULL, NULL);

}

void MainWindow::add3DPointVariableType(UA_Server *server)
{
    Point p;
    p.x = 0.0;
    p.y = 0.0;
    p.z = 0.0;

    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;
    UA_Variant_setScalar(&dattr.value, &p, &PointType);

    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0,UA_NS0ID_BASEDATAVARIABLETYPE);
    UA_NodeId referenceNodeId = UA_NODEID_NUMERIC(0,UA_NS0ID_HASSUBTYPE);
    UA_QualifiedName browsName = UA_QUALIFIEDNAME(1,"3D.Point");
    UA_NodeId typeDefinition = UA_NODEID_NUMERIC(0,UA_NS0ID_BASEDATAVARIABLETYPE);
    UA_Server_addVariableTypeNode(server,
                                  pointVariableTypeId,
                                  parentNodeId,
                                  referenceNodeId,
                                  browsName,
                                  typeDefinition,
                                  dattr, NULL, NULL);



}

void MainWindow::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_NodeId requestedNewNodeId = UA_NODEID_STRING(1,"3D.Point");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0,UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId referenceNodeId = UA_NODEID_NUMERIC(0,UA_NS0ID_ORGANIZES);
    UA_QualifiedName browsName = UA_QUALIFIEDNAME(1,"3D.Point");
    UA_Server_addVariableNode(server,
                              requestedNewNodeId,
                              parentNodeId,
                              referenceNodeId,
                              browsName,
                              pointVariableTypeId,
                              vattr, NULL, NULL);

}

custom_datatype.h

#ifndef CUSTOM_DATATYPE_H
#define CUSTOM_DATATYPE_H

extern "C"{
#include "open62541/open62541.h"
}

/* This work is licensed under the CCZero 1.0 general license agreement for knowledge sharing.
 *For more information, see http://creativecommons.org/publicdomain/zero/1.0/ . */

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

/* Point Data type description of data type */
#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)


/* Binary coded ID of the data type */

#define Point_binary_encoding_id 1

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

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

};

static const UA_DataType PointType = {

    {1, UA_NODEIDTYPE_NUMERIC, {4242}}, /* .typeId */
    {1, UA_NODEIDTYPE_NUMERIC, {Point_binary_encoding_id}}, /* .binaryEncodingId, the numeric
                                        identifier used on the wire (the
                                        namespaceindex is from .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_members
    UA_TYPENAME("Point")                /* .tyspeName */
};


#endif // CUSTOM_DATATYPE_H

code analysis

Code flow:

  1. First create the server and get its configuration.

  2. Add the user-defined data type information to the server configuration. In this example, there is only one data type, so create an array with only one element. According to the comments, you can know that config - > customdatatypes is a single linked list. Because the linked list currently has only one element, the next points to itself.

   /* An array of data types with custom type definitions can be added to a link list
    * Client or server configuration. Data type members can point to types in the same array through ` ` member type index ''.
    * `memberTypeIndex`Point to the same array. If "namespaceZero" is set to
    * true,The data type of the member is found in an array of built-in data types.
    * Instead.*/
typedef struct UA_DataTypeArray {
       const struct UA_DataTypeArray *next;
       const size_t typesSize;
       const UA_DataType *types;
} UA_DataTypeArray;
  1. First, add the data type to the address space of the Server.

  2. Then use this data type to add variable types.

  3. Finally, create variables using variable types.

add3DPointDataType() analysis:

Add the data type to the address space of the server. UA_ Server_ The second parameter of adddatatypenode () is the NodeId of the previously defined data type. The display name of the data type is "3D Point Type", and the browse name is "3D.Point".

add3DPointVariableType() analysis:

  1. Use this data type to add a variable type. The display name of the variable type is "3D Point" and the browse name is "3D.Point".

  2. The key point is dattr Datatype passed pointtype TypeID, that is, the data class that defines the variable type

  3. dattr.valueRank select UA_VALUERANK_SCALAR, because the members in the structure are single variables.

  4. Pointvariabletypeid is defined as follows: const UA_NodeId pointVariableTypeId = {1, UA_NODEIDTYPE_NUMERIC,{ 4243 } };

  5. To dattr Value assignment, you can set the initial value of the structure.

  6. The last parameter passes the address of PointType.

Note: the NodeId of the variable type and the NodeId of the data type are different and exist independently.

PS: when this variable type is used to create variables later, p will be regenerated, similar to the member variables in class

add3DPointVariable() analysis:

  1. Create a variable using the variable type. The display name of the variable is "3D Point" and the browse name is "3D.Point".
  2. The initialization of vattr is used to determine the data type and initial value.
  3. UA_ Server_ The penultimate parameter of addvariablenode() is the NodeId of the variable type we added, that is, pointVariableTypeId.

UaExpert test

Run Qt program

UaExpert view

reference resources: [6] open62541 use _serverto establish the connection between variables and physical information Xinfei blog CSDN blog Chapter with the same name.

reference resources: [6] Establish the connection between variables and physical information in Server Xinfei's personal website Chapter with the same name.

Using UaExpert to connect, you can see that the variable has been created, and then look at the properties of the variable, which is the same as expected.

The added data type is in the following location.

The added variable type is in the following location.

Recent updates

To view recent updates to this article, click

Keywords: IoT ARM MCU

Added by MiCR0 on Fri, 24 Dec 2021 22:48:49 +0200