Implementing an OPC-UA server using open62541
By: Daniel Garcia Coego
Tags: IoT, Beaglebone, Opc Ua, Industry 4 0

In recent years one of the main trends in industrial automation is the arrival of the so-called Industry 4.0 or Fourth Industrial Revolution, a new paradigm in manufacturing. This revolution will imply a change in currently existing manufacturing processes and business models due to the influence of several technologies such as IoT (Internet of Things), Big Data or data analytics. One of these technologies, which is getting more and more attention lately is OPC-UA [1].

OPC-UA, standarized as IEC 62541, is an evolution to the old OPC standard to create a service-oriented architecture that enables interoperability of assets deployed in industrial environments. It also allows to easily connect production systems with tools such as ERP, MES or other business applications. Moreover, OPC-UA is characterized too by several other features such as secure authentication and confidentiality mechanisms, node discovery or an object-based information model to simplify data access. It’s important to highlight that this architecture has not been devised as an alternative to fieldbus technologies such as Modbus or PROFINET (at least until OPC-UA TSN is extended) but as a parallel technology that allows to export shopfloor information to other ambits.

Several industrial manufacturing companies already have in their portfolio systems incorporating this architecture. For example, Siemens already incorporates OPC-UA servers in their SIMATIC S7–1500 controllers [2] or Beckhoff inside their TwinCAT solution [3].

On the other hand, there are several SDK and frameworks available to develop OPC-UA solutions, some of them open-source [4], and between them the libraries developed by the OPC foundation [5]. We have decided to choose open62541 [6] due to its high activity and participation as well as the versatility of the Mozilla v2.0 license. Nevertheless, it’s important to note that some functionalities are not fully supported yet. Inside this link you can find which functionalities are available in the master branch of the project as well as their state of development.

Through the next section we will talk about the basic features of this library while implementing an OPC-UA server programmed in C that deployed in a Beaglebone [7] will allow to read the state of a simple IR break beam sensor.

Programming the OPC-UA server

Getting and compiling the library

First we will ensure that we install all the needed dependencies to compile the library:

$ sudo apt-get install git build-essential gcc pkg-config cmake python python-six

It may be useful too that we install other dependencies that are useful for other features:

$ sudo apt-get install cmake-curses-gui# Needed for CMAKE GUI

$ sudo apt-get install libmbedtls-dev # For encryption

$ sudo apt-get install liburcu-dev # For multithreading

$ sudo apt-get install check # For unit tests

$ sudo apt-get install python-sphinx graphviz # For doc generation

$ sudo apt-get install python-sphinx-rtd-theme # For doc's style

Next we will clone the master branch of the library in a folder of our choice:

To compile the library we will follow the standard procedure with cmake projects:

$ cd open62541

$ mkdir build

$ cd build

$ cmake ..

$ ccmake ..

We must activate the UA_ENABLE_AMALGAMATION option that allows to get the library in two files, open62541.c and open62541.h. For this example it won’t be needed but we can also play activating other library options.

Save this configuration and compile the library:

$ make

After some time the compilation process will end and we will have inside our current directory our two files ready to be used in our OPC-UA project.

Initial project configuration

First of all we will create the adequate directory structure for our project:

$ cd ../../

$ mkdir opcsensor

$ mkdir opcsensor/src opcsensor/lib opcsensor/lib/open62541

Copy the OPC-UA library files previously compiled into the libs directory that we have just created:

$ cp open62541/build/open62541.* opcsensor/lib/open62541/

Finally, we will create the file containing the code for our OPC-UA server:

$ touch opcsensor/src/server.c

Basic server implementation

The next step will be to implement some code for our basic OPC-UA server. First of all we will go to the folder of our project dedicated to source code:

$ cd opcsensor/src/

Then open the server.c file with our favorite code editor and input the following code:

#include <signal.h>

#include "open62541.h" //The library includes several basic types used in the OPC-UA standard. When possible it is best to use these types.

