Avatar
Please consider registering
guest
sp_LogInOut Log Insp_Registration Register
Register | Lost password?
Advanced Search
Forum Scope


Match



Forum Options



Minimum search word length is 3 characters - maximum search word length is 84 characters
sp_Feed Topic RSSsp_TopicIcon
Custom Object Types
March 19, 2026
14:23, EET
Avatar
Patrick
Member
Members
Forum Posts: 17
Member Since:
October 15, 2020
sp_UserOfflineSmall Offline

Hi,

I’m trying to define custom object types and use them to create objects but run into some problems with that. I know the standard way is to have a NodeSet.xml defining the types import that with LoadModel into the AddressSpace, the part after that is a bit fuzzy for me though.

Do I need to actually create custom classes derived from TUaObject to use those? Or how is that supposed to work?

The concrete example I’m working on right now would be the PackML Specification:

https://reference.opcfoundatio…..odesets/56

Specifically the PackMLStatusObjectType

https://reference.opcfoundatio…..s/56/22059

The other topic related to this is, how can I dynamically, at runtime, create new ObjectTypes and use them? The definition for those types stem from data a user enters as a configuration for the type that should be created. I tried using TUaType like this:

MyType := TUaType.Create(MyNodeManager);
MyType.SuperType := MyNodeManager.GetType(Id_BaseObjectType);

p := TUaVariable.Create(MyNodeManager);
p.Name := ‘Test’;
p.DataTypeId := Id_Int32;

MyType.AddComponent(p);
MyType.Name := ‘MyTestType’;
MyType.NodeId := TUaNodeId.Create(MyNodeManager.Namespace, 9999);

MyNodeManager.RegisterType(MyType.NodeId, TUaObject);

MyObj := MyNodeManager.CreateObject(‘MyTestObj’);
MyObjectsFolder.AddOrganizes(MyObj);
MyObj.TypeDefinition := MyType;

but that runs into an abstract error at runtime. I guess because TUaType has the abstract Method GetNodeClass.
So that seems to be the wrong approach. Any hints on how this could be done?

Greetings
Patrick

March 19, 2026
16:00, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

Once you have the type loaded into the address space, you can create new instances of that type with ‘TUaNodeManager.CreateInstance’.

Take a look at ‘TUaSampleServerForm.CreateAlarmNode’ in the UaSampleServer, for an example.

You will just need the NodeId of the type for that.

Creating types dynamically at runtime is a bit more tricky, but basically, you just need to construct the nodes as you have done. Just use ‘TUaObjectType’ (in ProsysOPC.UaServer.Nodes), instead of ‘TUaType’. We haven’t tried that very much, but I believe it should work…

And again, you should be able to use ‘CreateInstance’ to create the whole structure that corresponds to the type definition.

For types, you should also define the ModellingRule for each InstanceDeclaration (Object and Variable component), to instruct how it should be created: only Mandatory nodes are created by default. For example:

MyComponent.AddReference(Id_ModellingRule_Mandatory, Id_HasModellingRule);
March 19, 2026
17:11, EET
Avatar
Patrick
Member
Members
Forum Posts: 17
Member Since:
October 15, 2020
sp_UserOfflineSmall Offline

Using the CreateInstance works if the NodeSet.xml is present. It does not create optional entries though, even when I add the NodeBuilderConfiguration as shown in the CreateAlarmNode function.

the following code:

UaServer.AddressSpace.LoadModel(‘PackMLNamespace.xml’);

