XML Nodeset Compiler
--------------------

When writing an application, it is more comfortable to create information models
using GUI tools. Most tools can export data according the OPC UA Nodeset XML
schema. open62541 contains a Python based nodeset compiler that transforms
Nodeset XML files into code for the open62541 SDK.

We take the following information model snippet as the starting point of the
following tutorial. A more detailed tutorial on how to create your own
information model and NodeSet2.xml can be found in this blog post:
https://profanter.medium.com/how-to-create-custom-opc-ua-information-models-1e9a461f5b58

.. code-block:: xml

    <UANodeSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd"
               xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd"
               xmlns:s1="http://yourorganisation.org/example_nodeset/"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <NamespaceUris>
            <Uri>http://yourorganisation.org/example_nodeset/</Uri>
        </NamespaceUris>
        <Aliases>
            <Alias Alias="Boolean">i=1</Alias>
            <Alias Alias="UInt32">i=7</Alias>
            <Alias Alias="String">i=12</Alias>
            <Alias Alias="HasModellingRule">i=37</Alias>
            <Alias Alias="HasTypeDefinition">i=40</Alias>
            <Alias Alias="HasSubtype">i=45</Alias>
            <Alias Alias="HasProperty">i=46</Alias>
            <Alias Alias="HasComponent">i=47</Alias>
            <Alias Alias="Argument">i=296</Alias>
        </Aliases>
        <Extensions>
            <Extension>
                <ModelInfo Tool="UaModeler" Hash="Zs8w1AQI71W8P/GOk3k/xQ=="
                           Version="1.3.4"/>
            </Extension>
        </Extensions>
        <UAReferenceType NodeId="ns=1;i=4001" BrowseName="1:providesInputTo">
            <DisplayName>providesInputTo</DisplayName>
            <References>
                <Reference ReferenceType="HasSubtype" IsForward="false">
                    i=33
                </Reference>
            </References>
            <InverseName Locale="en-US">inputProcidedBy</InverseName>
        </UAReferenceType>
        <UAObjectType IsAbstract="true" NodeId="ns=1;i=1001"
                      BrowseName="1:FieldDevice">
            <DisplayName>FieldDevice</DisplayName>
            <References>
                <Reference ReferenceType="HasSubtype" IsForward="false">
                    i=58
                </Reference>
                <Reference ReferenceType="HasComponent">ns=1;i=6001</Reference>
                <Reference ReferenceType="HasComponent">ns=1;i=6002</Reference>
            </References>
        </UAObjectType>
        <UAVariable DataType="String" ParentNodeId="ns=1;i=1001"
                    NodeId="ns=1;i=6001" BrowseName="1:ManufacturerName"
                    UserAccessLevel="3" AccessLevel="3">
            <DisplayName>ManufacturerName</DisplayName>
            <References>
                <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
                <Reference ReferenceType="HasModellingRule">i=78</Reference>
                <Reference ReferenceType="HasComponent" IsForward="false">
                    ns=1;i=1001
                </Reference>
            </References>
        </UAVariable>
        <UAVariable DataType="String" ParentNodeId="ns=1;i=1001"
                    NodeId="ns=1;i=6002" BrowseName="1:ModelName"
                    UserAccessLevel="3" AccessLevel="3">
            <DisplayName>ModelName</DisplayName>
            <References>
                <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
                <Reference ReferenceType="HasModellingRule">i=78</Reference>
                <Reference ReferenceType="HasComponent" IsForward="false">
                    ns=1;i=1001
                </Reference>
            </References>
        </UAVariable>
        <UAObjectType NodeId="ns=1;i=1002" BrowseName="1:Pump">
            <DisplayName>Pump</DisplayName>
            <References>
                <Reference ReferenceType="HasComponent">ns=1;i=6003</Reference>
                <Reference ReferenceType="HasComponent">ns=1;i=6004</Reference>
                <Reference ReferenceType="HasSubtype" IsForward="false">
                    ns=1;i=1001
                </Reference>
                <Reference ReferenceType="HasComponent">ns=1;i=7001</Reference>
                <Reference ReferenceType="HasComponent">ns=1;i=7002</Reference>
            </References>
        </UAObjectType>
        <UAVariable DataType="Boolean" ParentNodeId="ns=1;i=1002"
                    NodeId="ns=1;i=6003" BrowseName="1:isOn" UserAccessLevel="3"
                    AccessLevel="3">
            <DisplayName>isOn</DisplayName>
            <References>
                <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
                <Reference ReferenceType="HasModellingRule">i=78</Reference>
                <Reference ReferenceType="HasComponent" IsForward="false">
                    ns=1;i=1002
                </Reference>
            </References>
        </UAVariable>
        <UAVariable DataType="UInt32" ParentNodeId="ns=1;i=1002"
                    NodeId="ns=1;i=6004" BrowseName="1:MotorRPM"
                    UserAccessLevel="3" AccessLevel="3">
            <DisplayName>MotorRPM</DisplayName>
            <References>
                <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
                <Reference ReferenceType="HasModellingRule">i=78</Reference>
                <Reference ReferenceType="HasComponent" IsForward="false">
                    ns=1;i=1002
                </Reference>
            </References>
        </UAVariable>
        <UAMethod ParentNodeId="ns=1;i=1002" NodeId="ns=1;i=7001"
                  BrowseName="1:startPump">
            <DisplayName>startPump</DisplayName>
            <References>
                <Reference ReferenceType="HasModellingRule">i=78</Reference>
                <Reference ReferenceType="HasProperty">ns=1;i=6005</Reference>
                <Reference ReferenceType="HasComponent" IsForward="false">
                    ns=1;i=1002
                </Reference>
            </References>
        </UAMethod>
        <UAVariable DataType="Argument" ParentNodeId="ns=1;i=7001" ValueRank="1"
                    NodeId="ns=1;i=6005" ArrayDimensions="1"
                    BrowseName="OutputArguments">
            <DisplayName>OutputArguments</DisplayName>
            <References>
                <Reference ReferenceType="HasModellingRule">i=78</Reference>
                <Reference ReferenceType="HasProperty"
                           IsForward="false">ns=1;i=7001</Reference>
                <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
            </References>
            <Value>
                <ListOfExtensionObject>
                    <ExtensionObject>
                        <TypeId>
                            <Identifier>i=297</Identifier>
                        </TypeId>
                        <Body>
                            <Argument>
                                <Name>started</Name>
                                <DataType>
                                    <Identifier>i=1</Identifier>
                                </DataType>
                                <ValueRank>-1</ValueRank>
                                <ArrayDimensions></ArrayDimensions>
                                <Description/>
                            </Argument>
                        </Body>
                    </ExtensionObject>
                </ListOfExtensionObject>
            </Value>
        </UAVariable>
        <UAMethod ParentNodeId="ns=1;i=1002" NodeId="ns=1;i=7002"
                  BrowseName="1:stopPump">
            <DisplayName>stopPump</DisplayName>
            <References>
                <Reference ReferenceType="HasModellingRule">i=78</Reference>
                <Reference ReferenceType="HasProperty">ns=1;i=6006</Reference>
                <Reference ReferenceType="HasComponent"
                           IsForward="false">ns=1;i=1002</Reference>
            </References>
        </UAMethod>
        <UAVariable DataType="Argument" ParentNodeId="ns=1;i=7002" ValueRank="1"
                    NodeId="ns=1;i=6006" ArrayDimensions="1"
                    BrowseName="OutputArguments">
            <DisplayName>OutputArguments</DisplayName>
            <References>
                <Reference ReferenceType="HasModellingRule">i=78</Reference>
                <Reference ReferenceType="HasProperty" IsForward="false">
                    ns=1;i=7002
                </Reference>
                <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
            </References>
            <Value>
                <ListOfExtensionObject>
                    <ExtensionObject>
                        <TypeId>
                            <Identifier>i=297</Identifier>
                        </TypeId>
                        <Body>
                            <Argument>
                                <Name>stopped</Name>
                                <DataType>
                                    <Identifier>i=1</Identifier>
                                </DataType>
                                <ValueRank>-1</ValueRank>
                                <ArrayDimensions></ArrayDimensions>
                                <Description/>
                            </Argument>
                        </Body>
                    </ExtensionObject>
                </ListOfExtensionObject>
            </Value>
        </UAVariable>
    </UANodeSet>

