High Performance OPC UA Server SDK  1.5.2.321
XML2C Generator

The xml2c tool can be used to generate static information model files from XML NodeSet files. These models are generated in form of C code which gets compiled into the firmware.

All the node tables, string tables and value tables are generated as C constants, so thats this information can stay in ROM and doesn't need to be copied to RAM at startup.

Invoking xml2c

Calling xml2c works very similar to invoking xml2bin, but requires some additional arguments. To get an overview about the available options call it with option -h, which displays the built-in help.

Usage: xml2c [-h] [-o <outputfile>] [<base xml files...>] <xml file...>
-h: print this help
-V: print version information
-v: more verbose output
-q: more silent output
-s: strip unused type nodes (experimental)
-f <cat>: filter node by category. This option can be specified multiple times.
-o: output directory name
-a: export all namespaces (except ns0)
-d: include description strings in output (default=off)
-i: specifies a SDK nsidx and optional prefix to export (idx[:prefix[:alt_stor_idx]])
-u: specifies a namespace URI and optional prefix to export (uri[;prefix;[alt_store_idx]])
-b: byte string limit in byte (default = 4 MB = 4096 KB = 4194304 Byte)
-n: library name to generate
-p: print addressspace
-P <file>: placeholder file name
-l: default locale (default=auto)
-m: Generate provider skeleton for generated address space.
-M <file>: address mapping file name
<base xml files>: must be specified in correct order, starting with the base model,
followed by models built on top of the previous model.
<xml file>: The information model which gets processed.
Example: Generating code for DI model into folder 'output'.
xml2c -i2:di:12 -v -o output Opc.Ua.NodeSet2.xml Opc.Ua.Di.NodeSet2.xml

Example:

$> mkdir -p output
$> ./xml2c -n di -i2:di:12 -o output Opc.Ua.NodeSet2.xml Opc.Ua.Di.NodeSet2.xml

The main difference to xml2bin is that this tool not only generate the address spaces, but also C Header and Source files for all types defined in the information model. It also creates a CMakeLists.txt which will create a complete C library for that information model. If the model contains datatypes this will get registered automatically at the SDK's generic encoder/decoder, so that the SDK is able to encode/decode those datatypes. This works out-of-the-box, the only thing you need to to in your application is to linked against this library and call the generated function <prefix>_register_static_addressspace.

Options:

Option Description
-n Specifies the library name used in the generated CMakeLists.txt
-i Selects the SDK Index to export, the prefix, and the alternate store index
-o Selects the output folder where the source files should be generated.

The example command above will generate the following files.

$> tree output
output
├── CMakeLists.txt
└── di
├── devicehealthenumeration.c
├── devicehealthenumeration.h
├── fetchresultdatadatatype.c
├── fetchresultdatadatatype.h
├── fetchresulterrordatatype.c
├── fetchresulterrordatatype.h
├── identifier.h
├── ns2.c
├── parameterresultdatatype.c
├── parameterresultdatatype.h
├── type_identifier.h
├── type_table.c
└── type_table.h

The table below describes the purpose of the different generated files.

File/Folder Description
CMakeLists.txt CMake project to compile the 'DI' library.
di/ Folder containing all sources for the DI model.
ns2.c The constant address space tables.
identifier.h Defines with NodeId identifiers.
type_* Type table code for generic encoder/decoder.
*type.[c,h] Generated code for DI datatypes.

The library will contain all the code to for datatypes and the generic encoder/decoder, but does not include ns2.c! This will needs to be included directly by your provider code as shown in the example Sensor Model Server.

The library gets linked with the application and can be used with static address spaces as well as with dynamic address spaces loaded from file.

Default Value Handling

When the XML file contains a <Value> tag for a certain variable, xml2c will add this value to the generated value store (which is a static store) and will connect the variable with this value. The rule for this is that xml2c uses the store_index=nsidx+1 for this (0 is not a valid store index). This means for the SDK NsIdx 2 the store index 3 will get assigned.

Variables with no value will be connected to the "alternate store". You are assigning the alternate store index at the command line like e.g. -i2:demo:12, which means: "Export nsidx 2 with prefix 'demo' and the alternate store index 12". In the code you need to register a store at this index, which can be a writable store. This way values are read- and writable, even though the namespace is static.

Summary: When generating C code you need to specify a 3-tuple separated by ':' for each namespace that should be generated: e.g. -i2:demo:12

Tuple value Meaning
2 SDK NSIdx to export
demo Prefix for generated code
12 The alterante store index. All variables without a <Value> tag will get connected with this store by default.

