nodeset_compiler.rst 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. XML Nodeset Compiler
  2. --------------------
  3. When writing an application, it is more comfortable to create information models using some GUI tools. Most tools can export data according the OPC UA Nodeset XML schema. open62541 contains a python based nodeset compiler that can transform these information model definitions into a working server.
  4. Note that the nodeset compiler you can find in the *tools/nodeset_compiler* subfolder is *not* an XML transformation tool but a compiler. That means that it will create an internal representation when parsing the XML files and attempt to understand and verify the correctness of this representation in order to generate C Code.
  5. Getting started
  6. ...............
  7. 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://opcua.rocks/custom-information-models/
  8. .. code-block:: xml
  9. <UANodeSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  10. xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd"
  11. xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd"
  12. xmlns:s1="http://yourorganisation.org/example_nodeset/"
  13. xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  14. <NamespaceUris>
  15. <Uri>http://yourorganisation.org/example_nodeset/</Uri>
  16. </NamespaceUris>
  17. <Aliases>
  18. <Alias Alias="Boolean">i=1</Alias>
  19. <Alias Alias="UInt32">i=7</Alias>
  20. <Alias Alias="String">i=12</Alias>
  21. <Alias Alias="HasModellingRule">i=37</Alias>
  22. <Alias Alias="HasTypeDefinition">i=40</Alias>
  23. <Alias Alias="HasSubtype">i=45</Alias>
  24. <Alias Alias="HasProperty">i=46</Alias>
  25. <Alias Alias="HasComponent">i=47</Alias>
  26. <Alias Alias="Argument">i=296</Alias>
  27. </Aliases>
  28. <Extensions>
  29. <Extension>
  30. <ModelInfo Tool="UaModeler" Hash="Zs8w1AQI71W8P/GOk3k/xQ=="
  31. Version="1.3.4"/>
  32. </Extension>
  33. </Extensions>
  34. <UAReferenceType NodeId="ns=1;i=4001" BrowseName="1:providesInputTo">
  35. <DisplayName>providesInputTo</DisplayName>
  36. <References>
  37. <Reference ReferenceType="HasSubtype" IsForward="false">
  38. i=33
  39. </Reference>
  40. </References>
  41. <InverseName Locale="en-US">inputProcidedBy</InverseName>
  42. </UAReferenceType>
  43. <UAObjectType IsAbstract="true" NodeId="ns=1;i=1001"
  44. BrowseName="1:FieldDevice">
  45. <DisplayName>FieldDevice</DisplayName>
  46. <References>
  47. <Reference ReferenceType="HasSubtype" IsForward="false">
  48. i=58
  49. </Reference>
  50. <Reference ReferenceType="HasComponent">ns=1;i=6001</Reference>
  51. <Reference ReferenceType="HasComponent">ns=1;i=6002</Reference>
  52. </References>
  53. </UAObjectType>
  54. <UAVariable DataType="String" ParentNodeId="ns=1;i=1001"
  55. NodeId="ns=1;i=6001" BrowseName="1:ManufacturerName"
  56. UserAccessLevel="3" AccessLevel="3">
  57. <DisplayName>ManufacturerName</DisplayName>
  58. <References>
  59. <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
  60. <Reference ReferenceType="HasModellingRule">i=78</Reference>
  61. <Reference ReferenceType="HasComponent" IsForward="false">
  62. ns=1;i=1001
  63. </Reference>
  64. </References>
  65. </UAVariable>
  66. <UAVariable DataType="String" ParentNodeId="ns=1;i=1001"
  67. NodeId="ns=1;i=6002" BrowseName="1:ModelName"
  68. UserAccessLevel="3" AccessLevel="3">
  69. <DisplayName>ModelName</DisplayName>
  70. <References>
  71. <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
  72. <Reference ReferenceType="HasModellingRule">i=78</Reference>
  73. <Reference ReferenceType="HasComponent" IsForward="false">
  74. ns=1;i=1001
  75. </Reference>
  76. </References>
  77. </UAVariable>
  78. <UAObjectType NodeId="ns=1;i=1002" BrowseName="1:Pump">
  79. <DisplayName>Pump</DisplayName>
  80. <References>
  81. <Reference ReferenceType="HasComponent">ns=1;i=6003</Reference>
  82. <Reference ReferenceType="HasComponent">ns=1;i=6004</Reference>
  83. <Reference ReferenceType="HasSubtype" IsForward="false">
  84. ns=1;i=1001
  85. </Reference>
  86. <Reference ReferenceType="HasComponent">ns=1;i=7001</Reference>
  87. <Reference ReferenceType="HasComponent">ns=1;i=7002</Reference>
  88. </References>
  89. </UAObjectType>
  90. <UAVariable DataType="Boolean" ParentNodeId="ns=1;i=1002"
  91. NodeId="ns=1;i=6003" BrowseName="1:isOn" UserAccessLevel="3"
  92. AccessLevel="3">
  93. <DisplayName>isOn</DisplayName>
  94. <References>
  95. <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
  96. <Reference ReferenceType="HasModellingRule">i=78</Reference>
  97. <Reference ReferenceType="HasComponent" IsForward="false">
  98. ns=1;i=1002
  99. </Reference>
  100. </References>
  101. </UAVariable>
  102. <UAVariable DataType="UInt32" ParentNodeId="ns=1;i=1002"
  103. NodeId="ns=1;i=6004" BrowseName="1:MotorRPM"
  104. UserAccessLevel="3" AccessLevel="3">
  105. <DisplayName>MotorRPM</DisplayName>
  106. <References>
  107. <Reference ReferenceType="HasTypeDefinition">i=63</Reference>
  108. <Reference ReferenceType="HasModellingRule">i=78</Reference>
  109. <Reference ReferenceType="HasComponent" IsForward="false">
  110. ns=1;i=1002
  111. </Reference>
  112. </References>
  113. </UAVariable>
  114. <UAMethod ParentNodeId="ns=1;i=1002" NodeId="ns=1;i=7001"
  115. BrowseName="1:startPump">
  116. <DisplayName>startPump</DisplayName>
  117. <References>
  118. <Reference ReferenceType="HasModellingRule">i=78</Reference>
  119. <Reference ReferenceType="HasProperty">ns=1;i=6005</Reference>
  120. <Reference ReferenceType="HasComponent" IsForward="false">
  121. ns=1;i=1002
  122. </Reference>
  123. </References>
  124. </UAMethod>
  125. <UAVariable DataType="Argument" ParentNodeId="ns=1;i=7001" ValueRank="1"
  126. NodeId="ns=1;i=6005" ArrayDimensions="1"
  127. BrowseName="OutputArguments">
  128. <DisplayName>OutputArguments</DisplayName>
  129. <References>
  130. <Reference ReferenceType="HasModellingRule">i=78</Reference>
  131. <Reference ReferenceType="HasProperty"
  132. IsForward="false">ns=1;i=7001</Reference>
  133. <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
  134. </References>
  135. <Value>
  136. <ListOfExtensionObject>
  137. <ExtensionObject>
  138. <TypeId>
  139. <Identifier>i=297</Identifier>
  140. </TypeId>
  141. <Body>
  142. <Argument>
  143. <Name>started</Name>
  144. <DataType>
  145. <Identifier>i=1</Identifier>
  146. </DataType>
  147. <ValueRank>-1</ValueRank>
  148. <ArrayDimensions></ArrayDimensions>
  149. <Description/>
  150. </Argument>
  151. </Body>
  152. </ExtensionObject>
  153. </ListOfExtensionObject>
  154. </Value>
  155. </UAVariable>
  156. <UAMethod ParentNodeId="ns=1;i=1002" NodeId="ns=1;i=7002"
  157. BrowseName="1:stopPump">
  158. <DisplayName>stopPump</DisplayName>
  159. <References>
  160. <Reference ReferenceType="HasModellingRule">i=78</Reference>
  161. <Reference ReferenceType="HasProperty">ns=1;i=6006</Reference>
  162. <Reference ReferenceType="HasComponent"
  163. IsForward="false">ns=1;i=1002</Reference>
  164. </References>
  165. </UAMethod>
  166. <UAVariable DataType="Argument" ParentNodeId="ns=1;i=7002" ValueRank="1"
  167. NodeId="ns=1;i=6006" ArrayDimensions="1"
  168. BrowseName="OutputArguments">
  169. <DisplayName>OutputArguments</DisplayName>
  170. <References>
  171. <Reference ReferenceType="HasModellingRule">i=78</Reference>
  172. <Reference ReferenceType="HasProperty" IsForward="false">
  173. ns=1;i=7002
  174. </Reference>
  175. <Reference ReferenceType="HasTypeDefinition">i=68</Reference>
  176. </References>
  177. <Value>
  178. <ListOfExtensionObject>
  179. <ExtensionObject>
  180. <TypeId>
  181. <Identifier>i=297</Identifier>
  182. </TypeId>
  183. <Body>
  184. <Argument>
  185. <Name>stopped</Name>
  186. <DataType>
  187. <Identifier>i=1</Identifier>
  188. </DataType>
  189. <ValueRank>-1</ValueRank>
  190. <ArrayDimensions></ArrayDimensions>
  191. <Description/>
  192. </Argument>
  193. </Body>
  194. </ExtensionObject>
  195. </ListOfExtensionObject>
  196. </Value>
  197. </UAVariable>
  198. </UANodeSet>
  199. Take the previous snippet and save it to a file ``myNS.xml``. To compile this nodeset into the corresponding C code, which can then be used by the open62541 stack, the nodeset compiler needs some arguments when you call it. The output of the help command gives you the following info:
  200. .. code-block:: bash
  201. $ python ./nodeset_compiler.py -h
  202. usage: nodeset_compiler.py [-h] [-e <existingNodeSetXML>] [-x <nodeSetXML>]
  203. [--internal-headers]
  204. [-b <blacklistFile>] [-i <ignoreFile>]
  205. [-t <typesArray>]
  206. [-v]
  207. <outputFile>
  208. positional arguments:
  209. <outputFile> The path/basename for the <output file>.c and <output
  210. file>.h files to be generated. This will also be the
  211. function name used in the header and c-file.
  212. optional arguments:
  213. -h, --help show this help message and exit
  214. -e <existingNodeSetXML>, --existing <existingNodeSetXML>
  215. NodeSet XML files with nodes that are already present
  216. on the server.
  217. -x <nodeSetXML>, --xml <nodeSetXML>
  218. NodeSet XML files with nodes that shall be generated.
  219. --internal-headers Include internal headers instead of amalgamated header
  220. -b <blacklistFile>, --blacklist <blacklistFile>
  221. Loads a list of NodeIDs stored in blacklistFile (one
  222. NodeID per line). Any of the nodeIds encountered in
  223. this file will be removed from the nodeset prior to
  224. compilation. Any references to these nodes will also
  225. be removed
  226. -i <ignoreFile>, --ignore <ignoreFile>
  227. Loads a list of NodeIDs stored in ignoreFile (one
  228. NodeID per line). Any of the nodeIds encountered in
  229. this file will be kept in the nodestore but not
  230. printed in the generated code
  231. -t <typesArray>, --types-array <typesArray>
  232. Types array for the given namespace. Can be used
  233. mutliple times to define (in the same order as the
  234. .xml files, first for --existing, then --xml) the type
  235. arrays
  236. --max-string-length MAX_STRING_LENGTH
  237. Maximum allowed length of a string literal. If longer,
  238. it will be set to an empty string
  239. -v, --verbose Make the script more verbose. Can be applied up to 4
  240. times
  241. So the resulting call looks like this:
  242. .. code-block:: bash
  243. $ python ./nodeset_compiler.py --types-array=UA_TYPES --existing ../../deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml --xml myNS.xml myNS
  244. And the output of the command:
  245. .. code-block:: bash
  246. INFO:__main__:Preprocessing (existing) ../../deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml
  247. INFO:__main__:Preprocessing myNS.xml
  248. INFO:__main__:Generating Code
  249. INFO:__main__:NodeSet generation code successfully printed
  250. 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.
  251. 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.
  252. 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.
  253. 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.
  254. Although it is possible to run the compiler this way, it is highly discouraged. If you care to examine the CMakeLists.txt (examples/nodeset/CMakeLists.txt), you will find out that the file ``server_nodeset.xml`` is compiled using the following function::
  255. ua_generate_nodeset(
  256. NAME "example"
  257. FILE "${PROJECT_SOURCE_DIR}/examples/nodeset/server_nodeset.xml"
  258. DEPENDS_TYPES "UA_TYPES"
  259. DEPENDS_NS "${UA_FILE_NS0}"
  260. )
  261. 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 the ``myNS(server)`` method 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.
  262. This is how you can use the nodeset compiler to compile simple NodeSet XMLs to be used by the open62541 stack.
  263. For your convenience and for simpler use we also provide a CMake function which simplifies the use of the ``ua_generate_datatypes`` and ``ua_generate_nodeset`` function even more.
  264. It is highly recommended to use this function: ``ua_generate_nodeset_and_datatypes``. It uses some best practice settings and you only need to pass a name, the namespace index ``NAMESPACE_IDX`` (as described above) and the nodeset files.
  265. Passing the .csv and .bsd files is optional and if not given, generating datatypes for that noteset will be skipped. You can also define dependencies between nodesets using the ``DEPENDS`` argument.
  266. Here are some examples for the ``DI`` and ``PLCOpen`` nodesets::
  267. # Generate types and namespace for DI
  268. ua_generate_nodeset_and_datatypes(
  269. NAME "di"
  270. FILE_CSV "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/DI/OpcUaDiModel.csv"
  271. FILE_BSD "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/DI/Opc.Ua.Di.Types.bsd"
  272. NAMESPACE_IDX 2
  273. FILE_NS "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/DI/Opc.Ua.Di.NodeSet2.xml"
  274. )
  275. # generate PLCopen namespace which is using DI
  276. ua_generate_nodeset_and_datatypes(
  277. NAME "plc"
  278. # PLCopen does not define custom types. Only generate the nodeset
  279. FILE_NS "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/PLCopen/Opc.Ua.Plc.NodeSet2.xml"
  280. # PLCopen depends on the di nodeset, which must be generated before
  281. DEPENDS "di"
  282. )
  283. Creating object instances
  284. .........................
  285. 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.
  286. While variables are copied from the objetType definition (allowing the user for example to attach new dataSources to them), methods are always only linked. This paradigm is identical to languages like C++: The method called is always the same piece of code, but the first argument is a pointer to an object. Likewise, in OPC UA, only one methodCallback can be attached to a specific methodNode. If that methodNode is called, the parent objectId will be passed to the method - it is the methods job to derefence which object instance it belongs to in that moment.
  287. Let's look at an example that will create a pump instance given the newly defined objectType from myNS.xml:
  288. .. code-block:: c
  289. /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
  290. * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
  291. #include <signal.h>
  292. #include <stdio.h>
  293. #include "open62541.h"
  294. /* Files myNS.h and myNS.c are created from myNS.xml */
  295. #include "myNS.h"
  296. UA_Boolean running = true;
  297. static void stopHandler(int sign) {
  298. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
  299. running = false;
  300. }
  301. int main(int argc, char **argv) {
  302. signal(SIGINT, stopHandler);
  303. signal(SIGTERM, stopHandler);
  304. UA_ServerConfig *config = UA_ServerConfig_new_default();
  305. UA_Server *server = UA_Server_new(config);
  306. UA_StatusCode retval;
  307. /* create nodes from nodeset */
  308. if (myNS(server) != UA_STATUSCODE_GOOD) {
  309. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
  310. "Check previous output for any error.");
  311. retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
  312. } else {
  313. UA_NodeId createdNodeId;
  314. UA_ObjectAttributes object_attr = UA_ObjectAttributes_default;
  315. object_attr.description = UA_LOCALIZEDTEXT("en-US", "A pump!");
  316. object_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Pump1");
  317. // we assume that the myNS nodeset was added in namespace 2.
  318. // You should always use UA_Server_addNamespace to check what the
  319. // namespace index is for a given namespace URI. UA_Server_addNamespace
  320. // will just return the index if it is already added.
  321. UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(1, 0),
  322. UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
  323. UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
  324. UA_QUALIFIEDNAME(1, "Pump1"),
  325. UA_NODEID_NUMERIC(2, 1002),
  326. object_attr, NULL, &createdNodeId);
  327. retval = UA_Server_run(server, &running);
  328. }
  329. UA_Server_delete(server);
  330. UA_ServerConfig_delete(config);
  331. return (int) retval;
  332. }
  333. Make sure you have updated the headers and libs in your project, then recompile and run the server. Make especially sure you have added ``myNS.h`` to your include folder.
  334. As you can see instantiating an object is not much different from creating an object node. The main difference is that you *must* use an objectType node as typeDefinition.
  335. If you start the server and inspect the nodes with UA Expert, you will find the pump in the objects folder, which look like this :numref:`nodeset-compiler-pump`.
  336. .. _nodeset-compiler-pump:
  337. .. figure:: nodeset_compiler_pump.png
  338. :alt: Instantiated Pump Object with inherited children
  339. Instantiated Pump Object with inherited children
  340. As you can see the pump has inherited it's 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 ommitted (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).
  341. Combination of multiple nodesets
  342. ................................
  343. In 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.Plc.NodeSet2.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``.
  344. 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::
  345. ua_generate_datatypes(
  346. NAME "ua_types_di"
  347. TARGET_SUFFIX "types-di"
  348. NAMESPACE_IDX 2
  349. FILE_CSV "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/DI/OpcUaDiModel.csv"
  350. FILES_BSD "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/DI/Opc.Ua.Di.Types.bsd"
  351. )
  352. The ``NAMESPACE_IDX`` parameter indicates the namespace index of the generated node IDs for the type definitions. Currently we need to rely that the namespace is also added at this position in the final server. There is no automatic inferring yet (pull requests are warmly welcome).
  353. The CSV and BSD files contain the metadata and definition for the types. ``TARGET_SUFFIX`` is used to create a new target with the name ``open62541-generator-TARGET_SUFFIX``.
  354. Now you can compile the DI nodeset XML using the following command::
  355. ua_generate_nodeset(
  356. NAME "di"
  357. FILE "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/DI/Opc.Ua.Di.NodeSet2.xml"
  358. TYPES_ARRAY "UA_TYPES_DI"
  359. INTERNAL
  360. DEPENDS_TYPES "UA_TYPES"
  361. DEPENDS_NS "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml"
  362. DEPENDS_TARGET "open62541-generator-types-di"
  363. )
  364. 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.
  365. 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``
  366. 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::
  367. ua_generate_nodeset(
  368. NAME "plc"
  369. FILE "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/PLCopen/Opc.Ua.Plc.NodeSet2.xml"
  370. INTERNAL
  371. DEPENDS_TYPES
  372. "UA_TYPES" "UA_TYPES_DI"
  373. DEPENDS_NS
  374. "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/Schema/Opc.Ua.NodeSet2.xml"
  375. "${PROJECT_SOURCE_DIR}/deps/ua-nodeset/DI/Opc.Ua.Di.NodeSet2.xml"
  376. DEPENDS_TARGET "open62541-generator-ns-di"
  377. )
  378. 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.Plc.NodeSet2.xml -> UANodeSet -> Models -> Model``.
  379. As a result of the previous scripts you will have multiple source files:
  380. * ua_types_di_generated.c
  381. * ua_types_di_generated.h
  382. * ua_types_di_generated_encoding_binary.h
  383. * ua_types_di_generated_handling.h
  384. * ua_namespace_di.c
  385. * ua_namespace_di.h
  386. * ua_namespace_plc.c
  387. * ua_namespace_plc.h
  388. 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:
  389. .. code-block:: c
  390. UA_ServerConfig *config = UA_ServerConfig_new_default();
  391. UA_Server *server = UA_Server_new(config);
  392. /* create nodes from nodeset */
  393. UA_StatusCode retval = ua_namespace_di(server);
  394. if(retval != UA_STATUSCODE_GOOD) {
  395. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Adding the DI namespace failed. Please check previous error output.");
  396. UA_Server_delete(server);
  397. UA_ServerConfig_delete(config);
  398. return (int)UA_STATUSCODE_BADUNEXPECTEDERROR;
  399. }
  400. retval |= ua_namespace_plc(server);
  401. if(retval != UA_STATUSCODE_GOOD) {
  402. UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Adding the PLCopen namespace failed. Please check previous error output.");
  403. UA_Server_delete(server);
  404. UA_ServerConfig_delete(config);
  405. return (int)UA_STATUSCODE_BADUNEXPECTEDERROR;
  406. }
  407. retval = UA_Server_run(server, &running);