Take the previous snippet and save it to a file ``myNS.xml``. The file is
compiled using the following function call in the CMake build system::

    ua_generate_nodeset(
        NAME "myNs"
        FILE "${PROJECT_SOURCE_DIR}/examples/nodeset/myNS.xml"
        DEPENDS_TYPES "UA_TYPES"
        DEPENDS_NS "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml
    )

If you look into the files generated by the nodeset compiler, you will see that
it generated a method called ``extern UA_StatusCode myNS(UA_Server *server);``.
You need to include the header and source file and then call ``myNS(server)``
right after creating the server instance with ``UA_Server_new``. This will
automatically add all the nodes to the server and return ``UA_STATUSCODE_GOOD``
if there weren't any errors. Additionally you need to compile the open62541
stack with the full NS0 by setting ``UA_NAMESPACE_ZERO=FULL`` in CMake.
Otherwise the stack uses a subset where many nodes are not included and thus
adding a custom nodeset may fail.

If you also want to generate custom DataTypes for the nodeset, use the CMake
function ``ua_generate_nodeset_and_datatypes``. It uses some best practice
settings and you only need to pass a name and the nodeset files. Passing the
.csv and .bsd files with the datatype information is optional. If not given,
generating datatypes for that nodeset will be skipped. You can also define
dependencies between nodesets using the ``DEPENDS`` argument. Here are some
examples for the ``DI`` and ``PLCOpen`` nodesets::

    # Generate types and namespace for DI
    ua_generate_nodeset_and_datatypes(
        NAME "di"
        FILE_CSV "${UA_NODESET_DIR}/DI/Opc.Ua.Di.NodeIds.csv"
        FILE_BSD "${UA_NODESET_DIR}/DI/Opc.Ua.Di.Types.bsd"
        FILE_NS "${UA_NODESET_DIR}/DI/Opc.Ua.Di.NodeSet2.xml"
    )

    # generate PLCopen namespace which is using DI
    ua_generate_nodeset_and_datatypes(
        NAME "plc"
        # PLCopen does not define custom types. Only generate the nodeset
        FILE_NS "${UA_NODESET_DIR}/PLCopen/Opc.Ua.PLCopen.NodeSet2_V1.02.xml"
        # PLCopen depends on the di nodeset, which must be generated before
        DEPENDS "di"
    )

