How to create custom OPC UA Information Models

Stefan Profanter
9 min readAug 28, 2023

--

This post gives you a quick introduction into creating custom OPC UA Information Models.

As explained in my previous post, OPC UA organizes all the nodes inside an address space:

This address space can be extended by custom information models. A Tutorial on how to create your own custom information model is described in this chapter.

Every custom information model or companion specification should be delivered as the official NodeSet2.xml format. This file contains all the nodes and references between the nodes inside this specific information model.

The goal for this tutorial is to come from a mind-model to the final NodeSet2.xml format. That file can then be used to automatically initialize an OPC UA Server which supports loading that file format.

All the files and tools used in this tutorial are also available here:

https://github.com/Pro/opcua-animal-cs

An example OPC UA Server which is using this Information model is shown here:

https://github.com/Pro/opcua-animal-server

The basic pipeline is sketched in the following diagram:

The summarized steps are:

  1. You have an idea
  2. Then you need a tool or you manually write the Model.xml File
  3. Use the UA-ModelCompiler to convert the Model.xml file to especially the transportable NodeSet2.xml Format
  4. In addition to the NodeSet2.xml Format, the model compiler also outputs some more files which may be required by other tools
  5. Load the NodeSet2.xml file into an OPC UA Implementation which supports initialization by NodeSet2.xml files

The following sections lead you through these steps.

First the sketch

Before creating your own information model you should already have a rough idea what information this model should contain.

In this tutorial we want to create our own (simplified) companion specification for animals. This includes defining our own animal types, and even defining instances of animals.

Objects
- Animals
- Cat (Name: Cattie)
- Dog (Name: Wuffy, Weight: 10kg)
ObjectTypes
- AnimalType (Name)
- MammalType (Legs, Sound)
- DogType (Optional weight)
- CatType
DataTypes
- AnimalSoundType (Structure with Verb and URL to Audio file)

In our simplified world, every Animal should have a name. Every Mammal has a specific number of legs, and a sound. For the sound we define a custom datatype which represents a structure with two fields: the sound verb as per the Wikipedia article and a URL to a sound file. For a dog one can optionally also define a weight.

Modeling Best Practices:
The OPC Foundation released a Whitepaper describing best practices for information modelling. It includes naming conventions and other helpful tips:
https://opcfoundation.org/wp-content/uploads/2020/09/OPC-11030-Whitepaper-UA-Modeling-Best-Practices-1.00.00.pdf

Tools to create NodeSet2.xml file

Of course you could just write the NodeSet2.xml file manually. This would be really cumbersome and may lead to invalid and inconsistent node set files. Therefore this is not recommended. Another possible solution would be to just use the provided server API and create all your nodes through the corresponding API calls. This is not a portable solution and should also be avoided.

There are various tools which can be used to create your own NodeSet2.xml file. This section gives a short overview over some of the most common open-source and commercial tools:

Unified Automation UaModeler and Free OPC UA Modeler

UaModeler is a commercial tool which offers a nice GUI to define your own custom nodeset. You can load other nodesets and extend them with your own custom types and instances.

After modeling the nodeset, you can export it to various formats, including the NodeSet2.xml format. The free version is limited to a maximum of 100 nodes.

Playing around with this tool revealed that for simple node sets, this tool can be a good starting point. If your node set involves a lot of inheritance and complex relations between nodes the UaModeler comes to its limitations. In my tests changing the base type of a node lead to invalid child nodes and a broken node set.

