[{"data":1,"prerenderedAt":1488},["ShallowReactive",2],{"blog-\u002Fblog\u002F2023\u002F07\u002Fhow-to-deploy-a-basic-opc-ua-server-in-node-red":3},{"id":4,"title":5,"body":6,"description":12,"extension":1478,"meta":1479,"navigation":390,"path":1484,"seo":1485,"stem":1486,"__hash__":1487},"blog\u002Fblog\u002F2023\u002F07\u002Fhow-to-deploy-a-basic-opc-ua-server-in-node-red.md","How to Deploy a Basic OPC-UA Server in Node-RED - Part 1",{"type":7,"value":8,"toc":1471},"minimark",[9,13,18,21,24,43,47,62,71,74,81,89,97,103,106,113,127,130,134,149,158,161,167,186,398,413,594,597,604,611,618,645,651,658,668,675,681,693,696,715,718,742,753,890,893,995,998,1221,1224,1348,1355,1375,1382,1388,1392,1401,1404,1416,1419,1433,1440,1451,1457,1461,1464,1467],[10,11,12],"p",{},"This article is the first part of a series of OPC-UA content.  Here, we will explain some basic concepts of OPC-UA as they apply to building a server in Node-RED, then walk through and deploy an example OPC-UA Server.",[14,15,17],"h2",{"id":16},"what-is-opc-ua","What is OPC-UA?",[10,19,20],{},"Open Platform Communications Unified Architecture (OPC UA) is an open, platform independent communication framework frequently utilized in industrial automation, and is considered one of the key protocol standards for Industry 4.0 and Industrial IoT (IIoT).  The standard is developed and maintained by a consortium called the OPC Foundation, with recognizable industry names such as Siemens, Honeywell, Microsoft, Beckhoff, SAP, Yokogawa, ABB, Rockwell, and Schneider Electric.",[10,22,23],{},"Because of OPC-UA’s wide industry acceptance, it is increasingly becoming natively supported on devices and systems spanning the entirety of the automation pyramid.",[10,25,26,32],{},[27,28],"img",{"alt":29,"src":30,"title":31},"\"Automation Pyramid\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fautomation-pyramid.jpg","Automation Pyramid",[33,34,35,36],"em",{},"Image reference - ",[37,38,42],"a",{"href":39,"rel":40},"https:\u002F\u002Fwww.motioncontroltips.com\u002Fwhat-is-opc-ua-and-how-does-it-compare-with-industrial-ethernet\u002F",[41],"nofollow","imagecontroltips.com",[14,44,46],{"id":45},"fieldbus-model-vs-opc-ua-information-model","Fieldbus Model vs OPC-UA Information Model",[10,48,49,50,55],{},"As of today, industrial ethernet fieldbuses dominate the field\u002Fdevice-level (level 0) and controller\u002FPLC-level (level 1) of the automation pyramid.\n",[27,51],{"alt":52,"src":53,"title":54},"\"OPC-UA Pyramid\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002FOPC-UA-pyramid-2.webp","OPC-UA Pyramid",[33,56,35,57],{},[37,58,61],{"href":59,"rel":60},"https:\u002F\u002Fwww.mdpi.com\u002F1424-8220\u002F21\u002F14\u002F4656",[41],"mdpi.com",[10,63,64,65,70],{},"Fieldbuses such as Profinet, Ethernet\u002FIP, and EtherCAT, employ deterministic, real-time communication, which is essential for mission-critical and safety-oriented automation tasks.  OPC-UA is most commonly encountered at the SCADA level and above (level 2-4).  However, with the inclusion of ",[37,66,69],{"href":67,"rel":68},"https:\u002F\u002Fwww.tttech-industrial.com\u002Fresource-library\u002Fblog-posts\u002Fopc-ua-fx",[41],"Time Sensitive Networking (TSN) into the OPC-UA technology stack",", OPC-UA can be feasibly used for real-time communication all the way down to the device level.",[10,72,73],{},"Traditionally, fieldbus protocols transmit only raw data from field devices (ie, a float to represent a pressure, or a boolean to represent the position of a switch).  The fieldbus data gets pushed up the automation stack layer by layer, where eventually it will be converted to a format suitable for IT systems to consume (such as OPC-UA).",[10,75,76],{},[27,77],{"alt":78,"src":79,"title":80},"\"Fieldbus Model\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Ffieldbus-model.png","Fieldbus Model",[10,82,83,84,88],{},"In contrast to fieldbus protocols, OPC-UA represents automation data in the form of nodes. The framework for constructing nodes is referred to as the ",[37,85,87],{"href":86},"lhttps:\u002F\u002Freference.opcfoundation.org\u002FCore\u002FPart5\u002Fv104\u002Fdocs\u002F","OPC Information model",", and consists of pre-defined classes and methods that are programmed in the OPC Server address space.",[10,90,91,96],{},[27,92],{"alt":93,"src":94,"title":95},"\"OPC Information Model\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-information-model.png","OPC Information Model","\nDevices can be described as objects that give a holistic view of the device, beyond simply the raw value.  To construct a device object, we can take different individual attributes associated with a device, such as the transmitter raw value, transmitter fault flag, alarm setpoint, and combine them, similar to how user-defined datatypes (UDTs) are objects used to represent devices in PLCs.  The information model also defines a folder structure, to allow devices information to reside in a structured hierarchy.  Using the example temperature transmitter above, an example folder structure can be constructed as follows:",[10,98,99],{},[100,101,102],"code",{},"\u002FRoot\u002FObjects\u002FCalcinator 1 PLC\u002FTemperature Transmitters\u002FTank 1 Temperature\u002FTransmitter Value",[10,104,105],{},"This folder structure will be exposed via the OPC Client browser, allowing end-users to easily “drill down” to individual node information in a logical manner.",[10,107,108,112],{},[27,109],{"alt":110,"src":111,"title":95},"\"OPC Client Browser\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-browser.png","\nIn summary, OPC-UA represents a trade-off between complex information modeling, with the versatility for that data to be consumed by devices and systems all the way up the automation pyramid layers.  The data does not have to pass through subsequent automation layers on the way up, nor does the data need to undergo any conversion along the way.",[10,114,115,120],{},[27,116],{"alt":117,"src":118,"title":119},"\"OPC-UA Distributed Model\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002FOPC-UA-distributed-model.jpg","OPC-UA Distributed Model",[33,121,35,122],{},[37,123,126],{"href":124,"rel":125},"https:\u002F\u002Fifr.org\u002Fpost\u002Ffaster-robot-communication-through-the-opc-robotics-companion-specification",[41],"ifr.org",[10,128,129],{},"The OPC client simply needs to subscribe to the OPC Server endpoint url (ex. opc.tcp:\u002F\u002Fserver.address), and the client will be able to browse the structured OPC data as it’s modeled in the server.  Any client will receive the information in the same manner, regardless if it’s a PLC, SCADA, MES, or ERP system.  This opens the possibility for horizontal and vertical system integration in a standardized manner. Additionally, the more information that is exposed about a device, the easier it is to track, and use said data to autonomously reconfigure, or pre-emptively take maintenance actions.",[14,131,133],{"id":132},"deploying-an-example-opc-ua-server-in-node-red","Deploying an Example OPC-UA Server in Node-RED",[10,135,136,137,142,143,148],{},"With some background on OPC-UA and how information is modeled in mind, we can take a look at the ",[37,138,141],{"href":139,"rel":140},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-opcua-server",[41],"node-red-contrib-opcua-server"," node, which is merely a compact version of the ",[37,144,147],{"href":145,"rel":146},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-opcua",[41],"node-red-contrib-opcua"," node that only focuses on the OPC-UA server and hence requires less dependencies.",[10,150,151,152,157],{},"An ",[37,153,156],{"href":154,"rel":155},"https:\u002F\u002Fgithub.com\u002FBiancoRoyal\u002Fnode-red-contrib-opcua-server\u002Fblob\u002Fmaster\u002Fexamples\u002Fserver-with-context.json",[41],"example flow"," is provided on github that can serve as a basis for understanding how a OPC-UA server is constructed.  Let’s get the example server up and running.",[10,159,160],{},"Deploying the example flow yields the following result -",[10,162,163],{},[27,164],{"alt":165,"src":166,"title":165},"Compact Server Flow","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcompact-server-flow.png",[168,169,170],"ul",{},[171,172,173,174,177,178,181,182,185],"li",{},"an inject node is trigging the function ",[100,175,176],{},"set flow context Inputs"," at a one second interval, which creates 7 randomly generated float values and stores them as flow context variables, ",[100,179,180],{},"isoInput2"," - ",[100,183,184],{},"isoInput8"," (isolated inputs).  The values will change to a new random number each time the node is injected.",[187,188,193],"pre",{"className":189,"code":190,"language":191,"meta":192,"style":192},"language-javascript shiki shiki-themes github-light github-dark","flow.set('isoInput2', Math.random() + 12.0)\nflow.set('isoInput3', Math.random() + 13.0)\nflow.set('isoInput4', Math.random() + 14.0)\nflow.set('isoInput5', Math.random() + 15.0)\nflow.set('isoInput6', Math.random() + 16.0)\nflow.set('isoInput7', Math.random() + 17.0)\nflow.set('isoInput8', Math.random() + 18.0)\n\n...\n","javascript","",[100,194,195,235,260,285,310,335,360,385,392],{"__ignoreMap":192},[196,197,200,204,208,211,215,218,221,224,228,232],"span",{"class":198,"line":199},"line",1,[196,201,203],{"class":202},"sVt8B","flow.",[196,205,207],{"class":206},"sScJk","set",[196,209,210],{"class":202},"(",[196,212,214],{"class":213},"sZZnC","'isoInput2'",[196,216,217],{"class":202},", Math.",[196,219,220],{"class":206},"random",[196,222,223],{"class":202},"() ",[196,225,227],{"class":226},"szBVR","+",[196,229,231],{"class":230},"sj4cs"," 12.0",[196,233,234],{"class":202},")\n",[196,236,238,240,242,244,247,249,251,253,255,258],{"class":198,"line":237},2,[196,239,203],{"class":202},[196,241,207],{"class":206},[196,243,210],{"class":202},[196,245,246],{"class":213},"'isoInput3'",[196,248,217],{"class":202},[196,250,220],{"class":206},[196,252,223],{"class":202},[196,254,227],{"class":226},[196,256,257],{"class":230}," 13.0",[196,259,234],{"class":202},[196,261,263,265,267,269,272,274,276,278,280,283],{"class":198,"line":262},3,[196,264,203],{"class":202},[196,266,207],{"class":206},[196,268,210],{"class":202},[196,270,271],{"class":213},"'isoInput4'",[196,273,217],{"class":202},[196,275,220],{"class":206},[196,277,223],{"class":202},[196,279,227],{"class":226},[196,281,282],{"class":230}," 14.0",[196,284,234],{"class":202},[196,286,288,290,292,294,297,299,301,303,305,308],{"class":198,"line":287},4,[196,289,203],{"class":202},[196,291,207],{"class":206},[196,293,210],{"class":202},[196,295,296],{"class":213},"'isoInput5'",[196,298,217],{"class":202},[196,300,220],{"class":206},[196,302,223],{"class":202},[196,304,227],{"class":226},[196,306,307],{"class":230}," 15.0",[196,309,234],{"class":202},[196,311,313,315,317,319,322,324,326,328,330,333],{"class":198,"line":312},5,[196,314,203],{"class":202},[196,316,207],{"class":206},[196,318,210],{"class":202},[196,320,321],{"class":213},"'isoInput6'",[196,323,217],{"class":202},[196,325,220],{"class":206},[196,327,223],{"class":202},[196,329,227],{"class":226},[196,331,332],{"class":230}," 16.0",[196,334,234],{"class":202},[196,336,338,340,342,344,347,349,351,353,355,358],{"class":198,"line":337},6,[196,339,203],{"class":202},[196,341,207],{"class":206},[196,343,210],{"class":202},[196,345,346],{"class":213},"'isoInput7'",[196,348,217],{"class":202},[196,350,220],{"class":206},[196,352,223],{"class":202},[196,354,227],{"class":226},[196,356,357],{"class":230}," 17.0",[196,359,234],{"class":202},[196,361,363,365,367,369,372,374,376,378,380,383],{"class":198,"line":362},7,[196,364,203],{"class":202},[196,366,207],{"class":206},[196,368,210],{"class":202},[196,370,371],{"class":213},"'isoInput8'",[196,373,217],{"class":202},[196,375,220],{"class":206},[196,377,223],{"class":202},[196,379,227],{"class":226},[196,381,382],{"class":230}," 18.0",[196,384,234],{"class":202},[196,386,388],{"class":198,"line":387},8,[196,389,391],{"emptyLinePlaceholder":390},true,"\n",[196,393,395],{"class":198,"line":394},9,[196,396,397],{"class":226},"...\n",[168,399,400],{},[171,401,402,403,406,407,181,410,185],{},"another inject node is triggering the function ",[100,404,405],{},"set flow context Outputs",", also at a one second interval, which creates another set of 7 randomly generated float values and stores them as flow context variables, ",[100,408,409],{},"isoOutput2",[100,411,412],{},"isoOutput8",[187,414,416],{"className":189,"code":415,"language":191,"meta":192,"style":192},"flow.set('isoOutput2', Math.random() + 2.0)\nflow.set('isoOutput3', Math.random() + 3.0)\nflow.set('isoOutput4', Math.random() + 4.0)\nflow.set('isoOutput5', Math.random() + 5.0)\nflow.set('isoOutput6', Math.random() + 6.0)\nflow.set('isoOutput7', Math.random() + 7.0)\nflow.set('isoOutput8', Math.random() + 8.0)\n\n...\n",[100,417,418,442,466,490,514,538,562,586,590],{"__ignoreMap":192},[196,419,420,422,424,426,429,431,433,435,437,440],{"class":198,"line":199},[196,421,203],{"class":202},[196,423,207],{"class":206},[196,425,210],{"class":202},[196,427,428],{"class":213},"'isoOutput2'",[196,430,217],{"class":202},[196,432,220],{"class":206},[196,434,223],{"class":202},[196,436,227],{"class":226},[196,438,439],{"class":230}," 2.0",[196,441,234],{"class":202},[196,443,444,446,448,450,453,455,457,459,461,464],{"class":198,"line":237},[196,445,203],{"class":202},[196,447,207],{"class":206},[196,449,210],{"class":202},[196,451,452],{"class":213},"'isoOutput3'",[196,454,217],{"class":202},[196,456,220],{"class":206},[196,458,223],{"class":202},[196,460,227],{"class":226},[196,462,463],{"class":230}," 3.0",[196,465,234],{"class":202},[196,467,468,470,472,474,477,479,481,483,485,488],{"class":198,"line":262},[196,469,203],{"class":202},[196,471,207],{"class":206},[196,473,210],{"class":202},[196,475,476],{"class":213},"'isoOutput4'",[196,478,217],{"class":202},[196,480,220],{"class":206},[196,482,223],{"class":202},[196,484,227],{"class":226},[196,486,487],{"class":230}," 4.0",[196,489,234],{"class":202},[196,491,492,494,496,498,501,503,505,507,509,512],{"class":198,"line":287},[196,493,203],{"class":202},[196,495,207],{"class":206},[196,497,210],{"class":202},[196,499,500],{"class":213},"'isoOutput5'",[196,502,217],{"class":202},[196,504,220],{"class":206},[196,506,223],{"class":202},[196,508,227],{"class":226},[196,510,511],{"class":230}," 5.0",[196,513,234],{"class":202},[196,515,516,518,520,522,525,527,529,531,533,536],{"class":198,"line":312},[196,517,203],{"class":202},[196,519,207],{"class":206},[196,521,210],{"class":202},[196,523,524],{"class":213},"'isoOutput6'",[196,526,217],{"class":202},[196,528,220],{"class":206},[196,530,223],{"class":202},[196,532,227],{"class":226},[196,534,535],{"class":230}," 6.0",[196,537,234],{"class":202},[196,539,540,542,544,546,549,551,553,555,557,560],{"class":198,"line":337},[196,541,203],{"class":202},[196,543,207],{"class":206},[196,545,210],{"class":202},[196,547,548],{"class":213},"'isoOutput7'",[196,550,217],{"class":202},[196,552,220],{"class":206},[196,554,223],{"class":202},[196,556,227],{"class":226},[196,558,559],{"class":230}," 7.0",[196,561,234],{"class":202},[196,563,564,566,568,570,573,575,577,579,581,584],{"class":198,"line":362},[196,565,203],{"class":202},[196,567,207],{"class":206},[196,569,210],{"class":202},[196,571,572],{"class":213},"'isoOutput8'",[196,574,217],{"class":202},[196,576,220],{"class":206},[196,578,223],{"class":202},[196,580,227],{"class":226},[196,582,583],{"class":230}," 8.0",[196,585,234],{"class":202},[196,587,588],{"class":198,"line":387},[196,589,391],{"emptyLinePlaceholder":390},[196,591,592],{"class":198,"line":394},[196,593,397],{"class":226},[10,595,596],{},"We can confirm the values are being stored in memory by checking the flow context data and pressing the refresh button.",[10,598,599],{},[27,600],{"alt":601,"src":602,"title":603},"\"Screenshot showing the Context Data option\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcontext-data-1.png","Screenshot showing the Context Data option",[10,605,606,610],{},[27,607],{"alt":608,"src":609,"title":608},"Screenshot showing the flow variables in the context data tab","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcontext-data-2.png","\nEach time we hit refresh, the values change, confirming that the values are randomly changing every second.",[10,612,613,614,617],{},"The last, and most important part of the flow, is the ",[100,615,616],{},"Compact-Server"," node, which actually stands alone without any incoming or outgoing connections.",[10,619,620,625,626,628,629,632,633,636,637,640,641,644],{},[27,621],{"alt":622,"src":623,"title":624},"\"Compact Server Node\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcompact-server-node.png","Compact Server Node","\nIn the ",[100,627,616],{}," node properties, the first tab is ",[100,630,631],{},"Settings",", and the two important properties here are ",[100,634,635],{},"Port"," and ",[100,638,639],{},"Show Errors",".  As can be seen in the node screenshot above, the node is reporting ",[100,642,643],{},"active",", which means the server is configured correctly.",[10,646,647],{},[27,648],{"alt":649,"src":650,"title":649},"Screenshot showing the Settings Tab of compact server node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fsettings-tab.png",[10,652,653,654,657],{},"The ",[100,655,656],{},"Limits"," tab specifies some default limits that we can configure if we like, but are not necessary to be modified for test purposes.",[10,659,653,660,663,664,667],{},[100,661,662],{},"Security"," tab has one important option, ",[100,665,666],{},"Allow Anonymous",".  By default, anonymous access is enabled.",[10,669,670,674],{},[27,671],{"alt":672,"src":673,"title":672},"Screenshot showing the Security Tab of compact server node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fsecurity-tab.png","\nFor a production system, we will want to enable security, but for test purposes, we will leave anonymous access enabled.",[10,676,677,680],{},[100,678,679],{},"Users & Sets"," tab is related to security and permissions.  We can leave this empty for testing.",[10,682,653,683,686,687,692],{},[100,684,685],{},"Address Space"," tab is where our server OPC Information Model is constructed, using classes and methods from the ",[37,688,691],{"href":689,"rel":690},"https:\u002F\u002Fnode-opcua.github.io\u002F",[41],"node-opcua sdk",".",[10,694,695],{},"Breaking down the provided example code for further context, it starts with a function that is responsible for invoking the OPC-UA server,",[187,697,699],{"className":189,"code":698,"language":191,"meta":192,"style":192},"  const opcua = coreServer.choreCompact.opcua;\n",[100,700,701],{"__ignoreMap":192},[196,702,703,706,709,712],{"class":198,"line":199},[196,704,705],{"class":226},"  const",[196,707,708],{"class":230}," opcua",[196,710,711],{"class":226}," =",[196,713,714],{"class":202}," coreServer.choreCompact.opcua;\n",[10,716,717],{},"and then the namespace is created.",[187,719,721],{"className":189,"code":720,"language":191,"meta":192,"style":192},"const namespace = addressSpace.getOwnNamespace();\n",[100,722,723],{"__ignoreMap":192},[196,724,725,728,731,733,736,739],{"class":198,"line":199},[196,726,727],{"class":226},"const",[196,729,730],{"class":230}," namespace",[196,732,711],{"class":226},[196,734,735],{"class":202}," addressSpace.",[196,737,738],{"class":206},"getOwnNamespace",[196,740,741],{"class":202},"();\n",[10,743,744,745,748,749,752],{},"Further down, the variables that will be published by the server (which are our ",[100,746,747],{},"isoInput"," & ",[100,750,751],{},"isoOutput"," flow context variables) are initialized,",[187,754,756],{"className":189,"code":755,"language":191,"meta":192,"style":192},"  this.sandboxFlowContext.set(\"isoInput1\", 0);\n  this.setInterval(() => {\n    flexServerInternals.sandboxFlowContext.set(\n      \"isoInput1\",\n      Math.random() + 50.0\n    );\n  }, 500);\n  this.sandboxFlowContext.set(\"isoInput2\", 0);\n  this.sandboxFlowContext.set(\"isoInput3\", 0);\n...\n",[100,757,758,782,800,810,818,832,837,847,866,885],{"__ignoreMap":192},[196,759,760,763,766,768,770,773,776,779],{"class":198,"line":199},[196,761,762],{"class":230},"  this",[196,764,765],{"class":202},".sandboxFlowContext.",[196,767,207],{"class":206},[196,769,210],{"class":202},[196,771,772],{"class":213},"\"isoInput1\"",[196,774,775],{"class":202},", ",[196,777,778],{"class":230},"0",[196,780,781],{"class":202},");\n",[196,783,784,786,788,791,794,797],{"class":198,"line":237},[196,785,762],{"class":230},[196,787,692],{"class":202},[196,789,790],{"class":206},"setInterval",[196,792,793],{"class":202},"(() ",[196,795,796],{"class":226},"=>",[196,798,799],{"class":202}," {\n",[196,801,802,805,807],{"class":198,"line":262},[196,803,804],{"class":202},"    flexServerInternals.sandboxFlowContext.",[196,806,207],{"class":206},[196,808,809],{"class":202},"(\n",[196,811,812,815],{"class":198,"line":287},[196,813,814],{"class":213},"      \"isoInput1\"",[196,816,817],{"class":202},",\n",[196,819,820,823,825,827,829],{"class":198,"line":312},[196,821,822],{"class":202},"      Math.",[196,824,220],{"class":206},[196,826,223],{"class":202},[196,828,227],{"class":226},[196,830,831],{"class":230}," 50.0\n",[196,833,834],{"class":198,"line":337},[196,835,836],{"class":202},"    );\n",[196,838,839,842,845],{"class":198,"line":362},[196,840,841],{"class":202},"  }, ",[196,843,844],{"class":230},"500",[196,846,781],{"class":202},[196,848,849,851,853,855,857,860,862,864],{"class":198,"line":387},[196,850,762],{"class":230},[196,852,765],{"class":202},[196,854,207],{"class":206},[196,856,210],{"class":202},[196,858,859],{"class":213},"\"isoInput2\"",[196,861,775],{"class":202},[196,863,778],{"class":230},[196,865,781],{"class":202},[196,867,868,870,872,874,876,879,881,883],{"class":198,"line":394},[196,869,762],{"class":230},[196,871,765],{"class":202},[196,873,207],{"class":206},[196,875,210],{"class":202},[196,877,878],{"class":213},"\"isoInput3\"",[196,880,775],{"class":202},[196,882,778],{"class":230},[196,884,781],{"class":202},[196,886,888],{"class":198,"line":887},10,[196,889,397],{"class":226},[10,891,892],{},"and an OPC folder structure is defined.",[187,894,896],{"className":189,"code":895,"language":191,"meta":192,"style":192},"  coreServer.debugLog(\"init dynamic address space\");\n  const rootFolder = addressSpace.findNode(\"RootFolder\");\n\n  node.warn(\"construct new address space for OPC UA\");\n\n  const myDevice = namespace.addFolder(rootFolder.objects, {\n    \"browseName\": \"RaspberryPI-Zero-WLAN\"\n  });\n...\n",[100,897,898,913,934,938,953,957,975,986,991],{"__ignoreMap":192},[196,899,900,903,906,908,911],{"class":198,"line":199},[196,901,902],{"class":202},"  coreServer.",[196,904,905],{"class":206},"debugLog",[196,907,210],{"class":202},[196,909,910],{"class":213},"\"init dynamic address space\"",[196,912,781],{"class":202},[196,914,915,917,920,922,924,927,929,932],{"class":198,"line":237},[196,916,705],{"class":226},[196,918,919],{"class":230}," rootFolder",[196,921,711],{"class":226},[196,923,735],{"class":202},[196,925,926],{"class":206},"findNode",[196,928,210],{"class":202},[196,930,931],{"class":213},"\"RootFolder\"",[196,933,781],{"class":202},[196,935,936],{"class":198,"line":262},[196,937,391],{"emptyLinePlaceholder":390},[196,939,940,943,946,948,951],{"class":198,"line":287},[196,941,942],{"class":202},"  node.",[196,944,945],{"class":206},"warn",[196,947,210],{"class":202},[196,949,950],{"class":213},"\"construct new address space for OPC UA\"",[196,952,781],{"class":202},[196,954,955],{"class":198,"line":312},[196,956,391],{"emptyLinePlaceholder":390},[196,958,959,961,964,966,969,972],{"class":198,"line":337},[196,960,705],{"class":226},[196,962,963],{"class":230}," myDevice",[196,965,711],{"class":226},[196,967,968],{"class":202}," namespace.",[196,970,971],{"class":206},"addFolder",[196,973,974],{"class":202},"(rootFolder.objects, {\n",[196,976,977,980,983],{"class":198,"line":362},[196,978,979],{"class":213},"    \"browseName\"",[196,981,982],{"class":202},": ",[196,984,985],{"class":213},"\"RaspberryPI-Zero-WLAN\"\n",[196,987,988],{"class":198,"line":387},[196,989,990],{"class":202},"  });\n",[196,992,993],{"class":198,"line":394},[196,994,397],{"class":226},[10,996,997],{},"Then, with our variables and folder structure defined, nodes are added to the namespace for each context variable.",[187,999,1001],{"className":189,"code":1000,"language":191,"meta":192,"style":192},"  const gpioDI1 = namespace.addVariable({\n    \"organizedBy\": isoInputs,\n    \"browseName\": \"I1\",\n    \"nodeId\": \"ns=1;s=Isolated_Input1\",\n    \"dataType\": \"Double\",\n    \"value\": {\n      \"get\": function() {\n        return new Variant({\n          \"dataType\": DataType.Double,\n          \"value\": flexServerInternals.sandboxFlowContext.get(\"isoInput1\")\n        });\n      },\n      \"set\": function(variant) {\n        flexServerInternals.sandboxFlowContext.set(\n          \"isoInput1\",\n          parseFloat(variant.value)\n        );\n        return opcua.StatusCodes.Good;\n      }\n    }\n  });\n\n...\n",[100,1002,1003,1020,1028,1039,1051,1063,1071,1084,1097,1105,1122,1128,1134,1153,1163,1171,1180,1186,1194,1200,1206,1211,1216],{"__ignoreMap":192},[196,1004,1005,1007,1010,1012,1014,1017],{"class":198,"line":199},[196,1006,705],{"class":226},[196,1008,1009],{"class":230}," gpioDI1",[196,1011,711],{"class":226},[196,1013,968],{"class":202},[196,1015,1016],{"class":206},"addVariable",[196,1018,1019],{"class":202},"({\n",[196,1021,1022,1025],{"class":198,"line":237},[196,1023,1024],{"class":213},"    \"organizedBy\"",[196,1026,1027],{"class":202},": isoInputs,\n",[196,1029,1030,1032,1034,1037],{"class":198,"line":262},[196,1031,979],{"class":213},[196,1033,982],{"class":202},[196,1035,1036],{"class":213},"\"I1\"",[196,1038,817],{"class":202},[196,1040,1041,1044,1046,1049],{"class":198,"line":287},[196,1042,1043],{"class":213},"    \"nodeId\"",[196,1045,982],{"class":202},[196,1047,1048],{"class":213},"\"ns=1;s=Isolated_Input1\"",[196,1050,817],{"class":202},[196,1052,1053,1056,1058,1061],{"class":198,"line":312},[196,1054,1055],{"class":213},"    \"dataType\"",[196,1057,982],{"class":202},[196,1059,1060],{"class":213},"\"Double\"",[196,1062,817],{"class":202},[196,1064,1065,1068],{"class":198,"line":337},[196,1066,1067],{"class":213},"    \"value\"",[196,1069,1070],{"class":202},": {\n",[196,1072,1073,1076,1078,1081],{"class":198,"line":362},[196,1074,1075],{"class":213},"      \"get\"",[196,1077,982],{"class":202},[196,1079,1080],{"class":226},"function",[196,1082,1083],{"class":202},"() {\n",[196,1085,1086,1089,1092,1095],{"class":198,"line":387},[196,1087,1088],{"class":226},"        return",[196,1090,1091],{"class":226}," new",[196,1093,1094],{"class":206}," Variant",[196,1096,1019],{"class":202},[196,1098,1099,1102],{"class":198,"line":394},[196,1100,1101],{"class":213},"          \"dataType\"",[196,1103,1104],{"class":202},": DataType.Double,\n",[196,1106,1107,1110,1113,1116,1118,1120],{"class":198,"line":887},[196,1108,1109],{"class":213},"          \"value\"",[196,1111,1112],{"class":202},": flexServerInternals.sandboxFlowContext.",[196,1114,1115],{"class":206},"get",[196,1117,210],{"class":202},[196,1119,772],{"class":213},[196,1121,234],{"class":202},[196,1123,1125],{"class":198,"line":1124},11,[196,1126,1127],{"class":202},"        });\n",[196,1129,1131],{"class":198,"line":1130},12,[196,1132,1133],{"class":202},"      },\n",[196,1135,1137,1140,1142,1144,1146,1150],{"class":198,"line":1136},13,[196,1138,1139],{"class":213},"      \"set\"",[196,1141,982],{"class":202},[196,1143,1080],{"class":226},[196,1145,210],{"class":202},[196,1147,1149],{"class":1148},"s4XuR","variant",[196,1151,1152],{"class":202},") {\n",[196,1154,1156,1159,1161],{"class":198,"line":1155},14,[196,1157,1158],{"class":202},"        flexServerInternals.sandboxFlowContext.",[196,1160,207],{"class":206},[196,1162,809],{"class":202},[196,1164,1166,1169],{"class":198,"line":1165},15,[196,1167,1168],{"class":213},"          \"isoInput1\"",[196,1170,817],{"class":202},[196,1172,1174,1177],{"class":198,"line":1173},16,[196,1175,1176],{"class":206},"          parseFloat",[196,1178,1179],{"class":202},"(variant.value)\n",[196,1181,1183],{"class":198,"line":1182},17,[196,1184,1185],{"class":202},"        );\n",[196,1187,1189,1191],{"class":198,"line":1188},18,[196,1190,1088],{"class":226},[196,1192,1193],{"class":202}," opcua.StatusCodes.Good;\n",[196,1195,1197],{"class":198,"line":1196},19,[196,1198,1199],{"class":202},"      }\n",[196,1201,1203],{"class":198,"line":1202},20,[196,1204,1205],{"class":202},"    }\n",[196,1207,1209],{"class":198,"line":1208},21,[196,1210,990],{"class":202},[196,1212,1214],{"class":198,"line":1213},22,[196,1215,391],{"emptyLinePlaceholder":390},[196,1217,1219],{"class":198,"line":1218},23,[196,1220,397],{"class":226},[10,1222,1223],{},"Last, OPC views are defined.  Views create custom hierarchies our OPC Client can browse as an alternative to the default folder structure.",[187,1225,1227],{"className":189,"code":1226,"language":191,"meta":192,"style":192},"  const viewDI = namespace.addView({\n    \"organizedBy\": rootFolder.views,\n    \"browseName\": \"RPIW0-Digital-Ins\"\n  });\n\n  const viewDO = namespace.addView({\n    \"organizedBy\": rootFolder.views,\n    \"browseName\": \"RPIW0-Digital-Outs\"\n  });\n\n  viewDI.addReference({\n    \"referenceType\": \"Organizes\",\n    \"nodeId\": gpioDI1.nodeId\n  });\n\n...\n\n",[100,1228,1229,1245,1252,1261,1265,1269,1284,1290,1299,1303,1307,1317,1329,1336,1340,1344],{"__ignoreMap":192},[196,1230,1231,1233,1236,1238,1240,1243],{"class":198,"line":199},[196,1232,705],{"class":226},[196,1234,1235],{"class":230}," viewDI",[196,1237,711],{"class":226},[196,1239,968],{"class":202},[196,1241,1242],{"class":206},"addView",[196,1244,1019],{"class":202},[196,1246,1247,1249],{"class":198,"line":237},[196,1248,1024],{"class":213},[196,1250,1251],{"class":202},": rootFolder.views,\n",[196,1253,1254,1256,1258],{"class":198,"line":262},[196,1255,979],{"class":213},[196,1257,982],{"class":202},[196,1259,1260],{"class":213},"\"RPIW0-Digital-Ins\"\n",[196,1262,1263],{"class":198,"line":287},[196,1264,990],{"class":202},[196,1266,1267],{"class":198,"line":312},[196,1268,391],{"emptyLinePlaceholder":390},[196,1270,1271,1273,1276,1278,1280,1282],{"class":198,"line":337},[196,1272,705],{"class":226},[196,1274,1275],{"class":230}," viewDO",[196,1277,711],{"class":226},[196,1279,968],{"class":202},[196,1281,1242],{"class":206},[196,1283,1019],{"class":202},[196,1285,1286,1288],{"class":198,"line":362},[196,1287,1024],{"class":213},[196,1289,1251],{"class":202},[196,1291,1292,1294,1296],{"class":198,"line":387},[196,1293,979],{"class":213},[196,1295,982],{"class":202},[196,1297,1298],{"class":213},"\"RPIW0-Digital-Outs\"\n",[196,1300,1301],{"class":198,"line":394},[196,1302,990],{"class":202},[196,1304,1305],{"class":198,"line":887},[196,1306,391],{"emptyLinePlaceholder":390},[196,1308,1309,1312,1315],{"class":198,"line":1124},[196,1310,1311],{"class":202},"  viewDI.",[196,1313,1314],{"class":206},"addReference",[196,1316,1019],{"class":202},[196,1318,1319,1322,1324,1327],{"class":198,"line":1130},[196,1320,1321],{"class":213},"    \"referenceType\"",[196,1323,982],{"class":202},[196,1325,1326],{"class":213},"\"Organizes\"",[196,1328,817],{"class":202},[196,1330,1331,1333],{"class":198,"line":1136},[196,1332,1043],{"class":213},[196,1334,1335],{"class":202},": gpioDI1.nodeId\n",[196,1337,1338],{"class":198,"line":1155},[196,1339,990],{"class":202},[196,1341,1342],{"class":198,"line":1165},[196,1343,391],{"emptyLinePlaceholder":390},[196,1345,1346],{"class":198,"line":1173},[196,1347,397],{"class":226},[10,1349,1350,1351,1354],{},"Finally, on the ",[100,1352,1353],{},"Discovery"," tab, we must define an endpoint for an OPC Client to subscribe to.",[10,1356,653,1357,1360,1361,1364,1365,1367,1368,1371,1372],{},[100,1358,1359],{},"Endpoint Url"," follows the format ",[100,1362,1363],{},"opc.tcp:\u002F\u002F\u003Caddress>:port",".  Our port was defined on the ",[100,1366,631],{}," tab, which by default, is port ",[100,1369,1370],{},"54845",". The address will be either the url or ip address of your Node-RED instance.  In my case, it’s 192.168.0.114.  So my Endpoint Url = ",[100,1373,1374],{},"opc.tcp:\u002F\u002F192.168.0.114:54845",[10,1376,1377,1381],{},[27,1378],{"alt":1379,"src":1380,"title":1379},"Screenshot showing the Discovery Tab of compact server node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fdiscovery-tab.png","\nOnce the endpoint url is added, deploy the flow, and confirm the server is reporting “active”.",[10,1383,1384],{},[27,1385],{"alt":1386,"src":1387,"title":1386},"Screenshot showing the Active Tab of compact server node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcompact-server-active.png",[14,1389,1391],{"id":1390},"connect-to-example-opc-server-using-opc-ua-browser","Connect to Example OPC-Server Using OPC-UA Browser",[10,1393,1394,1395,1400],{},"To connect to our OPC endpoint, we need an OPC Client.  Prosys provides a ",[37,1396,1399],{"href":1397,"rel":1398},"https:\u002F\u002Fwww.prosysopc.com\u002Fproducts\u002Fopc-ua-browser\u002F",[41],"free OPC-UA Browser ","that supports Windows, Linux, and Mac OS.  To test our Server, the Windows version of Prosys OPC-UA Browser will be utilized.",[10,1402,1403],{},"To connect to our Node-RED OPC server, enter the endpoint url and press “connect to server”.",[10,1405,1406,1411,1412,1415],{},[27,1407],{"alt":1408,"src":1409,"title":1410},"\"Screenshot showing the OPC Client\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-connect.png","Screenshot showing the OPC Client","\nIt will ask for security.  Remember that we allowed anonymous access, so the default security mode of ",[100,1413,1414],{},"None"," is the correct option.",[10,1417,1418],{},"Once connected, we can browse our OPC Server.",[10,1420,1421,1425,1426,1429,1430,1432],{},[27,1422],{"alt":1423,"src":1424},"OPC Client UI","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-ui.png","\nIf we navigate to ",[100,1427,1428],{},"Objects → RaspberryPI-Zero-WLAN → GPIO → Inputs",", we can see a list of inputs that correspond to the ",[100,1431,747],{}," context variables defined in the example flow, which are randomly generated numbers.",[10,1434,1435,1436,1439],{},"Clicking ",[100,1437,1438],{},"I1"," we can see the value in real-time, along with some additional properties.",[10,1441,1442,1446,1447,1450],{},[27,1443],{"alt":1444,"src":1445,"title":1444},"OPC Client Node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-node.png","\nIf we go to ",[100,1448,1449],{},"Views",", we can see the custom hierarchy defined in the example server, which divides the data by Digital-Ins and Digital-Outs.",[10,1452,1453],{},[27,1454],{"alt":1455,"src":1456},"OPC Client View","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-view.png",[14,1458,1460],{"id":1459},"summary","Summary",[10,1462,1463],{},"In this article, we compare OPC-UA to traditional fieldbus protocols, explain the importance of the OPC UA Information Model to understand how data is modeled in the address space of an OPC Server, and then walk through and deploy an example compact OPC-UA Server flow.",[10,1465,1466],{},"In our next article, we will build a custom OPC-UA Server in Node-RED with data pulled from an Allen Bradley PLC over Ethernet\u002FIP, using the PLC data to develop a custom OPC UA Information Model programmed in the OPC server address space.",[1468,1469,1470],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":192,"searchDepth":237,"depth":237,"links":1472},[1473,1474,1475,1476,1477],{"id":16,"depth":237,"text":17},{"id":45,"depth":237,"text":46},{"id":132,"depth":237,"text":133},{"id":1390,"depth":237,"text":1391},{"id":1459,"depth":237,"text":1460},"md",{"navTitle":5,"excerpt":1480},{"type":7,"value":1481},[1482],[10,1483,12],{},"\u002Fblog\u002F2023\u002F07\u002Fhow-to-deploy-a-basic-opc-ua-server-in-node-red",{"title":5,"description":12},"blog\u002F2023\u002F07\u002Fhow-to-deploy-a-basic-opc-ua-server-in-node-red","j-b5lRxxCJOo62J0b1WV3_XH53P2xvd0mCvZYyooDTk",1780070550763]