Address Mapping

When the default value handling is not sufficient, e.g. if you need more than one alternate store, or if you want to make variable values writable, even though a value was already defined in XML, then you can specify an address mapping file using the option -M <file>. This file is a simple plaintext file that contains a list of NodeIds with the address mapping you want to use.

File format properties:

  • All lines with a leading '#' are comments and will be ignored.
  • Valid entries contain a NodeId and an address separated by one or more tab characters '\t'.
  • The NodeId uses the format that is generated by ua_nodeid_snprintf().
  • The address consists of two unsigned integers separated by a colon ':'. The first one is the store index, the second one is the value index.
  • The value index can also be the '*' sign, which will tell xml2c to automatically assign unique value index values.

Example:

ns=2;s=Demo.SimulationSpeed 13:*
ns=2;s=Demo.SimulationActive 13:*

This example (from demoserver) assigns two variables to another store with store index 13, which is used for pointerstore in the demoserver. The value indices are assigned automatically.

To illustrate this the following example ...

ns=2;s=abc 13:*
ns=2;s=def 13:*
ns=2;s=ghi 14:*
ns=2;s=jkl 14:*
ns=2;s=nmo 14:*

... would be equivalent with this manual configuration:

ns=2;s=abc 13:0
ns=2;s=def 13:1
ns=2;s=ghi 14:0
ns=2;s=jkl 14:1
ns=2;s=nmo 14:2

As you can see each store index gets assigned its own unique value index range. The tool can auto-assign value indices for store index 0..255, which should be sufficient for most cases.

Locale Handling

XML NodeSet files contain LocalizedText elements for different languages which can have different formats.

<Description>apple</Description>
<Description Locale="">apple</Description>
<Description Locale="en-US">apple</Description>
<Description Locale="de-DE">Apfel</Description>

Description elements (and other localized texts) with no Locale attribute or empty Locale attribute are treated the same way. Then you might have elements with valid Locale attributes which define what language this string belongs to. xml2c only use one of the specified languages. You can choose the locale to use by specifying the option -l <locale>, e.g. -l en-US. When the option is not specified the default locale is "auto", which is a special value telling xml2c, that it should automatically select the first found valid (non-empty) locale.

Background: Most existing companion specifications don't specify any locale, but the models use English as the language if they are international specifications. Some newer information models specify Locale="en". In both cases the "auto" keyword will extract the strings as expected.

Only when your model contains more than one locale, it might be necessary to specify the locale to use explicitly.

Locale Warnings

UA values in XML may contain locals as XML elements like this:

Invalid locale:

<LocalizedText>
<Locale>
</Locale>
<Text>NORMAL</Text>
</LocalizedText>

In this case xml2c will issue warnings, because the locale is invalid. The example above contains a locale "\n ", which is not a valid locale. xml2c will trim leading and trailing white spaces to make this working as expected, but actually this XML is invalid, because Locale elements are defined as xsd:string which preserves all whitespaces.

The warning will look like this:

warning: removed invalid leading/trailing whitespaces from <Locale> element. This is a workaround for buggy nodeset files. Please fix the nodeset to avoid this warning.
xml location info: The XML element at Opc.Ua.Di.NodeSet2.xml:1427:11 is causing this problem:
<Locale>
^

The location info of the warning tells you exactly where in the XML the invalid locale was found.

Correct would be to remove all white spaces between opening and closing tag, or simply use an empty tag like <Locale/>. You should report such issues to the working group responsible for this model.

Correct locale:

<LocalizedText>
<Locale></Locale>
<Text>NORMAL</Text>
</LocalizedText>

Placeholder Files

If you plan to mix static generated address spaces with dynamic address spaces you need to use placeholder files. A placeholder file is a plaintext file with NodeIds, where xml2c will create placeholder references, which allows you to add references at runtime.

All node tables and reference tables are C constants which cannot be modified at runtime. Placeholder references are located in RAM reference tables, and the last entry of a node's reference list is connected with such a placeholder reference. This allows you to added new references at runtime to nodes, which are actually static. See Mixed Address Space for more information on how this works.

Placeholder example file:

# ObjectsFolder
i=85

See also bin/placeholders.txt for a more realistic example.

File format properties:

  • All lines with a leading '#' are comments and will be ignored.
  • Valid entries contain NodeId strings as generated by ua_nodeid_snprintf().

The placeholder file can be specified using the option -P. This option can be specified multiple times, so that you can put placeholders for each namespace into a separate file.