{"id":5530,"date":"2023-09-14T08:53:37","date_gmt":"2023-09-14T06:53:37","guid":{"rendered":"https:\/\/www.basyskom.de\/?p=5530"},"modified":"2024-10-08T11:17:27","modified_gmt":"2024-10-08T09:17:27","slug":"generic-struct-handling-is-coming-to-qt-opc-ua","status":"publish","type":"post","link":"https:\/\/www.basyskom.de\/en\/generic-struct-handling-is-coming-to-qt-opc-ua\/","title":{"rendered":"Generic Struct Handling is Coming to Qt OPC UA"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"5530\" class=\"elementor elementor-5530\" data-elementor-post-type=\"post\">\n\t\t\t\t<div class=\"elementor-element elementor-element-c9c3aa6 e-con-full e-flex wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"c9c3aa6\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-41f4515 elementor-widget elementor-widget-heading\" data-id=\"41f4515\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Generic Structs in OPC UA<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e2cb390 elementor-widget elementor-widget-text-editor\" data-id=\"e2cb390\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The OPC UA data type system is hierarchically organized and enables its users to model custom structured data types and enumerations to represent data in the optimal way.\u00a0\u00a0<\/p>\n<p>OPC UA clients and servers mostly use the\u00a0<em><a href=\"https:\/\/reference.opcfoundation.org\/Core\/Part6\/v105\/docs\/5.2\" target=\"_blank\" rel=\"noopener\">Binary Data Encoding<\/a><\/em>\u00a0defined in Part 6 of the OPC UA specification for data exchange. As its main design goal was a small size of the encoded data, it does not contain any information about data types and field names which means that a decoder must know the type and structure of the data it is going to decode.<\/p>\n<p>The basic building blocks of the OPC UA type system are the so-called built-in types where\u00a0<a href=\"https:\/\/reference.opcfoundation.org\/Core\/Part6\/v105\/docs\/5.2.2\" target=\"_blank\" rel=\"noopener\">explicit encoding rules<\/a>\u00a0are available in Part 6. They can be used to form structured data types following a <a href=\"https:\/\/reference.opcfoundation.org\/Core\/Part6\/v105\/docs\/5.2.6\" target=\"_blank\" rel=\"noopener\">set of rules<\/a>:<\/p>\n<ul>\n<li>Structure fields are serialized in the order they appear in the structure<\/li>\n<li>Arrays are serialized as an\u00a0<em>Int32<\/em>\u00a0containing the array length followed by all array elements<\/li>\n<li>Multi-dimensional arrays are prefixed by the array dimensions array serialized as previously described<\/li>\n<li>Structs derived from\u00a0<em><a href=\"https:\/\/reference.opcfoundation.org\/Core\/Part6\/v105\/docs\/5.2.8\" target=\"_blank\" rel=\"noopener\">Union<\/a><\/em>\u00a0have their switch field serialized first, then the only member contained in the union (if any) is serialized<\/li>\n<li>Structs with <a href=\"https:\/\/reference.opcfoundation.org\/Core\/Part6\/v105\/docs\/5.2.7\" target=\"_blank\" rel=\"noopener\">optional fields<\/a> start with a UInt32 which contains a bit mask. Each optional field is assigned one bit in the bit mask with the first optional field corresponding to the first bit and so on. An optional field is only serialized if the corresponding bit it the bit mask is set.<\/li>\n<\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5d5d75f elementor-widget elementor-widget-text-editor\" data-id=\"5d5d75f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Due to the definitions above, the decoding must be done in a multi-step process. For example, the switch field or bit mask must be decoded to know which fields are really present in the serialized data and for array fields, the array length must be decoded to determine the number of array members to deserialize. So far the decoding logic had to be programmed manually based on external knowledge of the types involved (for example the XML based data type description).<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e891f6a elementor-widget elementor-widget-heading\" data-id=\"e891f6a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Type Descriptions<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-09b5ad7 elementor-widget elementor-widget-text-editor\" data-id=\"09b5ad7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Since OPC UA 1.04, the OPC UA node class DataType has the optional attribute <em><a href=\"https:\/\/reference.opcfoundation.org\/Core\/Part3\/v105\/docs\/5.8.3\" target=\"_blank\" rel=\"noopener\">DataTypeDescription<\/a><\/em>\u00a0which contains a\u00a0<a href=\"https:\/\/reference.opcfoundation.org\/Core\/Part3\/v105\/docs\/8.48\" target=\"_blank\" rel=\"noopener\"><em>StructureDefinition<\/em><\/a> value for types derived from <em>Structure<\/em>\u00a0or<em>\u00a0Union<\/em>\u00a0and an\u00a0<em><a href=\"https:\/\/reference.opcfoundation.org\/Core\/Part3\/v105\/docs\/8.50\" target=\"_blank\" rel=\"noopener\">EnumDefinition<\/a><\/em>\u00a0value for types derived from\u00a0<em>Enumeration<\/em>.<\/p>\n<p>The StructureDefinition type contains all necessary information to decode a structured type:<\/p>\n<ul>\n<li>The default encoding node id which is also present in an extension object containing a serialized value of that type<\/li>\n<li>The node id of the direct super type<\/li>\n<li>An enumerated value describing if the structure is a Union or if it has optional fields<\/li>\n<li>A list of structure fields with name, description, data type, value rank, array dimensions and a flag indicating if the field is optional. While all enums are encoded as an <em>Int32<\/em>\u00a0and no additional information is required to deserialize them, the\u00a0<em>EnumDefinition<\/em>\u00a0type contains a list of all possible enum values together with their symbolic names.<\/li>\n<\/ul>\n<p>A client that knows the set of explicit encoding rules mentioned above to decode all built-in types and how to interpret the <em>DataTypeDescription<\/em> attribute will be able to decode and encode all custom data types exposed by a server, even if it has never seen them before.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f934de3 elementor-widget elementor-widget-heading\" data-id=\"f934de3\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">New Classes in Qt OPC UA<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8b919cf elementor-widget elementor-widget-text-editor\" data-id=\"8b919cf\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The <em>dev<\/em> branch of the Qt OPC UA module has been extended with six new classes to support generic type decoding and encoding:<\/p>\n<ul>\n<li style=\"list-style-type: none;\">\n<ul><\/ul>\n<\/li>\n<li><em>QOpcUaStructureDefinition<\/em> and <em>QOpcUaStructureField<\/em> are returned for reading the <em>DataTypeDescription<\/em> attribute of a <em>Structure<\/em> DataType node<\/li>\n<\/ul>\n<ul>\n<li><em>QOpcUaEnumDefinition<\/em> and <em>QOpcUaEnumField<\/em> are returned for reading the <em>DataTypeDescription<\/em> attribute of an <em>Enumeration<\/em> DataType node<\/li>\n<\/ul>\n<ul>\n<li>QOpcUaGenericStructHandler is responsible for traversing the data type hierarchy of a server and encoding and decoding of generic structs<\/li>\n<\/ul>\n<ul>\n<li><em>QOpcUaGenericStructValue<\/em> is the data class which stores a decoded value generated by <em>QOpcUaGenericStructHandler<\/em> and can also be used to pass data to <em>QOpcUaGenericStructHandler&#8217;s<\/em> encoding method. It contains type information and a map of structure field names to values. For structures with optional fields and unions, only the fields contained in the serialized value are present in the map. For nested custom structs, the value is again of type <em>QOpcUaGenericStructValue<\/em>.<\/li>\n<\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f68b870 elementor-widget elementor-widget-heading\" data-id=\"f68b870\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">How to use QOpcUaGenericStructHandler<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-cc74ad6 elementor-widget elementor-widget-text-editor\" data-id=\"cc74ad6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p><em>QOpcUaGenericStructHandler<\/em> requires a connected client for initialization. It starts with the well-known node id of <em>BaseDataType<\/em> and traverses the entire data type hierarchy. The gathered information is then organized in internal data structures that represent the hierarchical relationships of all available data types and the necessary information to decode them.<\/p>\n<p>In addition to the type descriptions gathered from the server, it is also possible to add custom type descriptions to enable encoding and decoding for servers that don&#8217;t expose the <em>DataTypeDescription<\/em> attribute.<\/p>\n<p>The following example showcases all features of the generic struct handler.<\/p>\n<p>Our project requires Qt Core and Qt OPC UA. The <em>CMakeLists.txt<\/em> finds them for us automatically and builds the demo executable:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f6115f9 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"f6115f9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-markup'>project(GenericStructBlog LANGUAGES CXX)\n\nset(CMAKE_AUTOMOC ON)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\nfind_package(Qt6 REQUIRED COMPONENTS Core OpcUa)\n\nadd_executable(GenericStructBlog\n  main.cpp\n)\ntarget_link_libraries(GenericStructBlog Qt6::Core Qt6::OpcUa) <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fe69198 elementor-widget elementor-widget-text-editor\" data-id=\"fe69198\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>First, we create a test class which establishes a connection to the <a href=\"https:\/\/code.qt.io\/cgit\/qt\/qtopcua.git\/tree\/tests\/open62541-testserver\" target=\"_blank\" rel=\"noopener\">Qt OPC UA open62541 test server<\/a>, initializes the generic struct handler and reads the value attributes of several test nodes with custom struct values. The structured types of these nodes were modelled to show interesting features like nested structs, optional fields and unions.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b7d03b7 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"b7d03b7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>#include &lt;QCoreApplication&gt;\n\n#include &lt;QOpcUaClient&gt;\n#include &lt;QOpcUaGenericStructHandler&gt;\n#include &lt;QOpcUaProvider&gt;\n\nclass GenericStructHandlerExample : public QObject {\n    Q_OBJECT\n\npublic:\n    bool start();\n\nsignals:\n    void done();\n\nprivate:\n    void handleEndpointsRequestFinished(const QList&lt;QOpcUaEndpointDescription&gt; &amp;endpoints,\n                                       QOpcUa::UaStatusCode statusCode, const QUrl &amp;requestUrl);\n    void handleConnected();\n    void handleInitializeFinished(bool success);\n    void decoderDemo(const QList&lt;QOpcUaReadResult&gt; &amp;results, QOpcUa::UaStatusCode serviceResult);\n    void encoderDemo();\n    void customTypeDescriptionDemo();\n\n    QScopedPointer&lt;QOpcUaClient&gt; m_client;\n    QScopedPointer&lt;QOpcUaGenericStructHandler&gt; m_handler;\n};\n\n#include &lt;main.moc&gt;\n\nint main(int argc, char *argv[])\n{\n    QCoreApplication a(argc, argv);\n\n    GenericStructHandlerExample handler;\n    QObject::connect(&amp;handler, &amp;GenericStructHandlerExample::done, &amp;a, &amp;QCoreApplication::quit);\n\n    handler.start();\n\n    return a.exec();\n}\n\nbool GenericStructHandlerExample::start()\n{\n    m_client.reset(QOpcUaProvider().createClient(&quot;open62541&quot;));\n\n    if (!m_client) {\n        emit done();\n        return false;\n    }\n\n    QObject::connect(m_client.get(), &amp;QOpcUaClient::endpointsRequestFinished, this,\n                     &amp;GenericStructHandlerExample::handleEndpointsRequestFinished);\n    return m_client-&gt;requestEndpoints(QUrl(QStringLiteral(&quot;opc.tcp:\/\/127.0.0.1:43344&quot;)));\n}\n\nvoid GenericStructHandlerExample::handleEndpointsRequestFinished(const QList&lt;QOpcUaEndpointDescription&gt; &amp;endpoints,\n                                                                QOpcUa::UaStatusCode statusCode, const QUrl &amp;requestUrl)\n{\n    if (statusCode != QOpcUa::UaStatusCode::Good || endpoints.isEmpty()) {\n        emit done();\n        return;\n    }\n\n    QObject::connect(m_client.get(), &amp;QOpcUaClient::connected,\n                     this, &amp;GenericStructHandlerExample::handleConnected);\n    m_client-&gt;connectToEndpoint(endpoints.first());\n}\n\nvoid GenericStructHandlerExample::handleConnected()\n{\n    m_handler.reset(new QOpcUaGenericStructHandler(m_client.get()));\n    QObject::connect(m_handler.get(), &amp;QOpcUaGenericStructHandler::initializeFinished,\n                     this, &amp;GenericStructHandlerExample::handleInitializeFinished);\n    m_handler-&gt;initialize();\n}\n\nvoid GenericStructHandlerExample::handleInitializeFinished(bool success)\n{\n    if (!success) {\n        qWarning() &lt;&lt; &quot;Failed to initialize generic struct handler&quot;;\n        emit done();\n        return;\n    }\n\n    QObject::connect(m_client.get(), &amp;QOpcUaClient::readNodeAttributesFinished,\n                     this, &amp;GenericStructHandlerExample::decoderDemo);\n    m_client-&gt;readNodeAttributes({\n        \/\/ Nested struct node on the test server\n        QOpcUaReadItem(QStringLiteral(&quot;ns=4;i=6009&quot;)),\n        \/\/ Union node on the test server with first member set\n        QOpcUaReadItem(QStringLiteral(&quot;ns=4;i=6011&quot;)),\n        \/\/ Union node on the test server with second member set\n        QOpcUaReadItem(QStringLiteral(&quot;ns=4;i=6003&quot;)),\n        \/\/ Struct node with optional field which is specified on the test server\n        QOpcUaReadItem(QStringLiteral(&quot;ns=4;i=6010&quot;)),\n        \/\/ Struct node with optional field which is not specified on the test server\n        QOpcUaReadItem(QStringLiteral(&quot;ns=4;i=6002&quot;))\n    });\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b233cff elementor-widget elementor-widget-text-editor\" data-id=\"b233cff\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Now we attempt to decode the binary-encoded extension objects we received from the server. Each extension object is passed to the <em>QOpcUaGenericStructHandler::decode()<\/em> method and the resulting <em>QOpcUaGenericStructValue<\/em> is printed to the terminal:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-385e6a7 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"385e6a7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>void GenericStructHandlerExample::decoderDemo(const QList&lt;QOpcUaReadResult&gt; &amp;results, QOpcUa::UaStatusCode serviceResult)\n{\n    if (serviceResult != QOpcUa::UaStatusCode::Good) {\n        qWarning() &lt;&lt; &quot;Failed to read&quot;;\n        emit done();\n        return;\n    }\n\n    qDebug() &lt;&lt; &quot;\\n&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&quot;;\n    qDebug() &lt;&lt; &quot;&gt; Decode and print values read from the server &lt;&quot;;\n    qDebug() &lt;&lt; &quot;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&quot;;\n\n    for (const auto &amp;result : results) {\n        if (result.statusCode() != QOpcUa::UaStatusCode::Good) {\n            qWarning() &lt;&lt; &quot;Failed to read value for node&quot; &lt;&lt; result.nodeId() &lt;&lt; result.statusCode();\n            continue;\n        }\n\n        \/\/ Get the extension object that was read from the server\n        auto extensionObject = result.value().value&lt;QOpcUaExtensionObject&gt;();\n        qDebug() &lt;&lt; &quot;\\nRead encoded value of type&quot; &lt;&lt;\n            m_handler-&gt;typeNameForBinaryEncodingId(extensionObject.encodingTypeId()) &lt;&lt;\n            &quot;for node&quot; &lt;&lt; result.nodeId();\n        \/\/ Attempt to decode and check success\n        bool success = false;\n        auto decodedValue = m_handler-&gt;decode(extensionObject, success);\n\n        if (!success) {\n            qWarning() &lt;&lt; &quot;Failed to decode custom struct value for node&quot; &lt;&lt; result.nodeId();\n            emit done();\n            continue;\n        }\n\n        \/\/ Output via built-in debug stream operator\n        qDebug() &lt;&lt; &quot;Decoded value:&quot; &lt;&lt; decodedValue;\n    }\n\n    encoderDemo();\n    customTypeDescriptionDemo();\n\n    emit done();\n}\n <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0902950 elementor-widget elementor-widget-text-editor\" data-id=\"0902950\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The next method shows the usage of the <em>QOpcUaGenericStructHandler::encode()<\/em> method. We use the <em>QOpcUaGenericStructHandler::createGenericStructValueForTypeId()<\/em> helper to create a pre-filled <em>QOpcUaGenericStructValue<\/em> where only the fields and their values have to be inserted. After successfully encoding the values to binary-encoded extension objects, we decode these again to print the decoded data to the terminal.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7526a88 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"7526a88\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>void GenericStructHandlerExample::encoderDemo()\n{\n    QList&lt;QOpcUaGenericStructValue&gt; values;\n\n    qDebug() &lt;&lt; &quot;\\n&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&quot;;\n    qDebug() &lt;&lt; &quot;&gt; Demonstrate the encoding of generic struct values &lt;&quot;;\n    qDebug() &lt;&lt; &quot;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\\n&quot;;\n\n    \/\/ Add an instance of the optional field struct without the optional field specified\n    auto optionalFieldStruct = m_handler-&gt;createGenericStructValueForTypeId(QStringLiteral(&quot;ns=4;i=3006&quot;));\n    optionalFieldStruct.fieldsRef()[&quot;MandatoryMember&quot;] = 23.0;\n    values.push_back(optionalFieldStruct);\n\n    \/\/ Add an instance of the union struct with the second member specified\n    auto unionStruct = m_handler-&gt;createGenericStructValueForTypeId(QStringLiteral(&quot;ns=4;i=3005&quot;));\n    auto innerStruct = m_handler-&gt;createGenericStructValueForTypeId(QStringLiteral(&quot;ns=4;i=3004&quot;));\n    innerStruct.fieldsRef()[&quot;DoubleSubtypeMember&quot;] = 42.0;\n    unionStruct.fieldsRef()[&quot;Member2&quot;] = innerStruct;\n    values.push_back(unionStruct);\n\n    \/\/ Encode the values as extension object\n    \/\/ The resulting extension objects could be written into the value attribute\n    \/\/ of a node of the corresponding struct type on the server\n    QList&lt;QOpcUaExtensionObject&gt; encodedValues;\n    for (const auto &amp;value : values) {\n        QOpcUaExtensionObject obj;\n        const auto success = m_handler-&gt;encode(value, obj);\n        if (!success) {\n            qWarning() &lt;&lt; &quot;Failed to encode generic struct value&quot;;\n            continue;\n        }\n\n        qDebug() &lt;&lt; &quot;Serialized data:&quot; &lt;&lt; obj.encodedBody();\n        encodedValues.push_back(obj);\n    }\n\n    \/\/ Decode and print result\n    for (const auto &amp;extensionObject : encodedValues) {\n        qDebug() &lt;&lt; &quot;\\nRead encoded value of type&quot; &lt;&lt;\n            m_handler-&gt;typeNameForBinaryEncodingId(extensionObject.encodingTypeId());\n        \/\/ Attempt to decode and check success\n        bool success = false;\n        auto decodedValue = m_handler-&gt;decode(extensionObject, success);\n\n        if (!success) {\n            qWarning() &lt;&lt; &quot;Failed to decode custom struct value&quot;;\n            emit done();\n            continue;\n        }\n\n        \/\/ Output via built-in debug stream operator\n        qDebug() &lt;&lt; &quot;Decoded value:&quot; &lt;&lt; decodedValue;\n    }\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6dc34c2 elementor-widget elementor-widget-text-editor\" data-id=\"6dc34c2\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>To finish our example, there is a demonstration on how to add manually-created QOpcUaStructureDefinition objects to the handler to enable decoding of types the server did not expose a structure definition for. For real world usage, the type id and default encoding id for such a type must correspond to the values from the server the client is connected to.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-91b7711 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"91b7711\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-cpp'>void GenericStructHandlerExample::customTypeDescriptionDemo()\n{\n    qDebug() &lt;&lt; &quot;\\n&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&quot;;\n    qDebug() &lt;&lt; &quot;&gt; Demonstrate the encoding and decoding with custom descriptions &lt;&quot;;\n    qDebug() &lt;&lt; &quot;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\\n&quot;;\n\n    \/\/ Invented node ids, this should correspond to the real ids on a server\n    const auto customTypeId = &quot;ns=2;i=1234&quot;;\n    const auto customTypeEncodingId = &quot;ns=2;i=1235&quot;;\n\n    QOpcUaStructureDefinition customDefinition;\n    customDefinition.setStructureType(QOpcUaStructureDefinition::StructureType::StructureWithOptionalFields);\n    customDefinition.setBaseDataType(QOpcUa::namespace0Id(QOpcUa::NodeIds::Namespace0::Structure));\n    customDefinition.setDefaultEncodingId(customTypeEncodingId);\n\n    QOpcUaStructureField mandatoryField;\n    mandatoryField.setDataType(QOpcUa::namespace0Id(QOpcUa::NodeIds::Namespace0::Int32));\n    mandatoryField.setValueRank(-1);\n    mandatoryField.setName(&quot;MyCustomMandatoryField&quot;);\n\n    QOpcUaStructureField optionalField;\n    optionalField.setDataType(QOpcUa::namespace0Id(QOpcUa::NodeIds::Namespace0::String));\n    optionalField.setValueRank(-1);\n    optionalField.setName(&quot;MyCustomOptionalField&quot;);\n\n    customDefinition.setFields({ mandatoryField, optionalField });\n\n    auto success = m_handler-&gt;addCustomStructureDefinition(customDefinition,\n                                                           customTypeId,\n                                                           &quot;MyCustomTypeWithOptionalField&quot;);\n\n    if (!success) {\n        qWarning() &lt;&lt; &quot;Failed to add custom structure definition&quot;;\n        return;\n    }\n\n    auto value = m_handler-&gt;createGenericStructValueForTypeId(customTypeId);\n\n    value.fieldsRef()[&quot;MyCustomMandatoryField&quot;] = 23;\n    value.fieldsRef()[&quot;MyCustomOptionalField&quot;] = QStringLiteral(&quot;My test string&quot;);\n\n    QOpcUaExtensionObject obj;\n    success = m_handler-&gt;encode(value, obj);\n\n    if (!success) {\n        qWarning() &lt;&lt; &quot;Failed to encode custom structure&quot;;\n        return;\n    }\n\n    qDebug() &lt;&lt; &quot;Serialized data:&quot; &lt;&lt; obj.encodedBody();\n\n    const auto decodedValue = m_handler-&gt;decode(obj, success);\n\n    if (!success) {\n        qWarning() &lt;&lt; &quot;Failed to decode custom structure&quot;;\n        return;\n    }\n\n    qDebug() &lt;&lt; &quot;Decoded value:&quot; &lt;&lt; decodedValue;\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a76ab0b elementor-widget elementor-widget-text-editor\" data-id=\"a76ab0b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>After successful initialization, the following output is produced by the example:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1d085e5 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"1d085e5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-solid'>&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;\n&gt; Decode and print values read from the server &lt;\n&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\n\nRead encoded value of type &quot;QtTestStructType&quot; for node &quot;ns=4;i=6009&quot;\nDecoded value: Struct QtTestStructType (QualifiedNameMember: QVariant(QOpcUaQualifiedName, QOpcUaQualifiedname(1, &quot;TestName&quot;)) NestedStructMember: QVariant(QOpcUaGenericStructValue, Struct QtInnerTestStructType (DoubleSubtypeMember: QVariant(double, 42))) EnumMember: QVariant(int, 1) LocalizedTextMember: QVariant(QOpcUaLocalizedText, QOpcUaLocalizedText(&quot;en&quot;, &quot;TestText&quot;)) Int64ArrayMember: QVariant(QList&lt;qlonglong&gt;, QList(9223372036854775807, 9223372036854775806, -9223372036854775808)) NestedStructArrayMember: QVariant(QList&lt;QOpcUaGenericStructValue&gt;, QList(Struct QtInnerTestStructType (DoubleSubtypeMember: QVariant(double, 23)), Struct QtInnerTestStructType (DoubleSubtypeMember: QVariant(double, 42)))) StringMember: QVariant(QString, TestString))\n\nRead encoded value of type &quot;QtTestUnionType&quot; for node &quot;ns=4;i=6011&quot;\nDecoded value: Union QtTestUnionType (Member1: QVariant(qlonglong, 42))\n\nRead encoded value of type &quot;QtTestUnionType&quot; for node &quot;ns=4;i=6003&quot;\nDecoded value: Union QtTestUnionType (Member2: QVariant(QOpcUaGenericStructValue, Struct QtInnerTestStructType (DoubleSubtypeMember: QVariant(double, 23))))\n\nRead encoded value of type &quot;QtStructWithOptionalFieldType&quot; for node &quot;ns=4;i=6010&quot;\nDecoded value: StructWithOptionalFields QtStructWithOptionalFieldType (MandatoryMember: QVariant(double, 42) OptionalMember: QVariant(double, 23))\n\nRead encoded value of type &quot;QtStructWithOptionalFieldType&quot; for node &quot;ns=4;i=6002&quot;\nDecoded value: StructWithOptionalFields QtStructWithOptionalFieldType (MandatoryMember: QVariant(double, 42))\n\n&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;\n&gt; Demonstrate the encoding of generic struct values &lt;\n&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\n\nSerialized data: &quot;\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00&quot;&quot;7@&quot;\nSerialized data: &quot;\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00&quot;&quot;E@&quot;\n\nRead encoded value of type &quot;QtStructWithOptionalFieldType&quot;\nDecoded value: StructWithOptionalFields QtStructWithOptionalFieldType (MandatoryMember: QVariant(double, 23))\n\nRead encoded value of type &quot;QtTestUnionType&quot;\nDecoded value: Union QtTestUnionType (Member2: QVariant(QOpcUaGenericStructValue, Struct QtInnerTestStructType (DoubleSubtypeMember: QVariant(double, 42))))\n\n&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;\n&gt; Demonstrate the encoding and decoding with custom descriptions &lt;\n&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\n\nSerialized data: &quot;\\x00\\x00\\x00\\x00\\x17\\x00\\x00\\x00\\x0E\\x00\\x00\\x00My test string&quot;\nDecoded value: StructWithOptionalFields MyCustomTypeWithOptionalField (MyCustomMandatoryField: QVariant(int, 23) MyCustomOptionalField: QVariant(QString, My test string)) <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d3f3fad elementor-widget elementor-widget-heading\" data-id=\"d3f3fad\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Conclusion<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-efc6024 elementor-widget elementor-widget-text-editor\" data-id=\"efc6024\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Previously, a developer had to look at a structure&#8217;s XML definition and implement the decoding and encoding by hand using the QOpcUaBinaryDataEncoding class while handling every aspect like switch fields for unions and bit masks for structures with optional fields manually.<\/p>\n<p>The newly added classes make it much easier to interact with a server exposing custom structured type values because the <em>QOpcUaGenericStructValue<\/em> class is ideal for an explorative approach where getting to know the types involved can be as easy as printing a decoded value to the terminal and looking at the field names and values.<\/p>\n<p>The new feature is currently only available on the Qt OPC UA <a href=\"https:\/\/code.qt.io\/cgit\/qt\/qtopcua.git\/tree\/?h=dev\" target=\"_blank\" rel=\"noopener\">dev<\/a> branch.\u00a0\u00a0Until the feature freeze for Qt 6.7, which will take place in December, it will still possible to make API changes. Please let us know how you like the new API for generic structs!<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>OPC UA servers often use structured data types, for example when they are implementing a companion specification or exposing custom structured data types from a PLC program. Up to now, Qt OPC UA was just returning a binary blob when reading such a value and the decoding was left entirely to the user. Since OPC UA 1.04, there is a standardized way for a server to expose the data type description for custom data types. We have extended Qt OPC UA to use this information to make it much easier to encode and decode custom data types. The following article introduces the new API.<\/p>","protected":false},"author":4,"featured_media":4446,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1,2,781,230,8],"tags":[124,122,123],"class_list":["post-5530","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-allgemein","category-blog","category-newsletter-2024-02","category-opc-ua","category-qt","tag-opc-ua","tag-open62541","tag-qt-opc-ua"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/5530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/comments?post=5530"}],"version-history":[{"count":80,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/5530\/revisions"}],"predecessor-version":[{"id":11178,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/5530\/revisions\/11178"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media\/4446"}],"wp:attachment":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media?parent=5530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/categories?post=5530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/tags?post=5530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}