The development of OPC UA applications takes currently a lot of effort. This is caused by the large possibilities of the OPC UA specification. With this implementation we want to define some conventions, which shoud make the technology more useable.
The idea of the opcua-smart library is to simplify the OPC UA application generation. Since OPC UA has more than 1500 pages of basic specifications, and the number is still growing, we decided to make some simplification.
This is done by some constraints regarding the modeling functionality of OPC UA. This library deliberately does not offer all functions of OPC UA to simplify the creation of applications.
Copyright (C) 2019-* Jürgen "eTM" Mangler juergen.mangler@gmail.com. opcua-smart is freely distributable according to the terms of the GNU Lesser General Public License 3.0 (see the file 'COPYING'). This code is distributed without any warranty. See the file 'COPYING' for details.
Tested for for Ubuntu >= 21.04 and Fedora >= 33.
# Debian/Ubuntu
sudo add-apt-repository ppa:open62541-team/ppa
sudo apt-get update
sudo apt-get install build-essential libopen62541-1-dev
# Fedora/Redhat
sudo dnf install @buildsys-build @development-tools open62541-devel
After that as user
gem install --user opcua
If open62541 is not included in your distribution, then you can compile and install from source (e.g. if you are running on an old LTS version of ubuntu).
# Debian/Ubuntu
sudo apt install build-essential cmake-curses-gui libmbedtls-dev libxml2-dev libxslt-dev libz-dev libssl-dev libicu-dev
# Fedora/Redhat
sudo dnf install @buildsys-build @development-tools cmake libxml2-devel libxslt-devel zlib-devel libicu-devel mbedtls-devel
Dependency: https://github.com/open62541/open62541 > 1.1 (master branch as of 2020-06-04)
git clone https://github.com/open62541/open62541.git
cd open62541
mkdir build && cd build
cmake -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUA_ENABLE_AMALGAMATION=ON -DUA_ENABLE_ENCRYPTION=ON -DUA_ENABLE_ENCRYPTION_MBEDTLS=ON ..
make
sudo make install
gem install --user rake opcua
If you get errors during compilation, please file an issue in github. Maybe the API open62541 API changed (constant improvements are happening).
If the installation works correctly, but examples are still complaining about missing lib62541.so, try this:
sudo echo "/usr/local/lib" >> /etc/ld.so.conf.d/local.conf # add to libs path
sudo echo "/usr/local/lib64" >> /etc/ld.so.conf.d/local.conf # add to libs path
sudo ldconfig # update libs
sudo ldconfig -p | grep libopen62541 # check if its there
The server has following functions:
- Create the server and add_namespace
- Create ObjectTypes
- Manifest ObjectTypes
- Delete Objects
- Find nodes in the adress space
- Loop for getting real life data
Every server application uses the Daemonite gem, which allows to run the server as service.
Daemonite.new do
on startup do |opts|
...
end
run do |opts|
...
end
on exit do
...
end
end.loop!
Each server has 3 sections the startup, run, and exit. In the startup we create the server and the namespace, define all nodes and typically manifest the adress space. The run section loops and therefore updates the values of the nodes in the server. On exit we can d additionally things e.g. close the connection to another interface.
server = OPCUA::Server.new
server.add_namespace "https://yourdomain/testserver"
Basically all new created types are subtypes of the BaseObjectType. With server.types.add_object_type(:TestObjectType)
a new type is defined in the information model. All nodes of the new created type are defined in the tap{}
region.
to = server.types.add_object_type(:TestObjectType).tap{ |t|
t.add_variable :TestVariable
t.add_object(:TestObject, server.types.folder).tap{ |u|
u.add_object :M, mt, OPCUA::OPTIONAL
}
t.add_method :TestMethod, inputarg1: OPCUA::TYPES::STRING, inputarg2: OPCUA::TYPES::DATETIME do |node, inputarg1, inputarg2|
#do some stuff here
end
}
In this example the TestObjectType is defined. It consits of TestVariable of the BaseVariableType an TestObject of the FolderType and a TestMethod.
The .add_variable :TestVariable
command adds a variable with the name TestVariable.
Multible variables can be defined at once with the .add_variables
command.
t.add_variables :TestVar1, :TestVar2
By default variables are read-only.
If you want to add a variable with read/write support you must use the .add_variable_rw
method.
t.add_variable_rw :TestVar1
It is also possible to add a block to the variable. The block is called whenever the variable is changed. Each block gets three parameters passed: nodeid, new value, and if the value has been changed through a client (true) or internally in the server (false).
t.add_variable_rw :Value do |node,value,external|
p node.id # ObjectVarNode object, which has properties such as #id, #value
p value # new content of the variable
p external # true || false
end
With .add_object(:TestObject)
a new object named TestObject is added. The second parameter is optional and definies of which type the new object is. Default the object is from BaseObjectType. In this example the created object is from FolderType. All child nodes of the object can be definded in the tap{}
area.
Methods are added with the .add_method(:TestMethod)
function. Per default the method has no input and output arguments. By adding additional arguments you can define input arguments. The code for defining a method with input arguments looks like
t.add_method :TestMethod, inputarg1: OPCUA::TYPES::STRING, inputarg2: OPCUA::TYPES::DATETIME do |node, inputarg1, inputarg2|
#do some stuff here
end
Input arguments can have a name and a type.
in the do...end
section you write the code which should be executed by calling the method.
ObjectTypes can be instiantiated with the .manifest
method.
testobject =server.objects.manifest(:TestObjectType, to)
Objects can be deleted witch the .delete!
function.
testobject =server.objects.manifest(:TestObjectType, to)
testobject.delete!
To get a specific node you should use the .find
method.
tv = to.find :TestVariable
tv is now the TestVariable node.
You can also find several nodes at the same time.
tva = to.find :TestVariable1, :TestVariable2
tva is now a array containing the requested nodes.
tv1, tv2 = to.find :TestVariable1, :TestVariable2
You can also request several nodes with one find statement.
To get the value of a specific node use the .value
method.
tv.value = 10
tv.value = 'ten'
puts tv.value
You can assign vlaues without definig a datatype. The correct DataType will be used. Default we use _UA::STRING, UA::DOUBLE and UA::INT. Additional Datatypes can be added by request.
The server loop looks like follows:
run do
sleep server.run
to.value = 'Testvariable1'
p to.value
rescue => e
puts e.message
end
The loop starts with sleep server.run
. This is recommended by the open62541 developer. With the .value
function you can write or get the value of a node.
TBD. See examples subdirectory.