PackMLNodeManager := UaServer.AddressSpace.GetNodeManager(
‘http://opcfoundation.org/UA/PackML/’)
as TUaNodeManagerUaNode;

PackMLNodeManager.NodeBuilderConfiguration :=
TUaTypeDefinitionBasedNodeBuilderConfiguration.Builder
.AddOptional(‘Parameter’)
.AddOptional(‘Product’)
.AddOptional(‘MaterialInterlocked’)
.Build;

MyStatus := PackMLNodeManager.CreateInstance(TUaNodeId.Create(PackMLNodeManager.Namespace, 4), ‘Status’);
MyObjectsFolder.AddOrganizes(MyStatus);

leads to the following object being created:
https://ibb.co/dwxK492R

It’s not adding the optional elements. Guess I misread something there?

For the dynamic type creation, just using TUaObjectType will eliminate the runtime error but it does not seem to work either. The type will not be visible in the type library of the server and creating an instance just creates an empty object.

If I do this on the other hand it does work a bit but still not completely:

MyType := MyNodeManager.GetType(Id_BaseObjectType).AddSubtype(TUaNodeId.Create(MyNodeManager.Namespace, 9999), ‘MyTestType’);

// MyType := TUaObjectType.Create(MyNodeManager);
// MyType.SuperType := MyNodeManager.GetType(id_BaseObjectType);
// MyType.NodeId := TUaNodeId.Create(MyNodeManager.Namespace, 9999);

p := TUaVariable.Create(MyNodeManager);
p.Name := ‘Test’;
p.DataTypeId := Id_Int32;
p.AddReference(Id_ModellingRule_Mandatory, Id_HasModellingRule);

MyType.AddComponent(p);
MyType.AddReference(p, Id_HasComponent);

MyType.AddProperty(‘TestProperty’);

MyObj2 := MyNodeManager.CreateInstance(MyType.NodeId, ‘MyTestObj’);
MyObjectsFolder.AddOrganizes(MyObj2);

Now the Type does show up under the type definition.
https://ibb.co/4w16DCT6

But the variable under it does not, only the property is there as you can see.

And the created instance looks like this:
https://ibb.co/4ZcFF1Rv

It is missing the property and the variable.

As loading a NodeSet.xml is basically creating types at runtime too, I figured it should be possible to simulate that without a NodeSet.xml as well.

What is already working today is to simply go with a BasicObjectType and manually add all the variables and stuff I want to build the type I need, just without a dedicated type. The problem I ran into with that is if I need an array of objects it should really be an array of a specific object type instead of an array of basic objects. Otherwise the client will have a hard time figuring out how the structure actually is supposed to be.

PS: I’m on version 7.8.0 Build 881 if that has any relevance for this topic.

Greetings
Patrick

March 19, 2026
17:58, EET
Avatar
Patrick
Member
Members
Forum Posts: 17
Member Since:
October 15, 2020
sp_UserOfflineSmall Offline

Update:
Seems like the trick to get the UaVariable added to the type definition is to use:

p := MyNodeManager.CreateVariable(‘Test’);
instead of
p := TUaVariable.Create(MyNodeManager);

March 19, 2026
18:36, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

Good that you figured out the Variable creation. I don’t even recall what was the exact difference here, but CreateVariable is the proper way to do it…

When you create your object instances, you should not place them in the PackMLNodeManager, but your own. So always use

MyNodeManager.CreateInstance and TUaNodeId.Create(MyNodeManager, …)

for them. The PackMLNodeManager is hosting the types defined in that model and you should not extend it yourself.

Note that CreateInstance will also define a new NodeId based on the name, by default, if you omit the NodeId. But you can see which strategy works the best for you.

For the optional members, you need to define the names using the PackML Namespace. The default is the OPC UA Standard namespace, which works for those types only. And again, modify your own NodeManager. So, try this:

MyNodeManager.NodeBuilderConfiguration :=
TUaTypeDefinitionBasedNodeBuilderConfiguration.Builder
.AddOptional(TUaQualifiedName.Create(PackMLNodeManager.Namespace, ‘Parameter’))
.AddOptional(TUaQualifiedName.Create(PackMLNodeManager.Namespace, ‘Product’))
.AddOptional(TUaQualifiedName.Create(PackMLNodeManager.Namespace, ‘MaterialInterlocked’))
.Build;

March 19, 2026
18:38, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

And for the subtypes, you need to have the HasSubtype reference in place. So, best to use the AddSubType method, which adds that, too.

March 20, 2026
10:29, EET
Avatar
Patrick
Member
Members
Forum Posts: 17
Member Since:
October 15, 2020
sp_UserOfflineSmall Offline

Thanks for the help, seems like I got everything I need now and can build the actual logic for the server to support PackML and custom types.

March 20, 2026
10:32, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

Super. Just ask, if there are more details that you need to refine.

March 20, 2026
14:49, EET
Avatar
Patrick
Member
Members
Forum Posts: 17
Member Since:
October 15, 2020
sp_UserOfflineSmall Offline

I ran into another thing now. Is it possible to create datatypes for structures dynamically as well? The example in the server (CreateDynamicStructureValue) shows how to use a type that is defined in a nodeset.xml but again I’d like to skip the NodeSet.xml and do it manually / dynamically in code.

What I got so far is the following:

MyType2 := (MyNodeManager.GetType(Id_StructureType) as TUaDataType)
.AddSubtype(TUaNodeId.Create(MyNodeManager.Namespace, 9998), ‘MyTestStruct’) as TUaDataType;

pDef := TUaDataTypeDefinition.Create;

//define the actual datatype
//?

MyType2.DataTypeDefinition := pDef;

The problem is I don’t see a way to actually define the fields in the TypeDefinition, as it seems like it only allows to read fields, but not to actually define them. Using the Builder Interface (TUaDataTypeDefinition.Builder) doesn’t seem to help either, as that again expects a definition to create the structure…

March 20, 2026
19:07, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

Yes, this is not so simple, and I need to study a bit more, to see if it is even possible at the moment. The data types are typically defined with UaModeler and exported to Nodeset files, where they are loaded to the server types.

I tried to find the proper way to do it, but it may be that the SDK needs a bit of tweaks to enable what you need. I will keep you updated.

March 20, 2026
20:12, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

Well, it wasn’t that complicated, after all.:)