Another similar tool, but completely open-source, is the “Free OPC UA Modeler” (https://github.com/FreeOpcUa/opcua-modeler). It is currently work in progress, but the current state looks quite promising.

Beeond UMX Pro — UA Model eXcelerator ProfessionalUA Modeling eXcelerator (UMX)

https://beeond.net/umxpro/

The Beeond UMX Pro Tool is a graphical designing tool which can be used to design OPC UA Models similar to the UaModeler. It also adds adds code generation capability for a number of SDK’s (including Prosys Java, OPCF UANETStandard, Matrikon Flex, OPEN62541, and any other that can launch a CLI).

UA-ModelCompiler

https://github.com/OPCFoundation/UA-ModelCompiler

The OPC Foundation’s model compiler is used by various groups and by the OPC Foundation itself to create the official NodeSet2.xml files for the companion specifications. It is written in C#, but in combination with Mono, it can also be used on Linux.

The corresponding fix for mono can be found here:
https://github.com/OPCFoundation/UA-ModelCompiler/pull/34

This is currently the tool which is most up-to-date and supports all the required features to define a node set.

One major drawback is, that this model compiler does not provide any GUI. You have to write your own Model.xml file manually using a text editor. The UA-ModelCompiler then reads this Model.xml file, checks its consistency and integrity, and then creates the NodeSet2.xml files, including the Types.bsd definition, and NodeId.csv files.

Anyways I strongly recommend to use this approach, since it is the one which supports the most features. In the following sections I explain in more detail how to use the ModelCompiler.

Create your own Model.xml template file

As explained in the previous section, we are using the UA-ModelCompiler tool to get the final NodeSet2.xml file. The UA-ModelCompiler takes a Model.xml file as input. This file has to be manually written for your own model.

Remember, we want to create a node set for our previously presented animal model. Here is the corresponding model file as a starting point. This only includes the boilerplate template which is required for all model.xml files, and defines the Namespace URI for our own model. (See https://opcua.rocks/address-space/ for a more detailed explanation about the Namespace URI)

Note that we already define the resulting Namespace URI in the file as https://opcua.rocks/UA/animal/

<?xml version="1.0" encoding="utf-8"?>
<ModelDesign
xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ua="http://opcfoundation.org/UA/"
xmlns:ANIMAL="https://opcua.rocks/UA/animal/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
TargetNamespace="https://opcua.rocks/UA/animal/"
TargetXmlNamespace="https://opcua.rocks/UA/animal/"
TargetVersion="0.9.0"
TargetPublicationDate="2019-04-01T00:00:00Z"
xmlns="http://opcfoundation.org/UA/ModelDesign.xsd">
<Namespaces>
<Namespace Name="animal" Prefix="animal" XmlNamespace="https://opcua.rocks/UA/animal/Types.xsd" XmlPrefix="animal">https://opcua.rocks/UA/animal/</Namespace>
<Namespace Name="OpcUa" Version="1.03" PublicationDate="2013-12-02T00:00:00Z" Prefix="Opc.Ua" InternalPrefix="Opc.Ua.Server" XmlNamespace="http://opcfoundation.org/UA/2008/02/Types.xsd" XmlPrefix="OpcUa">http://opcfoundation.org/UA/</Namespace>
</Namespaces>
<!-- ### Reference Types ###-->
<!-- ### Object Types ###-->
<!-- ### Variable Types ###-->
<!-- ### Data Types ###-->
<!-- ### Objects ###-->
</ModelDesign>

In the next steps I assume that you are extending this base model file with the corresponding code snippets.

To build your custom Model.xml file and check if you have any errors I recommend to execute the build script from time to time.

All the files used in this tutorial are available on my Github Repository:

https://github.com/Pro/opcua-animal-cs

Add your own nodes to the Model.xml

In the previous section we created the basic model.xml file which we are going to extend in this section.

If you do not know how to write a specific XML construct, you can always look into other Model.xml files, like the one for the DI specification:
https://github.com/OPCFoundation/UA-Nodeset/blob/v1.04/DI/OpcUaDiModel.xml

DataType and VariableType

As shown at the beginning, we want to define our own data type and variables for the sound of an animal. This is achieved by first adding the AnimalSound DataType node. The base type is a ua:Structure since we define our own custom structure.

The datatype has two fields, a Verb as a String (see the Wikipedia article). Also, a AudioFile URL can be set:

<!-- ### Data Types ###-->
<!-- AnimalSound -->
<DataType SymbolicName="ANIMAL:AnimalSound" BaseType="ua:Structure">
<Description>Sound of an animal</Description>
<Fields>
<Field Name="Verb" DataType="ua:String">
<Description>Sound Verb</Description>
</Field>
<Field Name="AudioFile" DataType="ua:String">
<Description>URL to an audio file</Description>
</Field>
</Fields>
</DataType>

This part defines a custom data type, which we could already use for properties.

OPC UA defines two types of Variables: Properties, and Data Variables. A property does not allow to have subnodes, while variables can be extended an can have additional nodes as children. A more detailed explanation is given here:
https://opcua.rocks/address-space/

In our example we want to allow subtypes to extend the animal sound variable with more custom DataVariables, e.g., loudness. Therefore we define a VariableType which is using the custom DataType:

    <!-- AnimalSoundType -->
<VariableType SymbolicName="ANIMAL:AnimalSoundType" DataType="ANIMAL:AnimalSound" BaseType="ua:BaseDataVariableType" ValueRank="Scalar" ExposesItsChildren="true">
<Description>Represents the sound of an animal</Description>
<Children>
<Variable SymbolicName="ANIMAL:Verb" TypeDefinition="ua:BaseDataVariableType" DataType="ua:String" ModellingRule="Mandatory">
<Description>Verb describing the animal sound</Description>
</Variable>
<Variable SymbolicName="ANIMAL:AudioFile" TypeDefinition="ua:BaseDataVariableType" DataType="ua:String" ModellingRule="Optional">
<Description>URL to an audio file for the sound</Description>
</Variable>
</Children>
</VariableType>

Object Types

After defining our custom variable and data types, we add the AnimalType ObjectType node to the Model.xml file. The AnimalType should have a mandatory name property.

    <!-- ### Object Types ###-->
<!-- AnimalType with mandatory name -->
<ObjectType SymbolicName="ANIMAL:AnimalType" BaseType="ua:BaseObjectType" IsAbstract="true" SupportsEvents="true">
<Description>Base type for all animals</Description>
<Children>
<Property SymbolicName="ANIMAL:Name" DataType="ua:String" ValueRank="Scalar" ModellingRule="Mandatory">
<Description>Name of the animal</Description>
</Property>
</Children>
</ObjectType>

We do the same for the MammalType which is a subtype of the AnimalType and is using the AnimalSoundType variable:

    <!-- MammalType subtype of AnimalType with mandatory legs and sound -->
<ObjectType SymbolicName="ANIMAL:MammalType" BaseType="ANIMAL:AnimalType" IsAbstract="true" SupportsEvents="true">
<Description>Base type for all mammals</Description>
<Children>
<Property SymbolicName="ANIMAL:LegCount" DataType="ua:UInt32" ValueRank="Scalar" ModellingRule="Mandatory">
<Description>Number of legs the animal has</Description>
</Property>
<Variable SymbolicName="ANIMAL:Sound" TypeDefinition="ANIMAL:AnimalSoundType" DataType="ANIMAL:AnimalSound" ModellingRule="Optional">
<Description>The sound the animal makes</Description>
</Variable>
</Children>
</ObjectType>

Next, we define the object type for cats and dogs. Note that this time the IsAbstract attribute is not set. Therefore its value is set to false by default, and these types can be used to create instances.

    <!-- CatType as a subtype of a MammalType -->
<ObjectType SymbolicName="ANIMAL:CatType" BaseType="ANIMAL:MammalType" SupportsEvents="true">
<Description>A cat mammal</Description>
</ObjectType>
<!-- DogType as subtype of a MammalType -->
<ObjectType SymbolicName="ANIMAL:DogType" BaseType="ANIMAL:MammalType" SupportsEvents="true">
<Description>A dog mammal</Description>
<Children>
<Property SymbolicName="ANIMAL:Weight" DataType="ua:Double" ValueRank="Scalar" ModellingRule="Optional">
<Description>Weight of the dog in KG</Description>
</Property>
</Children>
</ObjectType>

Object Instances

Now that we have all the base types we need, to create animals, we can start creating instances.

To collect all the instances in one place, we create a AnimalSet sub-folder under the Objects folder. This example also shows how to create references between object nodes:

    <!-- ### Objects ###-->
<Object SymbolicName="ANIMAL:AnimalSet" TypeDefinition="ua:BaseObjectType">
<Description>Contains all instances of animals</Description>
<References>
<Reference IsInverse="true">
<ReferenceType>ua:Organizes</ReferenceType>
<TargetId>ua:ObjectsFolder</TargetId>
</Reference>
</References>
</Object>

Now it’s time to create our first instance of an animal. The snipped below shows the instantiation of a CatType. This should be organized in the previously created AnimalSet folder, and have the name Cattie. Here we do not yet define the optional animal sound child.

    <Object SymbolicName="ANIMAL:Cat" TypeDefinition="ANIMAL:CatType">
<Description>A Cat named Cattie</Description>
<References>
<Reference IsInverse="true">
<ReferenceType>ua:Organizes</ReferenceType>
<TargetId>ANIMAL:AnimalSet</TargetId>
</Reference>
</References>
<Children>
<Property SymbolicName="ANIMAL:Name" DataType="ua:String" AccessLevel="Read">
<DefaultValue>
<uax:String>Cattie</uax:String>
</DefaultValue>
</Property>
<Property SymbolicName="ANIMAL:LegCount" DataType="ua:UInt32" AccessLevel="Read">
<DefaultValue>
<uax:UInt32>4</uax:UInt32>
</DefaultValue>
</Property>
</Children>
</Object>

Similar to the cat example, we can create a dog. This time we set the sound variable and initialize it with a default value:

    <Object SymbolicName="ANIMAL:Dog" TypeDefinition="ANIMAL:DogType">
<Description>A dog named Wuffy</Description>
<References>
<Reference IsInverse="true">
<ReferenceType>ua:Organizes</ReferenceType>
<TargetId>ANIMAL:AnimalSet</TargetId>
</Reference>
</References>
<Children>
<Property SymbolicName="ANIMAL:Name" DataType="ua:String" AccessLevel="Read">
<DefaultValue>
<uax:String>Wuffy</uax:String>
</DefaultValue>
</Property>
<Property SymbolicName="ANIMAL:LegCount" DataType="ua:UInt32" AccessLevel="Read">
<DefaultValue>
<uax:UInt32>4</uax:UInt32>
</DefaultValue>
</Property>
<Variable SymbolicName="ANIMAL:Sound"
DataType="ANIMAL:AnimalSound"
TypeDefinition="ANIMAL:AnimalSoundType"
ModellingRule="Mandatory" ValueRank="Scalar" AccessLevel="Read">
<Description>Sound of the dog</Description>
<DefaultValue>
<uax:ExtensionObject>
<uax:TypeId>
<!-- we can not use ANIMAL:AnimalSound of the DataType here. The model compiler does not replace it.
Therefore we need to use the resulting node id: ns=1;i=15025-->
<uax:Identifier>ns=1;i=15025</uax:Identifier>
</uax:TypeId>
<uax:Body>
<AnimalSound xmlns="http://opcfoundation.org/UA/2008/02/Types.xsd">
<Verb>bark</Verb>
<AudioFile>https://en.wikipedia.org/wiki/File:Barking_of_a_dog_2.ogg</AudioFile>
</AnimalSound>
</uax:Body>
</uax:ExtensionObject>
</DefaultValue>
</Variable>
</Children>
</Object>

That’s it! The full Model.xml file can also be found here:

https://github.com/Pro/opcua-animal-cs/blob/master/animalModel.xml

Creating the NodeSet2.xml file

Now that you have your own Model.xml file. There are basically two ways to compile the Model using the official OPC Foundation UA-ModelCompiler:

  • Linux: Use a precompiled docker container
  • Linux/Windows: Download the UA-ModelCompiler sources and compile the binary yourself. It’s based on .Net

If you are on Linux, I strongly recommend to use the precompiled docker container available on DockerHub: https://hub.docker.com/r/sailavid/ua-modelcompiler

git clone https://github.com/Pro/opcua-animal-cs
cd opcua-animal-cs
docker run \
--mount type=bind,source=$(pwd),target=/model/src \
--entrypoint "/app/PublishModel.sh" \
sailavid/ua-modelcompiler:opcua_rocks_tested \
/model/src/animalModel animal /model/src/Published

This will build the model and copy it into opcua-animal-cs/Published/animal:

  • animal.Classes.cs
  • animal.Constants.cs
  • animal.DataTypes.cs
  • animalModel.csv
  • animalModel.xml
  • animal.NodeSet2.xml
  • animal.NodeSet.xml
  • animal.PredefinedNodes.uanodes
  • animal.PredefinedNodes.xml
  • animal.Types.xsd
  • animal.Types.bsd

To manually compile the model compiler binaries, please have a look into the corresponding Readme section:

https://github.com/Pro/opcua-animal-cs#linux-command-line

If you have issues creating the files in the Published folder, you can also use the pre-compiled files from the example repository:

https://github.com/Pro/opcua-animal-cs/tree/master/Published/animal

The next step is to use the generated NodeSet2.xml in any supported stack or modeler. E.g. you could open the NodeSet2.xml file using UaModeler.

The open source OPC UA Stack open62541 also supports reading NodeSet2.xml format and can initialize its address space with the predefined nodes. With this you can easily start an OPC UA server offering your custom nodeset.

A more detailed tutorial on how to achieve this will be added soon in a separate post. As a quick-start you can have a look at the prepared repository:

https://github.com/Pro/opcua-animal-server

This post was migrated from https://opcua.rocks/

--

--

Stefan Profanter

I love to program and write code in various Programming Languages and am now focusing on Automating as much as Possible.