Manually calling the Nodeset Compiler
.....................................

Besides the CMake macros, the underlying Python code of the Nodeset compiler can
be called directly. The call looks like this:

.. code-block:: bash

    $ python ./nodeset_compiler.py --types-array=UA_TYPES --existing ../../deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml --xml myNS.xml myNS

The output of the command is:

.. code-block:: bash

    INFO:__main__:Preprocessing (existing) ../../deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml
    INFO:__main__:Preprocessing myNS.xml
    INFO:__main__:Generating Code
    INFO:__main__:NodeSet generation code successfully printed

The first argument ``--types-array=UA_TYPES`` defines the name of the global
array in open62541 which contains the corresponding types used within the
nodeset in ``NodeSet2.xml``. If you do not define your own datatypes, you can
always use the ``UA_TYPES`` value. More on that later in this tutorial. The next
argument ``--existing ../../deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml`` points
to the XML definition of the standard-defined namespace 0 (NS0). Namespace 0 is
assumed to be loaded beforehand and provides definitions for data type,
reference types, and so. Since we reference nodes from NS0 in our myNS.xml we
need to tell the nodeset compiler that it should also load that nodeset, but not
compile it into the output. Note that you may need to initialize the git
submodule to get the ``deps/ua-nodeset`` folder (``git submodule update
--init``) or download the full ``NodeSet2.xml`` manually. The argument ``--xml
myNS.xml`` points to the user-defined information model, whose nodes will be
added to the abstract syntax tree. The script will then create the files
``myNS.c`` and ``myNS.h`` (indicated by the last argument ``myNS``) containing
the C code necessary to instantiate those namespaces.