A couple of hurdles (bugs) were just making it a bit harder…

This method defines a new structure type:

procedure TUaSampleServerForm.CreateMyStructureType;
var
Fields: TArray;
MyBinaryEncodeId: TUaNodeId;
MyBinaryEncoding: TUaObject;
MyStructureTypeId: TUaNodeId;
S: IUaStructureDefinition;
begin
MyStructureTypeId := TUaNodeId.Create(MyNodeManager.Namespace, 10001);
MyBinaryEncodeId := TUaNodeId.Create(MyNodeManager.Namespace, 10002);

// The new Type Node
MyStructureType := UaServer.AddressSpace.GetType(Id_Structure).
AddSubtype(MyStructureTypeId, ‘MyStructureType’) as TUaDataType;
// Seems to be a bug that this needs to be defined separately:
MyStructureType.BrowseName :=
TUaQualifiedName.Create(MyNodeManager.Namespace, ‘MyStructureType’);

// Encoding Node for the new Type
MyBinaryEncoding := MyNodeManager.CreateObject(‘Default Binary’, MyBinaryEncodeId);
MyBinaryEncoding.TypeDefinitionId := Id_DataTypeEncodingType;
MyStructureType.AddReference(MyBinaryEncoding, Id_HasEncoding);

// The Structure Fields
SetLength(Fields, 2);

Fields[0] := TUaStructureField.Builder.
Name(‘Field0’).DataType(Id_String).ValueRank(vrScalar).IsOptional(False).
Build;
Fields[1] := TUaStructureField.Builder.
Name(‘Field1’).DataType(Id_Double).ValueRank(vrScalar).IsOptional(False).
Build;

// The Structure Definition
S := TUaStructureDefinition.Builder.
BaseDataType(Id_Structure).
DefaultEncodingId(MyBinaryEncodeId).
StructureType(stStructure).
Fields(Fields).
Build;

MyStructureType.DataTypeDefinition := S;
end;

and then you can create the variable with:

MyStructure := MyNodeManager.CreateVariable(‘MyStructure’);
MyStructure.DataType := MyStructureType; // Another bug in here, so you need to use also:
MyStructure.DataTypeId := MyStructureType.NodeId;

and then you can create the value, with

MyStructure.Value := CreateMyStructureValue;

where

function TUaSampleServerForm.CreateMyStructureValue: IUaDynamicStructure;
begin
Result := UaServer.TypeDictionary.CreateDynamicStructureBuilder(MyStructureType.NodeId).
SetFieldValue(‘Field0’, ‘a string’).
SetFieldValue(‘Field1’, 4.4).
Build;
end;

I will get the bugs fixed to the next release, and can provide you a beta as well.

But fix the BrowseName for your ObjectType as well. Prosys OPC UA Browser didn’t seem to like it, if it was missing…

March 23, 2026
11:31, EET
Avatar
Patrick
Member
Members
Forum Posts: 17
Member Since:
October 15, 2020
sp_UserOfflineSmall Offline

That does look very promising. A first quick test with the sample server project looked good.
I added the browse name fix for the object type creation too, just in case.

Thank you for the help with this and if you found a couple bugs along the way that’s a nice benefit for you too. Fewer bugs are always nice 😉

March 23, 2026
11:57, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

Yeah, it’s good to find all the details. You seem to be ahead of everyone else, since these parts of the SDK hadn’t been used before and not 100% verified for your case…

And it’s always nice to work on things that people use for real. So, let us know if you bump into something else along the way.

March 24, 2026
13:53, EET
Avatar
Patrick
Member
Members
Forum Posts: 17
Member Since:
October 15, 2020
sp_UserOfflineSmall Offline

I stumbled over something else, which I guess is fairly simple I just don’t see it right now.

How do I create an array of a complex type? For example my Structure looks like this:

MyStructureType
-> Field1: Integer
-> Field2: String
-> Field3: array of String

MyStructureType2
-> Field1: string
-> Field2: array of MyStructureType

Just to clarify I’m not talking about the declaration of the type this time, that works perfectly fine but the creation of the actual value for it using the

MyBuilder := UAServer.TypeDictionary.CreateDynamicStructureBuilder(MyStructureType2.NodeID);

the standard fields are clear, just call

MyBuilder.SetFieldValue(‘Field1’, ‘Test’)

For an array of simple types I use something like this:
var
v: Variant;

v := VarArrayCreate([0, 6], varString);
TUaVariant.Create(v);