UA_Boolean running = true; //Handler that allows to stop server running when doing a ctrl-c in the command line

static void stopHandler(int sig) {

UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Received ctrl-c");

running = false;

} int main(void) { signal(SIGINT, stopHandler);

signal(SIGTERM, stopHandler); //Create a new server with default configuration

UA_ServerConfig *config = UA_ServerConfig_new_default();

UA_Server *server = UA_Server_new(config); //This line runs the server in a loop while the running variable is true. It's important that initializations and other things done in our code are before this function call.

UA_StatusCode retval = UA_Server_run(server, &running); //When the server stops running we free the resources

UA_Server_delete(server);

UA_ServerConfig_delete(config); return (int)retval;

}

We can verify that the server works as expected by compiling and running the code:

$ gcc -std=c99 -I../lib/open62541 ../lib/open62541/open62541.c server.c -o server

$ ./server

The following step will be to include the code to define our data model, that will be pretty simple for this example as it will be only made of an uint_32 variable to count the number of times an object passes through the IR break beam sensor. Update the server source code with two new methods and a global variable:

#include <signal.h>

#include "open62541.h" UA_Boolean running = true; //Global variable to keep the number of counted objects

int32_t numberOfParts = 0; static void stopHandler(int sig) {

UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Received ctrl-c");

running = false;

} //Function that the server will call each time that the information from the IR break beam sensor is updated

static UA_StatusCode readInteger(UA_Server * server, const UA_NodeId *sessionId,

void *sessionContext, const UA_NodeId * nodeid, void *nodeContext,

UA_Boolean sourceTimeStamp, const UA_NumericRange *range, UA_DataValue *dataValue) { dataValue->hasValue = true;

UA_Int32 myInteger = (UA_Int32) numberOfParts;

UA_Variant_setScalarCopy(&dataValue->value,&myInteger, &UA_TYPES[UA_TYPES_INT32]);

return UA_STATUSCODE_GOOD;

} static void addCounterSensorVariable(UA_Server * server) { //Here we are setting a specific ID for the node but the library can do it if we don't specify it

UA_NodeId counterNodeId = UA_NODEID_NUMERIC(1, 20305); //We specify also the name of the OPC-UA node

UA_QualifiedName counterName = UA_QUALIFIEDNAME(1, "Piece Counter[pieces]"); UA_VariableAttributes attr = UA_VariableAttributes_default;

attr.description = UA_LOCALIZEDTEXT("en_US","Piece Counter (units:pieces)");

attr.displayName = UA_LOCALIZEDTEXT("en_US","Piece Counter");

attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId; //Set the initial value to 0

UA_Int32 counterValue = 0;

UA_Variant_setScalarCopy(&attr.value, &counterValue, &UA_TYPES[UA_TYPES_INT32]); //Include the variable to the server under the root object folder

UA_Server_addVariableNode(server, counterNodeId,

UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),

UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),

counterName, UA_NODEID_NULL, attr, NULL, NULL); } int main(void) { signal(SIGINT, stopHandler);

signal(SIGTERM, stopHandler); UA_ServerConfig *config = UA_ServerConfig_new_default();

UA_Server *server = UA_Server_new(config); //Add the variable for the IR break beam

addCounterSensorVariable(server); UA_StatusCode retval = UA_Server_run(server, &running); UA_Server_delete(server);

UA_ServerConfig_delete(config);

return (int)retval;

}

Now the only step remaining is to physically connect the sensor to our Beaglebone and create the code to monitorize its state.

Connecting the IR break beam sensor

As explained previously, for this small tutorial we are going to use an IR break beam sensor as the one in the picture to detect and count objects. This sensor is in high level until an objects blocks the beam, when the output is low level.

To connect it to the Beaglebone we are going to use GPIO60. Thus, we connect ground to the pin number 1 of P9 and power to pin number 3 of P9. The cable that will return the current value from the sensor will be connected to pin number 12 of P9. Connections can be viewed in the following link or in the picture.

