Enumeration type generation at runtime

833 views
Skip to first unread message

Philipp

unread,
Dec 22, 2016, 6:00:24 AM12/22/16
to open62541
Hi,

how do I generate custom enum types at runtime? I did not find good examples or documentation regarding this topic, so i had a look at how ua_server.c generates the enum "ServerType":


 addDataTypeNode(server, "Enumeration", UA_NS0ID_ENUMERATION, true, UA_NS0ID_BASEDATATYPE);
         addDataTypeNode
(server, "ServerState", UA_NS0ID_SERVERSTATE, false, UA_NS0ID_ENUMERATION);


Based on this I used the UA_Server_addDataTypeNode method like this:


UA_LocalizedText localized_name = UA_LOCALIZEDTEXT_ALLOC ("en_US", enum_name);

UA_NodeId object_type_id
= UA_NODEID_NUMERIC (0, m_serial_id++);


UA_DataTypeAttributes eAttr
;              
UA_DataTypeAttributes_init
(&eAttr);
             
eAttr
.description = localized_name;
eAttr
.displayName = localized_name;
             
auto ret = UA_Server_addDataTypeNode(m_server,
                              object_type_id
,
                              UA_NODEID_NUMERIC
(0, UA_NS0ID_ENUMERATION),
                              UA_NODEID_NUMERIC
(0, UA_NS0ID_ORGANIZES),
                              UA_QUALIFIEDNAME_ALLOC
(1, enum_name),
                              eAttr
, nullptr, nullptr);


When checking the return code, the error is: "The reference could not be created because it violates constraints imposed by the data model (805c000)".

What is the correct way of doing this (plus generating the enumeration values with their names, too)? Maybe someone can come up with a small enum example?

On a sidenote: I read a previous post regarding the generation of arrays of structs, and one answer said that this is currently not supported at runtime. I would be interested
in this, too. Is there any chance that this feature will be coming soon?

Julius Pfrommer

unread,
Dec 23, 2016, 6:07:04 AM12/23/16
to open62541
Hey Philipp,

custom datatypes are available on the master branch. The feature was developed after freezing the 0.2 API.
A small example for creating a custom datatype description is here: https://github.com/open62541/open62541/blob/master/tests/check_types_custom.c
1. Add the custom datatype description to your server and client config.
2. Add a datatypenode to the information model. Variables (VariableTypes) respect the subtyping rules (basedatatype > number > integer > int32) defined via node relations.

We might compress this into a single step / Server API method in the future.

I will have a look at your UA_Server_addDataTypeNode example. But I might take some time as the holidays are beginning.

Best regards.
Julius

Philipp

unread,
Dec 23, 2016, 8:06:31 AM12/23/16
to open62541
Hi Julius,

great, I will have a look at it! 

In the meantime I solved my problem:

I had to change the referenceTypeId argument to UA_NODEID_NUMERIC (0, UA_NS0ID_HASSUBTYPE), the function did not accept UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES).

Cheers and happy holidays,
Philipp

Philipp

unread,
Jan 24, 2017, 11:33:30 AM1/24/17
to open62541
Hi Julius,

I have a question regarding the OPC Enumeration type. I have written an example program with the intention of creating a new
enum type 'MyEnum' which has two different values 'ONE' and 'TWO'. The new type is a subtype of 'Enumeration',
and has a property of type UA_String[] which is an array that contains 'ONE' and 'TWO' as strings.

This is working just fine, in UAExpert I can see 'MyEnum' as a subtype of 'Enumeration' with the correct property.

But when i want to instantiate a variable of type 'MyEnum' (as a DataSourceVariableNode) , i get the error message:

The type definition node id does not reference an appropriate type node

Can you tell me why this is not working, or what I need to do to create a subtype of Enumeration with certain properties and instantiate it?



The example program is:




#include <signal.h>

#ifdef UA_NO_AMALGAMATION
#include "ua_types.h"
#include "ua_server.h"
#include "ua_config_standard.h"
#include "ua_network_tcp.h"
#include "ua_log_stdout.h"
#else
#include "open62541.h"
#endif

UA_Boolean running
= true;
UA_Logger logger
= UA_Log_Stdout;

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

