High Performance OPC UA Server SDK
1.5.2.321
|
The SDK contains two tools for address space generation from XML NodeSet file.
To understand the possibilities and restrictions if the SDK's address space model, it is necessary to first understand the OPC UA model and the implemented data structures used to store this information.
The OPC UA Address Space Model consists of an undirected graph of nodes which are connected using typed references. Beside the type information references contain additional meta information like IsAbstract and Symmetric. Also references that have a direction from the OPC UA point of view (Symmetric=true), can be browsed in reverse direction, so from the graph point of view this is still a undirected edge. It is also important to note that this graph can contain cycles.
Important properties of the graph:
Important properties made by the SDK:
This can be best shown if the following simple node model:
Nodes and references are allocated in object pools which are managed by ua_addressspace. For each namespace index there exists a separate pool. This allows to eliminate redundant nsidx information.
A pool can be seen as an array (or table) of a structure (e.g. node or reference). No pointers are used, instead indices into those table entries create the "pointers" to other elements. For this reason the tables are address independent and relocatable in memory.
It is technically possible to store those tables in memory mapped files, or to generate C containing the table information, like it is done by xml2c
.
When loading a binary file the tables are created at runtime with information from the file, thus the tables exit in RAM.
Binary address space files are very compact files compared to their XML counterparts and way more efficient to load. This means it uses just a fraction of the memory required for XML parsing and it is much faster. The file format is described in OPC UA Binary File Format.
Binary address space files can be generated from XML Nodeset files using xml2bin
. See XML2BIN Generator for more information on using xml2bin
.
The uafileformat library (src/uafileformat) contains some test applications.
FileInfo Example:
FileTest Example:
To see if a binary file works and looks like expected in a real server you can use the provided demo server. By default the demo server loads the "demo" model, but by using one or more -m commandline switches you can load other models instead of the "demo" model.
Example: Loading DI Model and another model which requires DI.
Options explained:
Option | Description |
---|---|
-m | Load given information model. |
-d 32 | Enable Notice trace level. This is useful to see what namespaces were loaded. |
-t 0 | Don't wait before shutdown when CTRL-C is pressed. |
At runtime you can load binary files simply calling ua_addressspace_load_file. See Sensor Model Server for an example.
The purpose of generating C code is to reduce the memory requirements of OPC UA Server applications. UA address space can get huge and because they contain a lot of string information, the required memory is too big for many embedded applications.
By generating C code we can move the address space from RAM into ROM, thus reducing the RAM usage.
Note that this is only true for systems that can run code from ROM (e.g. flash memory). Systems that copy all data to RAM before execution cannot benefit from this solution for obvious reasons.
C code can be generated from XML Nodeset files using xml2c
. See XML2C Generator for more information on using xml2c
.
The process data (value attribute) is separated from other node information. This allows to connect dynamic data with static nodes. However it is also possible to define values already in the XML file for constant information like input- and output-arguments of a method.
xml2c
generates a static values store for the variables which contain a value in the XML file. The generated node is connected with the correct store entry at compile time by assigning the according store index and value index.
For all other variables an alternative store index is assigned that you can specify as an command line argument. At runtime you need to create any kind of value store and register it for this store index. This way you can connect static nodes with dynamic data.
See Lesson 2: Custom Provider with Value Stores for a detailed explanation of value stores.
If the whole address space is generated the table model just works as in RAM, because everything is index based. The only difference is that you cannot modify the address space, because it consists of C constants.
There is the typical use case that you have a compiled-in static address spaces for type models like NS0 (OPC UA) and other models like e.g. DI or PLCOpen, but you need to add also nodes at runtime (e.g. instance of such types).
This leads to the problem that you need to add references from compiled-in static nodes to nodes that exist in RAM. But the node tables and reference tables of the source nodes are constants, so this is not possible.
To fix this problem we created the so-called placeholder concept. A static address space can contain - in addition to the static node table and reference table - a further dynamic reference table with placeholder references. Nodes which should be extendable at runtime contain a placeholder reference as last element in the reference list. All the indices from nodes to references, and from the last static reference to the place holder reference are static, and don't need to be changed. The placeholder reference does not have a type or destination node, which marks it as a placeholder. When adding the reference from a static node to a dynamic node the placeholder reference becomes a real reference which points to the newly created dynamic node.
The following image illustrates how the static node "Objects", which contains already a reference to the static node "Server", is connected to the dynamic node "DemoFolder" from NS2 using such a placeholder.
Limitations: When adding references via placeholders this works only for outgoing (forward) references, not for incoming (inverse references). This means if you add e.g. a HasTypeDefinition reference to a static type, this will not be added to the type's inverse reference list. Thus reverse browsing of a type to get all the instances will not work. Because this is not a typical use case must people can live with this limitation.
Because xml2c can generate the C datatypes based on the UA type information and the according type tables for the generic encoder/decoder it makes it very easy for to use custom datatypes.
All you need to do is:
#include
the generated type_table.h
header fileUA_TYPE_TABLE_SORTING_NONE
to ua_type_table_register_const_table. The function will check the table and detects sorting automatically.Of course the application needs to link against th generated information model library, to be able to use the generated types and type tables.
Note: This works with dynamic and static type systems.
Limitations: Currently only numeric TypeIds are supported.