Topic RSS14:23, EET
October 15, 2020
OfflineHi,
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
16:00, EET
December 21, 2011
OfflineOnce 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:
17:11, EET
October 15, 2020
OfflineUsing 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
18:36, EET
December 21, 2011
OfflineGood 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;
18:38, EET
December 21, 2011
Offline10:32, EET
December 21, 2011
Offline14:49, EET
October 15, 2020
OfflineI 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…
19:07, EET
December 21, 2011
OfflineYes, 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.
20:12, EET
December 21, 2011
OfflineWell, 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:
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.DataType := MyStructureType; // Another bug in here, so you need to use also:
MyStructure.DataTypeId := MyStructureType.NodeId;
and then you can create the value, with
where
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…
11:31, EET
October 15, 2020
OfflineThat 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 😉
11:57, EET
December 21, 2011
OfflineYeah, 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.
13:53, EET
October 15, 2020
OfflineI 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…
15:45, EET
December 21, 2011
OfflineWell, yeah, quite simple, if you know what you need to do. 
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:
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).
16:05, EET
December 21, 2011
OfflineAnd this method would do the generic conversion:
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;
16:06, EET
October 15, 2020
OfflineUpdate:
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;
1 Guest(s)

Log In
Register