The help command show which additional options are available:

.. code-block:: bash

    $ python ./nodeset_compiler.py -h

Creating object instances
.........................

One of the key benefits of defining object types is being able to create object
instances fairly easily. Object instantiation is handled automatically when the
TypeDefinition NodeId points to a valid ObjectType node. All Attributes and
Methods contained in the objectType definition will be instantiated along with
the object node.

Let's look at an example that will create a pump instance given the newly
defined objectType from myNS.xml:

.. code-block:: c

    /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
     * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */

    #include <signal.h>
    #include <stdio.h>
    #include "open62541.h"

    /* Files myNS.h and myNS.c are created from myNS.xml */
    #include "myNS.h"

    UA_Boolean running = true;

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

    int main(int argc, char **argv) {
        signal(SIGINT, stopHandler);
        signal(SIGTERM, stopHandler);

        UA_Server *server = UA_Server_new();
        UA_ServerConfig_setDefault(UA_Server_getConfig(server));

        UA_StatusCode retval = myNS(server);
        /* Create nodes from nodeset */
        if(retval != UA_STATUSCODE_GOOD) {
            UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
                "Check previous output for any error.");
            retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
        } else {
            UA_NodeId createdNodeId;
            UA_ObjectAttributes object_attr = UA_ObjectAttributes_default;

            object_attr.description = UA_LOCALIZEDTEXT("en-US", "A pump!");
            object_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Pump1");

            // we assume that the myNS nodeset was added in namespace 2.
            // You should always use UA_Server_addNamespace to check what the
            // namespace index is for a given namespace URI. UA_Server_addNamespace
            // will just return the index if it is already added.
            UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(1, 0),
                                    UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                                    UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                                    UA_QUALIFIEDNAME(1, "Pump1"),
                                    UA_NODEID_NUMERIC(2, 1002),
                                    object_attr, NULL, &createdNodeId);


            retval = UA_Server_run(server, &running);
        }

        UA_Server_delete(server);
        return (int) retval;
    }

If you start the server and inspect the nodes, you will find the pump in the
objects folder, which look like this :numref:`nodeset-compiler-pump`.

.. _nodeset-compiler-pump:

.. figure:: nodeset_compiler_pump.png
   :alt: Instantiated Pump Object with inherited children

   Instantiated Pump Object with inherited children

As you can see the pump has inherited its parents attributes (ManufacturerName
and ModelName). Methods, in contrast to objects and variables, are never cloned
but instead only linked. The reason is that you will quite propably attach a
method callback to a central method, not each object. Objects are instantiated
if they are *below* the object you are creating, so any object (like an object
called associatedServer of ServerType) that is part of pump will be instantiated
as well. Objects *above* you object are never instantiated, so the same
ServerType object in Fielddevices would have been omitted (the reason is that
the recursive instantiation function protects itself from infinite recursions,
which are hard to track when first ascending, then redescending into a tree).


Combination of multiple nodesets
................................

In the previous section you have seen how you can use the nodeset compiler with
one single nodeset which depends on the default nodeset (NS0)
``Opc.Ua.NodeSet2.xml``. The nodeset compiler also supports nodesets which
depend on more than one nodeset. We will show this use-case with the PLCopen
nodeset. The PLCopen nodeset ``Opc.Ua.PLCopen.NodeSet2_V1.02.xml`` depends on
the DI nodeset ``Opc.Ua.Di.NodeSet2.xml`` which then depends on NS0. This
example is also shown in ``examples/nodeset/CMakeLists.txt``.