After connecting the sensor to the Beaglebone the code will need to be updated in order to read its value. For that, we will use a parallel thread to the main server that will sample the rading from the GPIO file and update the counter of detected objects (we could also use some of the availables libraries to work with interruptions). The updated code is the following one:

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

#include <signal.h>

#include "open62541.h" //Define the path for GPIO configuration

#define GPIO_EXPORT "/sys/class/gpio/export"

#define GPIO_FILENAME "/sys/class/gpio/gpio60/value" //Define the sampling time for the sensor

#define SLEEP_TIME_MILLIS 50 //Define the ID of the node externally as it will be needed inside the thread

#define COUNTER_NODE_ID 20305 UA_Boolean running = true; int32_t numberOfParts = 0; static void stopHandler(int sig) {

UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Recibido ctrl-c");

running = false;

} //This function allows to export the GPIO at the beginning. It is only needed the first time we boot the board but we will do it always anyway.

void configureGpio() { FILE *fp;

if (fp = fopen(GPIO_EXPORT, "w")){

fprintf(fp,"60");

fclose(fp);

} else {

printf("Error exporting GPIO

");

} } //Thread used to monitorize the sensor

void *mainSensor(void *ptr) { UA_Server * server = ptr; int utime = SLEEP_TIME_MILLIS * 1000;

int currentChar = 0;

bool valueIsZero = false;

FILE *fp; //Update the counter each time the GPIO file value changes from 0 to 1

while (running == 1){

if (fp = fopen(GPIO_FILENAME, "r")){

currentChar = fgetc(fp);

if (currentChar == 48){

if (valueIsZero == false) {

numberOfParts = numberOfParts + 1;

printf("Counter updated: %i

", numberOfParts); //Update the OPC-UA node

UA_Variant value;

UA_Int32 myInteger = (UA_Int32) numberOfParts;

UA_Variant_setScalarCopy(&value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);

UA_Server_writeValue(server, UA_NODEID_NUMERIC(1,COUNTER_NODE_ID), value); valueIsZero = true;

}

} else {

valueIsZero = false;

}

fclose(fp);

} else {

printf("Error reading GPIO

");

}

usleep(utime);

} } static void addCounterSensorVariable(UA_Server * server) { UA_NodeId counterNodeId = UA_NODEID_NUMERIC(1, COUNTER_NODE_ID); UA_QualifiedName counterName = UA_QUALIFIEDNAME(1, "Piece Counter[pieces]"); UA_VariableAttributes attr = UA_VariableAttributes_default;

attr.description = UA_LOCALIZEDTEXT("en_US","Piece Counter (units:pieces)");

attr.displayName = UA_LOCALIZEDTEXT("en_US","Piece Counter");

attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId; UA_Int32 counterValue = 0;

UA_Variant_setScalarCopy(&attr.value, &counterValue, &UA_TYPES[UA_TYPES_INT32]); UA_Server_addVariableNode(server, counterNodeId,

UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),

UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),

counterName, UA_NODEID_NULL, attr, NULL, NULL); } int main(void) { int ret;

pthread_t threadSensor; signal(SIGINT, stopHandler);

signal(SIGTERM, stopHandler); UA_ServerConfig *config = UA_ServerConfig_new_default();

UA_Server *server = UA_Server_new(config); configureGpio(); addCounterSensorVariable(server); //Launch the thread. The OPC-UA server is passed as parameter as the value of the node needs to be updated.

if(pthread_create( &threadSensor, NULL, mainSensor, server)) {

fprintf(stderr,"Error - pthread_create(): %d

",ret);

exit(EXIT_FAILURE);

} UA_StatusCode retval = UA_Server_run(server, &running); UA_Server_delete(server);

UA_ServerConfig_delete(config);

return (int)retval;

}

Compile the server using the pthread library too and run it with root permissions so we don’t have access problems:

$ gcc -std=c99 -I../lib/open62541 ../lib/open62541/open62541.c server.c -o server -lpthread -lrt $ sudo ./server

To verify that everything is working OK we could also implement our own OPC-UA client with the open62541 library but in this case we will use the UaExpert application (it’s free but registration is needed to download it).

In the main window we must add first the server that we want to monitorize. For that we will press the button with a plus sign in the toolbar.

A dialog will appear and we will double-clink the “Custom Discovery” option, which will open a new dialog where the IP and port of our Beaglebone will need to be introduced, as showed in the next picture.

Next, we expand the new field representing the newly discovered server and we select it, pressing OK to end the process and add the server to the list.

After this, we can see the objects included in our OPC-UA server after connecting to it (right cliver over the server->connect). They will appear on the left side of the screen. In this case, the “Pieces counter” and “Server” nodes show up (“Server” node is created by the library and includes information from the server as its own name implies). The current value of the node will be showed on the right side of the screen inside the “Attributes” panel.

And with these simple steps we will have a quite simple OPC-UA server working that updates the value of the created node when an object is detected by the IR break beam sensor.

Conclusions and future developments

Through this article we have made an introduction to one of the more promissing technologies in Industry 4.0 through the implementation of a simple server to read data from sensors connected to a Beaglebone.

This server can be improved in several ways to reach an application nearer to its application in a real environment. Some of the points that can be improved are the following:

Authentication configuration. With this code anyone could access our servers simply knowing its IP address and port. The open62541 library has implemented some of the OPC-UA standard authentication features that could be used to protect access to the server.

Transport encryptio. Data is transmitted unencrypted so anyone with access to the network where data is moving could see the content of the OPC-UA messages. In the last version of the library encryption features were added so they could be configured to protect transmitted information.

Use subscriptions. Thus, polling wouldn’t need to be used to check the state of the node as the server will take responsability to publish date when the value of the counter changes.

Send the information to business servers or to the cloud. The open62541 library has not implemented this functionality yet but it could be used to benefit from Big Data or data analytics tools (although this example has been so simplified that using them won’t make any sense).

At Gradiant, we are working with this technology in order to improve industrial processes. For that, we are developing OPC-UA that are easily deployed and configured by using container technologies. Also we are working to improve connectivity and security to transmit shopfloor information through an industrial data bus. This data bus, based on AMQP, allows increasing process visibility, eliminating the traditional existing information silos in industrial environments.Este bus de datos permite aumentar la visibilidad de los procesos industriales, eliminando los tradicionales silos de información existentes en los mismos. This data bus is designed also so an ecosystem for storing, analyzing and visualizing data is deployed fast and efficiently through the use of the ELK stack, enabling also the use of other services and technologies when needed.

In the future, in Gradiant we expect to improve our current technologies in several points such as automatic instantiation of OPC-UA nodes and servers depening on the specific needs of the use case, the application of machine learning techniques to extract knowledge from data in the edge as well as to improve security through the analysis of intrusions in industrial systems, dynamically modify industrial networks topologies or apply new technologies such as blockchain or smart contracts to the OPC-UA architecture.

References

[1] https://opcfoundation.org/about/opc-technologies/opc-ua/

[2] http://w3.siemens.com/mcms/automation-software/en/tia-portal-software/step7-tia-portal/simatic-step7-options/opc-ua-s7-1500/pages/default.aspx

[3] https://www.beckhoff.com/english.asp?twincat/twincat_opc_ua_server.htm

[4] https://github.com/open62541/open62541/wiki/List-of-Open-Source-OPC-UA-Implementations

[5] https://github.com/OPCFoundation

[6] https://open62541.org/

[7] https://beagleboard.org/black

Article originally published in spanish in: https://www.gradiant.org/blog/desarrollando-servidor-opc-ua-open62541/