OPC UA Protocol with Python and Node-Red

Industrial operations such as chemical refineries, power plants and mineral processing operations have quite different communications requirements than most IT installations. Some of the key industrial communications requirements include: security, multi-vendor connectivity, time tagging and quality indications.

To meet industrial requirements a communications standard called OPC (OLE for Process Control) was created. The original OPC design was based on Microsoft’s Object Linking and Embedding (OLE) and it quickly became the standard for communications between control systems consoles, historians and 3rd party applications.

The original OPC standard worked well but it had major limitations in the areas of Linux/embedded systems, routing across WANs, and new security concerns. To better address new industrial requirements the OPC UA, (Open Platform Communications Unified Architecture) standard was created.

In this article I will create an OPC UA server that will collect sensor data using Python and Node-Red, and the results will be shown in a Node-Red web dashboard.

Install Python OPC UA Server

There are a number of OPC UA open source servers to choose from.

For “C” development applications see the Open62541 project (https://open62541.org/), it offers a C99 architecture that runs on Windows, Linux, VxWorks, QNX, Android and a number of embedded systems.

For light weight quick testing OPC UA servers are available in Python and Node-Red.

The Free OPC-UA Library Project (https://github.com/FreeOpcUa) has a great selection of open source tools for people wishing to learn and play with OPC UA.

I keep things a little simple I will be using the python-opcua library which is a pure Python OPC-UA Server and client. (Note: a more complete Python OPCUA library, https://github.com/FreeOpcUa/opcua-asyncio, is available for more detailed work). Also an OPC-UA browser is a useful tool for monitoring OPC UA server and their tags. To load both of these libraries:

# Install the pure Python OPC-UA server and client
sudo apt install python-opcua
# Install the OPC UA client and the QT dependencies
sudo apt install PyQT*
pip3 install opcua-client

Simple Python OPC-UA Server

As a first project a simple OPC-UA server will be created to add OPC-UA tags and then simulate values.

The first step in getting this defined is to set an endpoint or network location where the OPC-UA server will be accessed from.

The default transport for OPC-UA is opc.tcp. The Python socket library can be used to determine a node’s IP address. (To simplify my code I also hard coded my IP address, opc.tcp://192.168.0.120:4841).

The OPC-UA structure is based on objects and files, and under an object or file tags are configured. Tags by default have properties like value, time stamp and status information, but other properties like instrument or alarm limits can be added.

Once a tag object is define, the set_value function is used to simulate the tag values.

# opcua_server1.py - Create an OPC UA server and simulate 2 tags
#
import opcua
import random
import time
 
s = opcua.Server()
s.set_server_name("OpcUa Test Server")
s.set_endpoint("opc.tcp://192.168.0.120:4841")
  
# Register the OPC-UA namespace
idx = s.register_namespace("http://192.168.0.120:4841")
# start the OPC UA server (no tags at this point)  
s.start() 
  
objects = s.get_objects_node()
# Define a Weather Station object with some tags
myobject = objects.add_object(idx, "Station")
  
# Add a Temperature tag with a value and range
myvar1 = myobject.add_variable(idx, "Temperature", 25)
myvar1.set_writable(writable=True)
  
# Add a Windspeed tag with a value and range
myvar2 = myobject.add_variable(idx, "Windspeed", 11)
myvar2.set_writable(writable=True)
 
# Create some simulated data
while True:
    myvar1.set_value(random.randrange(25, 29))
    myvar2.set_value(random.randrange(10, 20))
    time.sleep(5)

The status of the OPC-UA server can be checked using the OPC-UA browser:

# start the Python OPC-UA browser client
opcua-client

Items within an OPC-UA server are defined by their name space index (ns) and their object index. The name space index is returned after an name space is register. An object’s index is defined when a new object is create. For this example the Windspeed tag has a NodeId of “ns-2;i=5”, or an index 5 on name space 2.

The opcua-client application can view real-time changes to a tag’s value using the subscription option.

In OPC the terms “tags” and “variables” are often used interchangeably. In the instrument lists the hardware signals are usually referred to as “tags”, but within the OPC UA server the term “variables” is used. The key difference is that a variable can also be an internal or soft point such as a counter.

Python OPC-UA Client App

For my Python client application I loaded up a simple gauge library (https://github.com/slightlynybbled/tk_tools):

pip install tk_tools

The Python client app (station1.py) defines an OPC-UA client connection and then it uses the NodeId definition of the Temperature and Windspeed tags to get their values:

# station1.py - Put OPC-UA data into gauges 
#
import tkinter as tk
import tk_tools
import opcua

# Connect to the OPC-UA server as a client
client = opcua.Client("opc.tcp://192.168.0.120:4841")
client.connect()

root = tk.Tk()
root.title("OPC-UA Weather Station 1")

# Create 2 gauge objects
gtemp = tk_tools.Gauge(root, height = 200, width = 400,
            max_value=50, label='Temperature', unit='°C')
gtemp.pack()
gwind = tk_tools.Gauge(root, height = 200, width = 400,
            max_value=100, label='Windspeed', unit='kph') 
gwind.pack()

def update_gauge():
    # update the gauges with the OPC-UA values every 1 second
    gtemp.set_value(client.get_node("ns=2;i=2").get_value())
    gwind.set_value(client.get_node("ns=2;i=5").get_value())
    root.after(1000, update_gauge)

root.after(500, update_gauge)

root.mainloop()

XML Databases

In the earlier Python OPC-UA server example tags were dynamically added when the server was started. This method works fine for simple testing but it can be awkward for larger tag databases.

All industrial control vendors will have proprietary solutions to create OPC-UA tag databases from process control logic.

Users can also create their own tag databases using XML. The OPC-UA server tag database can be imported and exported to XML using the commands:

# to export from the online system to an XML file:
# where: s = opcua.Server()
s.export_xml_by_ns("mytags.xml")
# to import an XML file:
s.import_xml("mytags2.xml","")

The XML files can be viewed in a web browser, and unfortunately the format is a little ugly. The XML files have a header area with a large number of options.The Name Space Uris is the custom area that defines the OPC UA end point address.

After the header there are object and variable definitions (<AUVariable>). In these section the variable’s NodeID, tag name and description are defined.

The Free OPC-UA modeler that can help with the creation of XML tag databases. To install and run the Free OPC-UA modeler:

$ pip install opcua-modeler
$ opcua-modeler

The OPC-UA modeler will read existing XML files and then allow for objects, tags and properties to be inserted into the XML structure.

CSV to XML

A CSV file is an easy format for defining tag databases. For example a file mytags.csv could be defined with 3 fields; tagname, description and default value.

$ cat mytags.csv
# field: tag, description, default-value
TI-101,temperature at river, 25
PI-101,pressure at river, 14

A basic CSV to XML import tool can be created to meet your project requirements. There are a number of good programming options to do this migration. For my project I created a small Bash/AWK program to translate the 3 field CSV file to the required OPC-UA XML format.

The first awk section prints out the header information. The second awk section reads the input (CSV) text line by line and pulls out each of the three fields ($1, $2 and $3) and prints out the XML with these fields inserted in the output.

#!/usr/bin/bash
# csv2xml.sh - create an OPC UA XML file from CSV
# 
 
# add the xml header info
awk ' BEGIN {
  print "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
  print "<UANodeSet xmlns=\"http://opcfoundation.org/UA/2011/03/UANodeSet.xsd\"" 
  print "           xmlns:uax=\"http://opcfoundation.org/UA/2008/02/Types.xsd\""
  print "           xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"" 
  print "           xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
  print "<NamespaceUris>"
  print "  <Uri>http://192.168.0.120:4841</Uri>" ; # This address would be passed in
  print "</NamespaceUris>"
}'

# Read the input CSV format and process to XML
awk ' {
   FS="," ; # separate fields with a comma
# Skip any comment lines that start with a #
  if ( substr($1,1,1) != "#" )
  {
    i = i+1 ; # increment the NodeID index
    print "<UAVariable BrowseName=\"1:"$1"\" DataType=\"Int32\" NodeId=\"ns=1;i="i"\" ParentNodeId=\"i=85\">"
    print "  <DisplayName>"$1"</DisplayName>" ; # set the display name to the 1st field
    print "  <Description>"$2"</Description>" ; # set the description to the 2nd field
    print "      <References>"
    print "        <Reference IsForward=\"false\" ReferenceType=\"HasComponent\">i=85</Reference>"
    print "      </References>"
    print "    <Value>"
    print "      <uax:Int32>"$3"</uax:Int32>" ; # set the default value to the 3rd field
    print "    </Value>"
    print "</UAVariable>"
  }   
}
END{ print "</UANodeSet>"} '

To run this script to read a CSV file (mytags.csv) and create an XML file (mytags.xml) :

cat mytags.csv | ./csv2xml.sh > mytags.xml

Node-Red OPC UA Server

There is a good OPC UA node (https://flows.nodered.org/node/node-red-contrib-opcua) that includes a server and most common OPC UA features. This node can be install within Node-Red using the “Manage Palette” option.

To setup a Node-Red OPC UA server and a browser, define a OPCUA server node to use the Node Red IP address and set a custom nodeset directory. For my example I set the directory to /home/pi/opcua and in this directory I copied the XML file that I created from CSV (mytags.xml) into.

The OPCUA Browser node will send messages directly into the debug pane. This browse node allows me to see the objects/variables that I defined in my XML file.

The next step is to look at writing and reading values.

The simplest way to communicate with an OPC UA server is to use an OpcUa Item node to define the NodeID and an OpcUa Client node to do some action. For the OpcUa Client node the End point address and an action needs to be defined.

In this example the pressure (PI-101) has a NodeID of “ns=5;i=2”, and this string is entered into the OpcUA item node. The OpcUA Client node uses a Write action. When a Write action is issued a Good or Bad status message is returned.

The OpcUa Client node supports a number of different actions. Rather than doing a Read action like in the Python client app, a Subscribe can be used. A Subscribe action will return a value whenever the value changes.

NodeRed Dashboards with the Python OPC UA Server

For the last example I will use the Python OPC UA server from the first example. The Temperature and WindSpeed will use the same simulation code, but an added Waveheight tag will be a manually entered value from Node-Red.

A Node-Red application that connects to the Python OPC UA server and presents that data in a Node-Red dashboard would be:

This example subscribes to two real-time inputs (Temperature and Windspeed) and presents the values in gauges. The OpcUA Item nodes define the OPC UA NodeId’s to be used.

All the OpcUa Client nodes will need their Endpoints defined to the Python OPC UA server address.

The subscribed data values are returned as a 2 item array (because the data type is a Int64). The Gauge node will only read the first payload array item, (which is 0) so a small function node copies the second payload item (msg.payload[1]) to the payload message:

// Copy the second payload array item to be the payload
//  Note: msg.payload[0] = 0 and the Dashboard Gauge needs to use the value at payload[1]
msg.payload = msg.payload[1]
return msg;

For this example a manual input was included. The WaveHeight is subscribed to like the other tags, and the slider position is updated to its value. The slider can also be used to manually set the value by having the slider output passed to an OpcUa Client node with a WRITE action.

After the logic is complete the Deploy button will make the application live. The Node-Red dashboard can be viewed at: http://node-red-ip:1880/ui

Final Comments

This is a quick and dirty set of examples on how to use Python and Node-Red with OPC UA.

OPC UA has a ton of other features that can be implemented like : Alarms and Events and Historical Data.

Also it should be noted that most high end OPC UA servers support accessing the OPC UA items via their browse names. So instead of accessing a point using “ns=5;i=6” a browser name string can be used, such as “ns=5;s=MYTAGNAME”.