This DI nodeset makes use of some additional data types in
``deps/ua-nodeset/DI/Opc.Ua.Di.Types.bsd``. Since we also need these types
within the generated code, we first need to compile the types into C code. The
generated code is mainly a definition of the binary representation of the types
required for encoding and decoding. The generation can be done using the
``ua_generate_datatypes`` CMake function, which uses the
``tools/generate_datatypes.py`` script::

    ua_generate_datatypes(
        NAME "ua_types_di"
        TARGET_SUFFIX "types-di"
        FILE_CSV "${UA_NODESET_DIR}/DI/Opc.Ua.Di.NodeIds.csv"
        FILES_BSD "${UA_NODESET_DIR}/DI/Opc.Ua.Di.Types.bsd"
    )

``TARGET_SUFFIX`` is used to create a new target with the name
``open62541-generator-TARGET_SUFFIX``.

Now you can compile the DI nodeset XML using the following command::

    ua_generate_nodeset(
        NAME "di"
        FILE "${UA_NODESET_DIR}/DI/Opc.Ua.Di.NodeSet2.xml"
        TYPES_ARRAY "UA_TYPES_DI"
        INTERNAL
        DEPENDS_TYPES "UA_TYPES"
        DEPENDS_NS    "${UA_NODESET_DIR}/Schema/Opc.Ua.NodeSet2.xml"
        DEPENDS_TARGET "open62541-generator-types-di"
    )

There are now two new arguments: ``INTERNAL`` indicates that internal headers
(and non public API) should be included within the generated source code. This
is currently required for nodesets which use structures as data values, and will
probably be fixed in the future. The ``DEPENDS_TYPES`` types array argument is
matched with the nodesets in the same order as they appear on the
``DEPENDS_TARGET`` parameter. It tells the nodeset compiler which types array it
should use: ``UA_TYPES`` for ``Opc.Ua.NodeSet2.xml`` and ``UA_TYPES_DI`` for
``Opc.Ua.Di.NodeSet2.xml``. This is the type array generated by the
``generate_datatypes.py`` script. The rest is similar to the example in previous
section: ``Opc.Ua.NodeSet2.xml`` is assumed to exist already and only needs to
be loaded for consistency checks, ``Opc.Ua.Di.NodeSet2.xml`` will be generated
in the output file ``ua_namespace_di.c/.h``

Next we can generate the PLCopen nodeset. Since it doesn't require any
additional datatype definitions, we can immediately start with the nodeset
compiler command::

    ua_generate_nodeset(
        NAME "plc"
        FILE "${UA_NODESET_DIR}/PLCopen/Opc.Ua.PLCopen.NodeSet2_V1.02.xml"
        INTERNAL
        DEPENDS_TYPES
            "UA_TYPES" "UA_TYPES_DI"
        DEPENDS_NS
            "${UA_NODESET_DIR}/Schema/Opc.Ua.NodeSet2.xml"
            "${UA_NODESET_DIR}/DI/Opc.Ua.Di.NodeSet2.xml"
        DEPENDS_TARGET "open62541-generator-ns-di"
    )

This call is quite similar to the compilation of the DI nodeset. As you can see,
we do not define any specific types array for the PLCopen nodeset. Since the
PLCopen nodeset depends on the NS0 and DI nodeset, we need to tell the nodeset
compiler that these two nodesets should be seen as already existing. Make sure
that the order is the same as in your XML file, e.g., in this case the order
indicated in ``Opc.Ua.PLCopen.NodeSet2_V1.02.xml -> UANodeSet -> Models ->
Model``.

As a result of the previous scripts you will have multiple source files:

* ua_types_di_generated.c
* ua_types_di_generated.h
* ua_types_di_generated_encoding_binary.h
* ua_types_di_generated_handling.h
* ua_namespace_di.c
* ua_namespace_di.h
* ua_namespace_plc.c
* ua_namespace_plc.h

Finally you need to include all these files in your build process and call the
corresponding initialization methods for the nodesets. An example application
could look like this:

.. code-block:: c

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

    /* Create nodes from nodeset */
    UA_StatusCode retval = ua_namespace_di(server);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
                     "Adding the DI namespace failed. Please check previous error output.");
        UA_Server_delete(server);
        return (int)UA_STATUSCODE_BADUNEXPECTEDERROR;
    }

    retval |= ua_namespace_plc(server);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
                     "Adding the PLCopen namespace failed. Please check previous error output.");
        UA_Server_delete(server);
        return (int)UA_STATUSCODE_BADUNEXPECTEDERROR;
    }

    retval = UA_Server_run(server, &running);

Outstanding Companion Spec Issues
.................................

There are some Companion Specifications that currently cannot be compiled with
the Nodeset compiler. Which Companion Specifications are affected and what
causes this is described below.

Safety, Glass, DEXPI
   Do not specify a BSD file or BSD blob in the XML file.
   The BSD file is considered deprecated.
   However, it is currently still required by the Nodeser compiler.

I4AAS, RSL, FDI
   Attempting to load will result in a runtime error
   ("Type-checking failed with error code BadTypeMismatch" or "Parent node not found").

BACnet
   Defines data types whose fields have the names signed or unsigned.
   This leads to errors when creating C structures, because signed and unsigned are keywords in C.

Automatic Nodesetinjection
..........................