static UA_StatusCode read_builtin (void * handle, const UA_NodeId id, UA_Boolean source_timestamp,
                                 
const UA_NumericRange * range, UA_DataValue * data_value) {
 
if (range) {
      data_value
->hasStatus = true;
      data_value
->status    = UA_STATUSCODE_BADINDEXRANGEINVALID;
     
return UA_STATUSCODE_GOOD;
 
}

  data_value
->hasValue = true;

  UA_Variant_setScalarCopy
(&data_value->value, handle,
                           
&UA_TYPES[UA_TYPES_INT32]);

 
if (source_timestamp) {
      data_value
->hasSourceTimestamp = true;
      data_value
->sourceTimestamp    = UA_DateTime_now ();
 
}

 
return UA_STATUSCODE_GOOD;
}

static UA_StatusCode write_builtin (void * handle, const UA_NodeId id, const UA_Variant * data,
                                 
const UA_NumericRange * r) {

 
if (UA_Variant_isScalar (data) && data->type == &UA_TYPES[UA_TYPES_INT32] && data->data != NULL) {
     
* (int32_t*) (handle) = * (int32_t *) (data->data);
 
}

 
return UA_STATUSCODE_GOOD;
}

int main(int argc, char** argv) {
    signal
(SIGINT, stopHandler); /* catches ctrl-c */

    UA_ServerConfig config
= UA_ServerConfig_standard;
    UA_ServerNetworkLayer nl
;
    nl
= UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
    config
.networkLayers = &nl;
    config
.networkLayersSize = 1;
    UA_Server
*server = UA_Server_new(config);


    uint32_t node_id_serial
= 2000;

   
// Register enum Datatype with 'base type' Enumeration

    UA_DataTypeAttributes attr
;
    UA_DataTypeAttributes_init
(&attr);

    attr
.displayName = UA_LOCALIZEDTEXT("en_US", "MyEnum");
    attr
.description = UA_LOCALIZEDTEXT("en_US", "A custom enum type");

    UA_NodeId nodeid
= UA_NODEID_NUMERIC (1, node_id_serial++);

    UA_NodeId parentNodeId          
= UA_NODEID_NUMERIC (0, UA_NS0ID_ENUMERATION);
    UA_NodeId parentReferenceNodeId
= UA_NODEID_NUMERIC (0, UA_NS0ID_HASSUBTYPE);

    UA_QualifiedName nodeName
= UA_QUALIFIEDNAME (1, "MyEnum");

    UA_Server_addDataTypeNode
(server, nodeid, parentNodeId, parentReferenceNodeId,
                                 nodeName
, attr, NULL, NULL);

   
// Register enum strings property

    UA_VariableAttributes property_arr_string
;
    UA_VariableAttributes_init
(&property_arr_string);

    property_arr_string
.displayName = UA_LOCALIZEDTEXT ("en_US", "StringValues");

    property_arr_string
.description = UA_LOCALIZEDTEXT ("en_US", "string value array");

    property_arr_string
.accessLevel     = 3;
    property_arr_string
.userAccessLevel = 3;
    property_arr_string
.valueRank       = 1;
    property_arr_string
.dataType        = UA_TYPES[UA_TYPES_STRING].typeId;
    property_arr_string
.arrayDimensions = UA_Array_new (2u, &UA_TYPES[UA_TYPES_STRING]);

    UA_String
* strings = UA_Array_new (2u, &UA_TYPES[UA_TYPES_STRING]);

    strings
[0] = UA_STRING("ONE");
    strings
[1] = UA_STRING("TWO");

   
// Enum string array

    UA_Variant_setArray
(&property_arr_string.value, strings, 2u, &UA_TYPES[UA_TYPES_STRING]);

    UA_NodeId typeDefinition
= UA_NODEID_NUMERIC (0, UA_NS0ID_PROPERTYTYPE);

    parentReferenceNodeId
= UA_NODEID_NUMERIC (0, UA_NS0ID_HASPROPERTY);

    UA_NodeId str_nodeid
= UA_NODEID_NUMERIC (1, node_id_serial++);

    UA_QualifiedName qualified_node_name
= UA_QUALIFIEDNAME (1, "EnumStrings");

    uint32_t ret
= UA_Server_addVariableNode (server, str_nodeid, nodeid, parentReferenceNodeId,
                                   qualified_node_name
, typeDefinition, property_arr_string,
                                   NULL
, NULL);

   
if (ret != UA_STATUSCODE_GOOD) {
        printf
("ERROR: %s", UA_StatusCode_description (ret)->explanation);
       
return -1;
   
}

    int32_t myEnumVar
= 1;

   
// Instantiate enum variable as datasource

    UA_LocalizedText localized_name
= UA_LOCALIZEDTEXT ("en_US", "MyEnumVar");

    UA_NodeId var_node_id
= UA_NODEID_NUMERIC (1, node_id_serial++);

    UA_VariableAttributes vAttr
;
    UA_VariableAttributes_init
(&vAttr);

    UA_LocalizedText localized_descr
;

    localized_descr
= UA_LOCALIZEDTEXT ("en_US", "This is a variable of type MyEnumVar");

    vAttr
.displayName     = localized_name;
    vAttr
.description     = localized_descr;
    vAttr
.accessLevel     = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    vAttr
.userAccessLevel = 3;
    vAttr
.valueRank       = -1;

    typeDefinition
= nodeid;

    parentReferenceNodeId
= UA_NODEID_NUMERIC (0, UA_NS0ID_HASCOMPONENT);

    nodeName
= UA_QUALIFIEDNAME (1, "MyEnumVar");

    UA_DataSource datasource
;

    datasource
.handle = &myEnumVar;
    datasource
.read = read_builtin;
    datasource
.write = write_builtin;

    ret
= UA_Server_addDataSourceVariableNode (server, var_node_id, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                                                  parentReferenceNodeId
, nodeName,
                                                  typeDefinition
, vAttr, datasource, NULL);

   
if (ret != UA_STATUSCODE_GOOD) {
      printf
("Failed to add variable node: %s\n",
                 UA_StatusCode_description
(ret)->explanation);
     
return -1;
   
}

    UA_StatusCode retval
= UA_Server_run(server, &running);

    UA_Server_delete
(server);
    nl
.deleteMembers(&nl);

   
return 0;
}




 