for a complex type I tried using this:
var
A: Array of TUaVariant;
MyTypeVar: TUaVariable;
begin
MyTypeVar := MyNodeManager.CreateVar(‘test’);
MyTypeVar.DataType := MyStructType2;
MyTypeVar.DataTypeID := MyStructType2.NodeID;

MyBuilder := UAServer.TypeDictionary.CreateDynamicStructureBuilder(myStructType2.NodeId);

SetLength(A, 3);

for i := 0 to 2 do
begin
p := CreateStructure(…); //this one creates a builder for the MyStructType and fills it with values, that works
A[0] := p;
end;

MyBuilder.SetField(‘Field2’, TUaVariant.Create(A));

MyTypeVar.Value := MyBuilder.Build;
end;

that compiles without problems but gives me a runtime error “Invalid class typecast” when I try to assign the actual build variable to the UAVariable, so last line there.

I tried a couple different approaches too but always ran in some kind of runtime error. I’m pretty sure I’m doing something wrong there but I’m not sure what…

March 24, 2026
15:45, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

Well, yeah, quite simple, if you know what you need to do. Wink

I didn’t remember even myself by heart, but managed to create the sample. It’s a bit too complicated to work together with TUaVariant and Variant and the arrays require Variants.

So, it goes like this:

function TUaSampleServerForm.CreateMyStructureArrayValue: TUaVariant;
var
V: Variant;
begin
V := VarArrayCreate([0,1], varVariant);
// VarStructureCreate is in ProsysOPC.UaVariants
V[0] := VarStructureCreate(CreateMyStructureValue);
V[1] := VarStructureCreate(CreateMyStructureValue);
Result := TUaVariant.Create(V);
end;

where ‘CreateMyStructureValue’ returns a single ‘IUaStructure’ value, similar to what I wrote above (I will add the function definition in there, too).

March 24, 2026
16:05, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

And this method would do the generic conversion:

function TUaSampleServerForm.CreateStructureArray(Value: array of TUaVariant):
TUaVariant;
var
High: Integer;
I: Integer;
V: Variant;
begin
High := Length(Value)-1;
V := VarArrayCreate([0,High], varVariant);
for I := 0 to High do
// VarStructureCreate is in ProsysOPC.UaVariants
V[I] := VarStructureCreate(Value[I]);
Result := TUaVariant.Create(V);
end;
March 24, 2026
16:06, EET
Avatar
Patrick
Member
Members
Forum Posts: 17
Member Since:
October 15, 2020
sp_UserOfflineSmall Offline

Update:

Ok, figured out how that is supposed to work. I found the VarStructureCreate function in UAVariants.

What you do is create a normal variant array and then add the type structure with VarStructureCreate(…) to each element of that array. So something like this (this example omits a couple steps but highlights the part of how to create the array of the structure type.

var
V: Variant
myStruct: IUaDynamicStructure;
begin
MyTypeVar := MyNodeManager.CreateVar(‘test’);
MyTypeVar.DataType := MyStructType2;
MyTypeVar.DataTypeID := MyStructType2.NodeID;

MyBuilder := UAServer.TypeDictionary.CreateDynamicStructureBuilder(myStructType2.NodeId);

//fill normal elements
V := VarArrayCreate([0, 1], varVariant);
for i := 0 to 2 do
begin
myStruct := CreateStructure(..); //this creates the actual structure using the Builder Interface
V[i] := VarStructureCreate(MyStruct);
end;

MyBuilder.SetFieldValue(‘Field2’, TUaVariant.Create(V));

MyTypeVar.Value := MyBuilder.Build;
end;

March 24, 2026
16:20, EET
Avatar
Jouni Aro
Moderator
Moderators
Forum Posts: 1058
Member Since:
December 21, 2011
sp_UserOfflineSmall Offline

Well done. I managed to explain that, as well, while you already got it 🙂

Forum Timezone: Europe/Helsinki
Most Users Ever Online: 1919
Currently Online:
Guest(s) 41
Currently Browsing this Page:
1 Guest(s)
Top Posters:
Heikki Tahvanainen: 402
hbrackel: 146
rocket science: 114
pramanj: 86
Francesco Zambon: 83
Ibrahim: 78
Sabari: 62
kapsl: 57
gjevremovic: 49
Xavier: 43
Member Stats:
Guest Posters: 0
Members: 904
Moderators: 7
Admins: 1
Forum Stats:
Groups: 3
Forums: 15
Topics: 1587
Posts: 6693
Newest Members:
Michaelkam, chnmrc, ahmad.qureshi3@se.abb.com, connieorchard88, carlotae86, otiliabanks, kasha94646158368, bridgetterandle, julietabernacchi, eulakilvington
Moderators: Jouni Aro: 1058, Pyry: 1, Petri: 1, Bjarne Boström: 1081, Jimmy Ni: 26, Matti Siponen: 370, Lusetti: 1
Administrators: admin: 1