The nodesetinjector is a mechanism for automatically loading nodeset/companion
specifications during server initialization. It provides a fast and easy way to
load nodesets in all applications, focusing on the official
OPCFoundation/UANodeset Repository
(https://github.com/OPCFoundation/UA-Nodeset). Specify the required information
models using CMake.

Which nodesets are to be loaded is determined by the Cmake flag
``DUA_INFORMATION_MODEL_AUTOLOAD``. All nodesets that are to be loaded
automatically are listed here. The naming is based on the folder name of the
Companion Specification in the ua-nodeset folder.

A CMake call could look like this.

.. code-block:: bash

    -DCMAKE_BUILD_TYPE=Debug
    -DUA_BUILD_EXAMPLES=ON
    -DUA_INFORMATION_MODEL_AUTOLOAD=DI;POWERLINK;PROFINET;MachineVision
    -DUA_NAMESPACE_ZERO=FULL

The order of nodesets is important! Nodesets that build on other nodesets must
be placed after them in the list. The following nodesets are currently
supported, grouped by dependency depth:

**Level 0 — Standalone (no companion-spec dependency, only ns0):**

AMB, BACnet, CNC, DEXPI, DI, GDS, I4AAS, IOLinkIODD, IREDES,
ISA95-JOBCONTROL, MachineVision, Machinery-Result,
MDIS, MTConnect, OpenSCS, PackML, PROFINET,
Safety, Scheduler, UAFX-Data, WireHarness-VEC, WoT

**Level 1 — Depends on Level-0 specs (typically DI):**

ADI (DI), AutoID (DI), CommercialKitchenEquipment (DI),
CSPPlusForMachine (DI), FDI5 (DI), FDI7 (DI), FDT (DI), IA (DI),
IOLink (DI), Machinery-Jobs (ISA95-JOBCONTROL),
Onboarding (GDS), PADIM (DI),
PlasticsRubber-GeneralTypes (DI), PLCopen (DI),
PNEM (DI), PNENC (DI), PNGSDGM (DI), PNRIO (DI), POWERLINK (DI),
Robotics (DI), Sercos (DI),
UAFX-AC (DI, UAFX-Data), UAFX-CM (DI, UAFX-Data)

**Level 2 — Depends on Level-1 specs (two or more dependency hops):**

Each entry lists its required dependencies in parentheses.

CAS (DI, Machinery, IA),
ECM (DI, IA),
Eumabois (DI, Machinery, Woodworking),
Glass (DI, Machinery),
LADS (DI, AMB, Machinery),
Machinery (DI, IA),
Machinery-Energy (DI, IA, ECM),
Machinery-ProcessValues (DI, PADIM),
MachineTool (DI, Machinery, IA, ISA95-JOBCONTROL, Machinery-Jobs),
Mining-General (DI, IA, Machinery),
PNDRV (DI, PNENC),
PlasticsRubber-TCD (DI, PlasticsRubber-GeneralTypes),
PlasticsRubber-IMM2MES (DI, PlasticsRubber-GeneralTypes),
PlasticsRubber-LDS (DI, PlasticsRubber-GeneralTypes),
PlasticsRubber-HotRunner (DI, PlasticsRubber-GeneralTypes),
Pumps (DI, Machinery),
Scales (DI, Machinery, PackML),
Shotblasting (DI, Machinery, ISA95-JOBCONTROL, Machinery-Jobs),
STGeneralTypes (DI, IA, Machinery, ISA95-JOBCONTROL, Machinery-Jobs),
SurfaceTechnology (DI, IA, Machinery, ISA95-JOBCONTROL, Machinery-Jobs),
TMC (DI, PackML),
Weihenstephan (DI, Machinery, PackML),
Woodworking (DI, Machinery, ISA95-JOBCONTROL, Machinery-Jobs)

**Level 3 — Deep stacks (depends on Level-2 specs):**

Each entry lists its full dependency chain in parentheses,
i.e. every spec that must be loaded before it.

AdditiveManufacturing (DI, IA, Machinery, PADIM, Machinery-ProcessValues, ISA95-JOBCONTROL, Machinery-Jobs, MachineTool),
CuttingTool (DI, IA, Machinery, ISA95-JOBCONTROL, Machinery-Jobs, Machinery-Result, MachineTool, GMS),
GMS (DI, IA, Machinery, ISA95-JOBCONTROL, Machinery-Jobs, Machinery-Result, MachineTool),
IJT (DI, AMB, IA, Machinery, Machinery-Result — loads both IJT/Base and IJT/Tightening),
LaserSystems (DI, IA, Machinery, ISA95-JOBCONTROL, Machinery-Jobs, MachineTool),
MetalForming (DI, IA, Machinery, PADIM, Machinery-ProcessValues, ISA95-JOBCONTROL, Machinery-Jobs, MachineTool),
Mining-Extraction-General (DI, IA, Machinery, Mining-General),
Mining-Extraction-ShearerLoader (DI, IA, Machinery, Mining-General, Mining-Extraction-General),
Mining-Loading-General (DI, IA, Machinery, Mining-General),
Mining-Loading-HydraulicExcavator (DI, IA, Machinery, Mining-General, Mining-Loading-General),
Mining-DevelopmentSupport-General (DI, IA, Machinery, Mining-General),
Mining-DevelopmentSupport-RoofSupportSystem (DI, IA, Machinery, Mining-General, Mining-DevelopmentSupport-General),
Mining-DevelopmentSupport-Dozer (DI, IA, Machinery, Mining-General, Mining-DevelopmentSupport-General),
Mining-TransportDumping-General (DI, IA, Machinery, Mining-General),
Mining-TransportDumping-RearDumpTruck (DI, IA, Machinery, Mining-General, Mining-TransportDumping-General),
Mining-TransportDumping-ArmouredFaceConveyor (DI, IA, Machinery, Mining-General, Mining-TransportDumping-General),
Mining-MineralProcessing-General (DI, IA, Machinery, Mining-General),
Mining-MineralProcessing-RockCrusher (DI, IA, Machinery, Mining-General, Mining-MineralProcessing-General),
Mining-PELOServices-General (DI, IA, Machinery, Mining-General),
Mining-PELOServices-FaceAlignmentSystem (DI, IA, Machinery, Mining-General, Mining-PELOServices-General),
Mining-MonitoringSupervisionServices-General (DI, IA, Machinery, Mining-General),
PAEFS (DI, Machinery, PADIM, Machinery-ProcessValues),
Powertrain (DI, Machinery, UAFX-Data, UAFX-AC),
WMTP (DI, Machinery, PADIM, Machinery-ProcessValues),
PlasticsRubber-Extrusion-GeneralTypes (DI, IA, Machinery, PlasticsRubber-GeneralTypes),
PlasticsRubber-Extrusion-{ExtrusionLine,Extruder,Die,Filter,MeltPump,HaulOff,
Pelletizer,Calender,Calibrator,Corrugator,Cutter}
(DI, IA, Machinery, PlasticsRubber-GeneralTypes, PlasticsRubber-Extrusion-GeneralTypes),
PlasticsRubber-Extrusion_v2-GeneralTypes (DI, IA, Machinery, PlasticsRubber-GeneralTypes),
PlasticsRubber-Extrusion_v2-{ExtrusionLine,Extruder,Die,Filter,MeltPump,HaulOff,
Pelletizer,Calender,Calibrator,Corrugator,Cutter}
(DI, IA, Machinery, PlasticsRubber-GeneralTypes, PlasticsRubber-Extrusion_v2-GeneralTypes),
TTD (DI, IA, Machinery, ISA95-JOBCONTROL, Machinery-Jobs, Machinery-Result, MachineTool),
WireHarness (DI, Machinery, ISA95-JOBCONTROL, Machinery-Jobs, Machinery-Result, WireHarness-VEC)

.. note::

   **PADIM** and **Machinery-ProcessValues** are generated as internal
   helper nodesets (including the IRDI dictionary) when a model that
   requires them is selected.

.. warning::

   Some companion specifications are not yet supported:

   - **GPOS** and **RSL** depend on OPC UA Part 8 spatial types
     (``3DFrame``, ``3DCartesianCoordinates``) and use abstract
     VariableTypes as TypeDefinitions, which are not yet fully
     supported by the open62541 type system.
   - **Glass** has no embedded ``.bsd`` blob, so the compile-time
     nodeset compiler cannot generate ``UA_DataType`` definitions for
     its custom structs. Loading Glass via the **NodesetLoader** at
     runtime works (the loader registers types from ``<Definition>``
     elements dynamically).

   Some companion specifications define types with the same C name in
   different OPC UA namespaces. These cannot be loaded simultaneously:

   - **Pumps** and **PAEFS** both define ``UA_ControlModeEnum``.
   - **TMC** and **PlasticsRubber-GeneralTypes** both define
     ``UA_ControlModeEnumeration`` / ``UA_ProductionStatusEnumeration``.
   - **CommercialKitchenEquipment** and **PlasticsRubber-TCD** both
     define ``UA_OperatingModeEnumeration``.
   - **PlasticsRubber-LDS** and **PlasticsRubber-Extrusion-GeneralTypes**
     both define ``UA_ComponentStatusEnumeration``.
   - **PlasticsRubber-Extrusion** (v1) and **PlasticsRubber-Extrusion_v2**
     share multiple type names and cannot be loaded together.
   - **MachineVision** and **Machinery-Result** both define
     ``ProcessingTimesDataType`` / ``ResultDataType``.
     Choose Machinery-Result when IJT, GMS, or TTD are needed.
   - **PNEM** and **ECM** both define ``AcPeDataType`` /
     ``AcPpDataType`` / ``EnergyStateInformationDataType``.
     Choose ECM when Machinery-Energy is needed.

When the open62541 library is installed on the system, the automatically
generated autoinject library is installed alongside it. Additionally, the header
files for the specified nodesets will be accessible on the system.

Integrating the autoinject library into an existing CMake project is
straightforward. To achieve this, the CMake configuration should include the
following:

.. code-block:: cmake

    set(open62541_LIBRARIES "")
    find_package(open62541 REQUIRED)
    list(APPEND open62541_LIBRARIES open62541::open62541 open62541::autoinject)

The generated namespace header files can also be included and used with the following code:

.. code-block:: c

    #include <autoinject/namespace_di_generated.h>
    #include <autoinject/namespace_amb_generated.h>
    #include <autoinject/namespace_machinery_generated.h>