Cheers,

Philipp


Am Freitag, 23. Dezember 2016 12:07:04 UTC+1 schrieb Julius Pfrommer:

Grant Edwards

unread,
Jan 24, 2017, 12:33:24 PM1/24/17
to open62541


On Tuesday, January 24, 2017 at 10:33:30 AM UTC-6, Philipp wrote:

This is working just fine, in UAExpert I can see 'MyEnum' as a subtype of 'Enumeration' with the correct property.

But when i want to instantiate a variable of type 'MyEnum' (as a DataSourceVariableNode) , i get the error message:

The type definition node id does not reference an appropriate type node

IIRC, when you create a variable node, it's type definition needs to be a child of /Root/Types/VariableTypes.

I suspect you are passing as a type definition something that's under /Root/Types/DataTypes.


Philipp

unread,
Jan 25, 2017, 7:22:47 AM1/25/17
to open62541
Thank you. If I change the variable type to MultiStateDiscreteType it works, but creating a 'MyEnum' subtype of MultiStateDiscreteType does not work?

I just started reading into OPC UA and playing around with open62541, so I am not sure if I understand everything correctly, maybe you can clear things up a little bit?


First, let me point out my intention: I have a C++ software that organizes many of its modules and parameters in structs and arrays of structs. I want to use OPC UA
for 3 things:

1. run a server that automatically creates an OPC model at runtime, which maps my modules and parameters to OPC UA types, so I can browse them and modify
their values if needed.  At startup, a daemon would look into all registered modules and parameters etc, and then generate a server from this information.

2. Map the modules and parameters - or parts of them - to a companion specification, such that regardless of the internal organization, other people can still browse
and access everything via the companion specification logic. For example, I could map some of my modules to IEC 61131-3 Ctrl Configurations.

3. Run an OPC UA client to access other controls / PLCs (mapping the results and logic to the internal organization again)


