{"id":11371,"date":"2024-12-10T20:30:56","date_gmt":"2024-12-10T19:30:56","guid":{"rendered":"https:\/\/www.basyskom.de\/?p=11371"},"modified":"2024-12-11T11:57:53","modified_gmt":"2024-12-11T10:57:53","slug":"revisiting-nodeopcua","status":"publish","type":"post","link":"https:\/\/www.basyskom.de\/en\/revisiting-nodeopcua\/","title":{"rendered":"Revisiting NodeOPCUA"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"11371\" class=\"elementor elementor-11371\" data-elementor-post-type=\"post\">\n\t\t\t\t<div class=\"elementor-element elementor-element-3151829 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"3151829\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-72d9310 elementor-widget elementor-widget-heading\" data-id=\"72d9310\" 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\">Introducing NodeOPCUA<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-58b2474 elementor-widget elementor-widget-text-editor\" data-id=\"58b2474\" 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 <a href=\"https:\/\/node-opcua.github.io\/\" target=\"_blank\" rel=\"noopener\">NodeOPCUA<\/a> library is a very mature OPC UA implementation for TypeScript\/JavaScript on the Node.js platform. Since the initial release eleven years ago, it has been continuously updated and extended by the main developer and various external contributors. Read this article to learn if NodeOPCUA might be the right OPC UA stack for your next project.<\/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-6e74e77 elementor-widget elementor-widget-heading\" data-id=\"6e74e77\" 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\">Previous Experiences<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f8fa6ee elementor-widget elementor-widget-text-editor\" data-id=\"f8fa6ee\" 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>When we started exploring the OPC UA protocol and its software ecosystem in 2015, NodeOPCUA was the library we used to build our first rudimentary server, which exposed data from a weather sensor in its address space. Of course we didn&#8217;t really know much back then but we were pretty impressed by how easily we could get started and quickly expose a few variable nodes using very little code.<\/p><p>Since then, our OPC UA journey focused on C\/C++ based libraries like the now mostly abandoned <a href=\"https:\/\/github.com\/FreeOpcUa\/freeopcua\" target=\"_blank\" rel=\"noopener\">freeopcua<\/a>\u00a0and later <a href=\"https:\/\/www.open62541.org\/\" target=\"_blank\" rel=\"noopener\">open62541<\/a> because they fitted much better into the software architecture of our projects. After several years of learning, launching and maintaining <a href=\"https:\/\/doc.qt.io\/qt-6\/qtopcua-index.html\" target=\"_blank\" rel=\"noopener\">Qt OPC UA<\/a>, making contributions to the open62541 code base, and completing OPC UA based projects for our customers, we decided to have a fresh look at NodeOPCUA based on the experience we gathered over the last years.<\/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-6efe0d8 elementor-widget elementor-widget-heading\" data-id=\"6efe0d8\" 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\">Our Evaluation<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d6e6a69 elementor-widget elementor-widget-text-editor\" data-id=\"d6e6a69\" 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\tOur main objective was to find out if the NodeOPCUA module&#8217;s feature set would be sufficient to use it in one of our customer projects. So first of all, we made a list of the features we usually need:\n<ul>\n \t<li>Loading a custom OPC UA model with structured types, ObjectTypes, EventTypes and fixed objects in the Objects folder<\/li>\n \t<li>All aspects of custom structured types (unions, optional fields, array fields, nested structures)<\/li>\n \t<li>Exposing and connecting to endpoints with all current security policies and security modes<\/li>\n \t<li>Attaching callbacks to Method and Variable nodes on the server<\/li>\n \t<li>Instantiating custom object types with selected optional child nodes<\/li>\n \t<li>Populating and triggering custom events on the server<\/li>\n \t<li>Historizing variables on the server<\/li>\n \t<li>Calling methods from the client<\/li>\n \t<li>Resolving browse paths<\/li>\n \t<li>Reading, writing and monitoring variables of custom structured types<\/li>\n \t<li>Reading the raw history of variable nodes<\/li>\n \t<li><i>EventNotifier<\/i> monitored items with select and where clause<\/li>\n<\/ul>\nThen we modeled a compact OPC UA nodeset containing the types and structures mentioned above to have everything we need for our evaluation in one place. The exported nodeset XML file was then loaded into a new TypeScript based NodeOPCUA server project and after verifying the successful loading of the nodeset using UaExpert, we started implementing the mentioned features we usually require. A client adapted to interact with the custom server was implemented in a separate TypeScript project in order to evaluate the client API.\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-b5b9bd1 elementor-widget elementor-widget-heading\" data-id=\"b5b9bd1\" 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\">Observations and Remarks<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e936558 elementor-widget elementor-widget-text-editor\" data-id=\"e936558\" 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><span style=\"text-align: var(--text-align); color: var( --e-global-color-f7d9691 );\">Coming from C\/C++ based OPC UA implementations, we were really impressed by the way NodeOPCUA handles structured types. The\u00a0<\/span><i style=\"text-align: var(--text-align); color: var( --e-global-color-f7d9691 );\">constructExtensionObject()<\/i><span style=\"text-align: var(--text-align); color: var( --e-global-color-f7d9691 );\">\u00a0method reduces building an extension object containing a custom structured type to just passing an object containing a property for each structure field. Just leaving out or including a property takes care of optional fields and nested structured types are deduced and constructed automatically.<\/span><\/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-9a18698 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"9a18698\" 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-typescript'>const ext = await session.constructExtensionObject(coerceNodeId(&#039;ns=2;i=3003&#039;), {\n    int64ArrayMember: [ 1, 2 ],\n    nestedStructArrayMember: [\n        {\n            doubleSubtypeMember: 23\n        },\n        {\n            doubleSubtypeMember: 42,\n        }\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-5cf8bde elementor-widget elementor-widget-text-editor\" data-id=\"5cf8bde\" 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>We also liked the\u00a0<i>makeBrowsePath()<\/i>\u00a0method which builds basic browse paths from a starting node and a path string like\u00a0<i>\/2:MyObject\/2:MyTestVariable<\/i>. This covers most daily usage of browse paths, but if additional\u00a0<i>RelativePathElement<\/i>\u00a0fields beside the\u00a0<i>BrowseName<\/i>\u00a0are required, the returned\u00a0<i>BrowsePath<\/i>\u00a0can be easily edited manually to add the necessary information.<\/p><p>Building and triggering an event on the server side is also very easy.\u00a0An event type and an object containing a property for each child node of the event type requiring a customized value is passed to\u00a0<i>raiseEvent()<\/i>\u00a0which performs all necessary steps.<\/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-eb93819 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"eb93819\" 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-typescript'>const serverNode = server.engine.addressSpace?.getDefaultNamespace().findNode(ObjectIds.Server) as UAObject\n\nif (serverNode) {\n    serverNode.raiseEvent(&#039;2:MyTestEvent&#039;, {\n        sourceName: { dataType: DataType.String, value: &#039;Server&#039; },\n        sourceNode: { dataType: DataType.NodeId, value: serverNode.nodeId },\n        message: { dataType: DataType.String, value: &#039;Test message&#039; },\n        customDouble: { dataType: DataType.Double, value: 42 },\n        customString: { dataType: DataType.String, value: &#039;Lorem Ipsum Dolor Sit Amet&#039; }\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-53ceea6 elementor-widget elementor-widget-text-editor\" data-id=\"53ceea6\" 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 client side API for creating an\u00a0<i>EventNotifier<\/i>\u00a0monitored item is made easy by the\u00a0<i>constructEventFilter()<\/i>\u00a0method which takes an array of strings in the style of\u00a0<i>Message,<\/i>\u00a0<i>2:MyProperty<\/i>\u00a0or\u00a0<span style=\"text-align: var(--text-align);\"><i>EnabledState.EffectiveDisplayName\u00a0<\/i><\/span><span style=\"color: var( --e-global-color-f7d9691 ); text-align: var(--text-align);\">to build the select clause. Any additional fields of the\u00a0<em>SimpleAttributeOperand<\/em>\u00a0can be set in the returned\u00a0<em>EventFilter<\/em>\u00a0as described for\u00a0<em>ReleativePathElement<\/em>. The where clause is directly built from a\u00a0<\/span><span style=\"text-align: var(--text-align);\"><em>ContentFilterElement<\/em>\u00a0array.<\/span><\/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-953226c elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"953226c\" 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-typescript'>const eventMonitoredItem = await sub.monitor({ nodeId: ObjectIds.Server, attributeId: AttributeIds.EventNotifier },\n    {\n        samplingInterval: 0,\n        queueSize: 10, \n        filter: constructEventFilter([ &#039;Message&#039;, &#039;2:CustomString&#039;, &#039;2:CustomDouble&#039; ])\n    }, TimestampsToReturn.Both) <\/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-3eac3dc elementor-widget elementor-widget-text-editor\" data-id=\"3eac3dc\" 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 only part that seemed a bit strange from our perspective was that an\u00a0<i>HA Configuration<\/i>\u00a0object containing all optional child nodes is added automatically to every variable node historizing is enabled for. Using history with a variable node that is bound to a\u00a0<i>get<\/i>\u00a0callback (for example to get a value from an external source) was a bit of a surprise because there seems to be no built-in history sampling mechanism like in open62541. This resulted in a history that only contained values whenever a client was reading the variable or a monitored item was sampling it.<\/p><p><span style=\"text-align: var(--text-align); color: var( --e-global-color-f7d9691 );\">Using our knowledge of the OPC UA specification, implementing all listed points for both the client and the server side was relatively straightforward. As the examples only cover a few basics of the API and there is not much public documentation (especially for more advanced features), looking at the source code of the tests or the implementation itself was sometimes necessary to find the right way of using the NodeOPCUA module. Knowing the names of the involved OPC UA services and types definitely helped finding the corresponding code.<\/span><\/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-0f396c7 elementor-widget elementor-widget-heading\" data-id=\"0f396c7\" 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-d59c611 elementor-widget elementor-widget-text-editor\" data-id=\"d59c611\" 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 NodeOPCUA module is not just a straight implementation of the OPC UA services but\u00a0has a well thought out API with lots of great convenience methods which greatly reduce the amount of code that&#8217;s usually necessary to perform simple tasks in an OPC UA server or client.<br \/>The asynchronous nature of the OPC UA protocol and the mechanisms for asynchronous programming built into NodeJS are a perfect match. For example, chaining a <i>TranslateBrowsePathsToNodeIds<\/i>\u00a0call to a read or write operation using the retrieved nodeId feels very intuitive.<\/p><p>With the <a href=\"https:\/\/digital-strategy.ec.europa.eu\/en\/policies\/cyber-resilience-act\" target=\"_blank\" rel=\"noopener\">Cyber Resilience Act<\/a> looming at the horizon, it is a good idea to look into alternatives for building network facing applications in languages without manual memory management. From what we have seen in our evaluation, the NodeOPCUA module will most definitely be an option for some of our future customer projects.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\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>The NodeOPCUA library is a very mature OPC UA implementation for TypeScript\/JavaScript on the Node.js platform. Since the initial release eleven years ago, it has been continuously updated and extended by the main developer and various external contributors. Read this article to learn if NodeOPCUA might be the right OPC UA stack for your next project.<\/p>","protected":false},"author":4,"featured_media":4110,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1,2,230],"tags":[],"class_list":["post-11371","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-allgemein","category-blog","category-opc-ua"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/11371","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=11371"}],"version-history":[{"count":101,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/11371\/revisions"}],"predecessor-version":[{"id":11579,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/11371\/revisions\/11579"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media\/4110"}],"wp:attachment":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media?parent=11371"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/categories?post=11371"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/tags?post=11371"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}