As far as I understand from looking at an earlier discussion you had with Julius (https://groups.google.com/forum/#!topic/open62541/AH8K9tOkBZs) and Julius
first answer in this thread, it is not possible to create a custom datatype (modeling a C struct) at runtime. There seems to be some support for custom datatypes,
but it is not possible to only define a datatype for the server and have the client browse it, right? 

So I thought I have to model all my C structs as object types and ran into the enum issue. I thought when having a struct

enum class MyEnum { type_a, type_b};


struct a {
 
MyEnum t;
  int32_t i
;
};

I would be able to generate a new type 'MyEnum' subtyping the OPC UA type Enumeration. Or subtype MultiStateDiscreteType (but as I said this does not work for me, too). 
Then create a new object type with components t of type MyEnum and a component i of type UA_TYPES_INT32. Then I thought I would be able to instanciate an object and 
map all the variables to addresses in memory by using a DataSourceVariableNode. 

I thought this would be the way to implement goal 1. But I fail to make enum types work. I can add a variable of type MultiStateDiscreteType and set its properties, but do
I then need to set the properties for all my instances of type 'MyEnum'?  I hoped just having a custom defined enumeration type would be sufficient. 

Regarding goal 2: The 'OPC UA Information Model for IEC 61131-3' describes the Mapping of structured data types in section 5.2.3.4. For clients that do not
support complex datatypes, the specification states how the server can map structured data as a set of sub variables, but they recommend the support of complex data.
Are there any plans to support the creation of custom data types (and their encoding) in the future?

Regarding goal3: The section 'Mapping of structure data types' describes how servers should provide a DataTypeDictionary, maybe even a DictionaryFragment property
that only describes a single datatype and not the full information model (in the form of a ByteString that contains XML, if IIRC). Is it possible to load XML models or does
open62541 require the model to be available at compile time? 


To sum it up, I guess the main question (aside from this specific enumeration instantiation question) would be, if it is realistic that I will be able to achieve these goals 
using open62541?

Grant Edwards

unread,
Jan 25, 2017, 6:17:58 PM1/25/17
to open62541
On Wednesday, January 25, 2017 at 6:22:47 AM UTC-6, Philipp wrote:
 
Thank you. If I change the variable type to MultiStateDiscreteType it works, but
creating a 'MyEnum' subtype of MultiStateDiscreteType does not work?

If MultiStateDiscreteType is a variable type (rather than a data type), I would expect
it to work, but I haven't ever tried anything like that.
 
First, let me point out my intention: I have a C++ software that organizes many of
its modules and parameters in structs and arrays of structs. I want to use OPC UA
for 3 things:

1. run a server that automatically creates an OPC model at runtime, which maps
my modules and parameters to OPC UA types, so I can browse them and modify
their values if needed.  At startup, a daemon would look into all registered modules
and parameters etc, and then generate a server from this information.

That's sort of how my server works.  The data model is generated
at run-time based on which product model it's running on and what
extra hardware is attached.  I experimented with using a modeler to
create an XML file and then build the server using that, but it was
going to be a huge amount of extra work.

As far as I understand from looking at an earlier discussion you had with Julius
(https://groups.google.com/forum/#!topic/open62541/AH8K9tOkBZs) and Julius
first answer in this thread, it is not possible to create a custom datatype (modeling
a C struct) at runtime.

I'm pretty sure it is possible to do that at run-time.  However, I've decide that in
practice it's useless.  Clients don't support the full set of basic scalar datatypes,
let alone any custom ones.
 
There seems to be some support for custom datatypes, but it is not possible to
only define a datatype for the server and have the client browse it, right? 

Right.  I've discussed this with one SCADA vendor, and they stated that their
OPC/UA client has no support at all for custom datatypes.  Some of the clients
I've worked with don't even support all of the basic scalar data types.  For example,
they can't read a ByteString and do anything with it.
 
So I thought I have to model all my C structs as object types and ran into the
enum issue. I thought when having a struct

That's basically what I did: all of my structs are now object trees.  And since you can't have
arrays of object trees, you have to modal an array of structs as sets of identically structured
object trees that are logically independent of each other with names like "Port1" "Port2" "Port3"
etc.

I initially tried create object types and instantiating them, but then I ran into problems
because the nodeIDs where unpredictable and changed from one firmware build
to the next.  So, I gave up on using object types and now I just have a C function for
each "object type" that I call to create a tree of nodes under a specific parent node.
 

enum class MyEnum { type_a, type_b};


struct a {
 
MyEnum t;
  int32_t i
;
};

I would be able to generate a new type 'MyEnum' subtyping the OPC UA type Enumeration.

I'm afraid I haven't tried to create subtypes of any of the standard types.
 
Regarding goal 2: The 'OPC UA Information Model for IEC 61131-3' describes
the Mapping of structured data types in section 5.2.3.4. For clients that do not
support complex datatypes,

From what I can gather "clients that do not support complex datatype" is pretty
much all of them in use today.
 
I think you are going to need to identify what set of clients you want to work with
and do some testing to figure out what features and data types they actually support.

Reply all
Reply to author
Forward
0 new messages