Building open62541¶
open62541 uses CMake to build the library and binaries. The library version is automatically detected using git describe. This command returns a valid version string based on the current tag. If you did not directly clone the sources, but use the tar or zip package from a release, you need to manually specify the version. In that case use e.g. cmake -DOPEN62541_VERSION=v1.0.3.
Building the Library¶
Building with CMake on Ubuntu or Debian¶
sudo apt-get install git build-essential gcc pkg-config cmake python
# enable additional features
sudo apt-get install cmake-curses-gui # for the ccmake graphical interface
sudo apt-get install libmbedtls-dev # for encryption support
sudo apt-get install check libsubunit-dev # for unit tests
sudo apt-get install python-sphinx graphviz # for documentation generation
sudo apt-get install python-sphinx-rtd-theme # documentation style
cd open62541
mkdir build
cd build
cmake ..
make
# select additional features
ccmake ..
make
# build documentation
make doc # html documentation
make doc_pdf # pdf documentation (requires LaTeX)
Building with CMake on Windows¶
Here we explain the build process for Visual Studio (2013 or newer). To build with MinGW, just replace the compiler selection in the call to CMake.
- Download and install
- Python 2.7.x (Python 3.x works as well): https://python.org/downloads
- CMake: http://www.cmake.org/cmake/resources/software.html
- Microsoft Visual Studio: https://www.visualstudio.com/products/visual-studio-community-vs
- Download the open62541 sources (using git or as a zipfile from github)
- Open a command shell (cmd) and run
cd <path-to>\open62541
mkdir build
cd build
<path-to>\cmake.exe .. -G "Visual Studio 14 2015"
:: You can use use cmake-gui for a graphical user-interface to select features
- Then open buildopen62541.sln in Visual Studio 2015 and build as usual
Building on OS X¶
- Download and install
- Xcode: https://itunes.apple.com/us/app/xcode/id497799835?ls=1&mt=12
- Homebrew: http://brew.sh/
- Pip (a package manager for python, may be preinstalled): sudo easy_install pip
- Run the following in a shell
brew install cmake
pip install sphinx # for documentation generation
pip install sphinx_rtd_theme # documentation style
brew install graphviz # for graphics in the documentation
brew install check # for unit tests
Follow Ubuntu instructions without the apt-get commands as these are taken care of by the above packages.
Building on OpenBSD¶
The procedure below works on OpenBSD 5.8 with gcc version 4.8.4, cmake version 3.2.3 and Python version 2.7.10.
- Install a recent gcc, python and cmake:
pkg_add gcc python cmake
- Tell the system to actually use the recent gcc (it gets installed as egcc on OpenBSD):
export CC=egcc CXX=eg++
- Now procede as described for Ubuntu/Debian:
cd open62541
mkdir build
cd build
cmake ..
make
Building Debian Packages inside Docker Container with CMake on Ubuntu or Debian¶
Here is an example howto build the library as Debian package inside a Docker container
- Download and install
- Docker Engine: https://docs.docker.com/install/linux/docker-ce/debian/
- docker-deb-builder: https://github.com/tsaarni/docker-deb-builder.git
- open62541: https://github.com/open62541/open62541.git
Install Docker as described at https://docs.docker.com/install/linux/docker-ce/debian/ .
Get the docker-deb-builder utility from github and make Docker images for the needed Debian and/or Ubuntu relases
# make and goto local development path (e.g. ~/development)
mkdir ~/development
cd ~/development
# clone docker-deb-builder utility from github and change into builder directory
git clone https://github.com/tsaarni/docker-deb-builder.git
cd docker-deb-builder
# make Docker builder images (e.g. Ubuntu 18.04 and 17.04)
docker build -t docker-deb-builder:18.04 -f Dockerfile-ubuntu-18.04 .
docker build -t docker-deb-builder:17.04 -f Dockerfile-ubuntu-17.04 .
Make a local copy of the open62541 git repo and checkout a pack branch
# make a local copy of the open62541 git repo (e.g. in the home directory)
# and checkout a pack branch (e.g. pack/1.0)
cd ~
git clone https://github.com/open62541/open62541.git
cd ~/open62541
git checkout pack/1.0
Now it’s all set to build Debian/Ubuntu open62541 packages
# goto local developmet path
cd ~/development
# make a local output directory for the builder where the packages can be placed after build
mkdir output
# build Debian/Ubuntu packages inside Docker container (e.g. Ubuntu-18.04)
./build -i docker-deb-builder:18.04 -o output ~/open62541
After a successfull build the Debian/Ubuntu packages can be found at ~/development/docker-deb-builder/output
CMake Build Options and Debian Packaging¶
If the open62541 library will be build as a Debian package using a pack branch (e.g. pack/master or pack/1.0) then altering or adding CMake build options should be done inside the debian/rules file respectively in the debian/rules-template file if working with a development branch (e.g. master or 1.0).
The section in debian/rules where the CMake build options are defined is
...
override_dh_auto_configure:
dh_auto_configure -- -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUA_NAMESPACE_ZERO=FULL -DUA_ENABLE_AMALGAMATION=OFF -DUA_PACK_DEBIAN=ON
...
This CMake build options will be passed as command line variables to CMake during Debian packaging.
Build Options¶
The open62541 project uses CMake to manage the build options, for code generation and to generate build projects for the different systems and IDEs. The tools ccmake or cmake-gui can be used to graphically set the build options.
Most options can be changed manually in ua_config.h (open62541.h for the single-file release) after the code generation. But usually there is no need to adjust them.
Main Build Options¶
- CMAKE_BUILD_TYPE
- RelWithDebInfo -O2 optimization with debug symbols
- Release -O2 optimization without debug symbols
- Debug -O0 optimization with debug symbols
- MinSizeRel -Os optimization without debug symbols
- UA_LOGLEVEL
The SDK logs events of the level defined in UA_LOGLEVEL and above only. The logging event levels are as follows:
- 600: Fatal
- 500: Error
- 400: Warning
- 300: Info
- 200: Debug
- 100: Trace
Select build artefacts¶
By default only the main library shared object libopen62541.so (open62541.dll) or static linking archive open62541.a (open62541.lib) is built. Additional artifacts can be specified by the following options:
- UA_BUILD_EXAMPLES
- Compile example servers and clients from examples/*.c.
- UA_BUILD_UNIT_TESTS
- Compile unit tests. The tests can be executed with make test
- UA_BUILD_SELFSIGNED_CERTIFICATE
- Generate a self-signed certificate for the server (openSSL required)
Detailed SDK Features¶
- UA_ENABLE_SUBSCRIPTIONS
- Enable subscriptions
- UA_ENABLE_SUBSCRIPTIONS_EVENTS (EXPERIMENTAL)
- Enable the use of events for subscriptions. This is a new feature and currently marked as EXPERIMENTAL.
- UA_ENABLE_METHODCALLS
- Enable the Method service set
- UA_ENABLE_NODEMANAGEMENT
- Enable dynamic addition and removal of nodes at runtime
- UA_ENABLE_AMALGAMATION
- Compile a single-file release into the files open62541.c and open62541.h. Not recommended for installation.
- UA_ENABLE_MULTITHREADING (EXPERIMENTAL)
- Enable multi-threading support. Work is distributed to a number of worker threads. This is a new feature and currently marked as EXPERIMENTAL.
- UA_ENABLE_IMMUTABLE_NODES
- Nodes in the information model are not edited but copied and replaced. The replacement is done with atomic operations so that the information model is always consistent and can be accessed from an interrupt or parallel thread (depends on the node storage plugin implementation). This feature is a prerequisite for UA_ENABLE_MULTITHREADING.
- UA_ENABLE_COVERAGE
- Measure the coverage of unit tests
- UA_ENABLE_DISCOVERY
- Enable Discovery Service (LDS)
- UA_ENABLE_DISCOVERY_MULTICAST
- Enable Discovery Service with multicast support (LDS-ME)
- UA_ENABLE_DISCOVERY_SEMAPHORE
- Enable Discovery Semaphore support
UA_NAMESPACE_ZERO
Namespace zero contains the standard-defined nodes. The full namespace zero may not be required for all applications. The selectable options are as follows:
- MINIMAL: A barebones namespace zero that is compatible with most clients. But this namespace 0 is so small that it does not pass the CTT (Conformance Testing Tools of the OPC Foundation).
- REDUCED: Small namespace zero that passes the CTT.
- FULL: Full namespace zero generated from the official XML definitions.
The advanced build option UA_FILE_NS0 can be used to override the XML file used for namespace zero generation.
Some options are marked as advanced. The advanced options need to be toggled to be visible in the cmake GUIs.
- UA_ENABLE_TYPENAMES
- Add the type and member names to the UA_DataType structure. Enabled by default.
- UA_ENABLE_STATUSCODE_DESCRIPTIONS
- Compile the human-readable name of the StatusCodes into the binary. Enabled by default.
- UA_ENABLE_FULL_NS0
- Use the full NS0 instead of a minimal Namespace 0 nodeset UA_FILE_NS0 is used to specify the file for NS0 generation from namespace0 folder. Default value is Opc.Ua.NodeSet2.xml
Debug Build Options¶
This group contains build options mainly useful for development of the library itself.
- UA_DEBUG
- Enable assertions and additional definitions not intended for production builds
- UA_DEBUG_DUMP_PKGS
- Dump every package received by the server as hexdump format
Minimizing the binary size¶
The size of the generated binary can be reduced considerably by adjusting the build configuration. With open2541, it is possible to configure minimal servers that require less than 100kB of RAM and ROM.
The following options influence the ROM requirements:
First, in CMake, the build type can be set to CMAKE_BUILD_TYPE=MinSizeRel. This sets the compiler flags to minimize the binary size. The build type also strips out debug information. Second, the binary size can be reduced by removing features via the build-flags described above.
Second, setting UA_NAMESPACE_ZERO to MINIMAL reduces the size of the builtin information model. Setting this option can reduce the binary size by half in some cases.
Third, some features might not be needed and can be disabled to reduce the binary footprint. Examples for this are Subscriptions or encrypted communication.
Last, logging messages take up a lot of space in the binary and might not be needed in embedded scenarios. Setting UA_LOGLEVEL to a value above 600 (FATAL) disables all logging. In addition, the feature-flags UA_ENABLE_TYPENAMES and UA_ENABLE_STATUSCODE_DESCRIPTIONS add static information to the binary that is only used for human-readable logging and debugging.
The RAM requirements of a server are mostly due to the following settings:
- The size of the information model
- The number of connected clients
- The configured maximum message size that is preallocated
Building the Examples¶
Make sure that you can build the shared library as explained in the previous steps. Even easier way to build the examples is to install open62541 in your operating system (see Installing open62541).
Then the compiler should automatically find the includes and the shared library.
cp /path-to/examples/tutorial_server_firststeps.c . # copy the example server
gcc -std=c99 -o server tutorial_server_firststeps.c -lopen62541
Building for specific architectures¶
The open62541 library can be build for many operating systems and embedded systems. This document shows a small excerpt of already tested architectures. Since the stack is only using the C99 standard, there are many more supported architectures.
A full list of implemented architecture support can be found in the arch folder.
Windows, Linux, MacOS¶
These architectures are supported by default and are automatically chosen by CMake.
Have a look into the previous sections on how to do that.
freeRTOS + LwIP¶
Credits to @cabralfortiss
This documentation is based on the discussion of the PR https://github.com/open62541/open62541/pull/2511. If you have any doubts, please first check the discussion there.
This documentation assumes that you have a basic example using LwIP and freeRTOS that works fine, and you only want to add an OPC UA task to it.
There are two main ways to build open62541 for freeRTOS + LwIP:
- Select the cross compiler in CMake, set the flags needed for compilation (different for each microcontroller so it can be difficult) and then run make in the folder and the library should be generated. This method can be hard to do because you need to specify the include files and some other configurations.
- Generate the open6254.h and open6254.c files with the freeRTOSLWIP architecture and then put these files in your project in your IDE that you’re using for compiling. This is the easiest way of doing it and the documentation only focus on this method.
In CMake, select freertosLWIP using the variable UA_ARCHITECTURE, enable amalgamation using the UA_ENABLE_AMALGAMATION variable and just select the native compilers. Then try to compile as always. The compilation will fail, but the open62541.h and open62541.c will be generated.
NOTE: If you are using the memory allocation functions from freeRTOS (pvPortMalloc and family) you will need also to set the variable UA_ARCH_FREERTOS_USE_OWN_MEMORY_FUNCTIONS to true. Many users had to implement pvPortCalloc and pvPortRealloc.
If using the terminal, the command should look like this
mkdir build_freeRTOS
cd build_freeRTOS
cmake -DUA_ARCHITECTURE=freertosLWIP -DUA_ENABLE_AMALGAMATION=ON ../
make
Remember, the compilation will fail. That’s not a problem, because you need only the generated files (open62541.h and open62541.c) found in the directory where you tried to compile. Import these in your IDE that you’re using. There is no standard way of doing the following across all IDEs, but you need to do the following configurations in your project:
- Add the open62541.c file for compilation
- Add the variable UA_ARCHITECTURE_FREERTOSLWIP to the compilation
- Make sure that the open62541.h is in a folder which is included in the compilation.
When compiling LwIP you need a file called lwipopts.h. In this file, you put all the configuration variables. You need to make sure that you have the following configurations there:
#define LWIP_COMPAT_SOCKETS 0 // Don't do name define-transformation in networking function names.
#define LWIP_SOCKET 1 // Enable Socket API (normally already set)
#define LWIP_DNS 1 // enable the lwip_getaddrinfo function, struct addrinfo and more.
#define SO_REUSE 1 // Allows to set the socket as reusable
#define LWIP_TIMEVAL_PRIVATE 0 // This is optional. Set this flag if you get a compilation error about redefinition of struct timeval
For freeRTOS there’s a similar file called FreeRTOSConfig.h. Usually, you should have an example project with this file. The only two variables that are recommended to check are:
#define configCHECK_FOR_STACK_OVERFLOW 1
#define configUSE_MALLOC_FAILED_HOOK 1
Most problems when running the OPC UA server in freeRTOS + LwIP come from the fact that is usually deployed in embedded systems with a limited amount of memory, so these definitions will allow checking if there was a memory problem (will save a lot of effort looking for hidden problems).
Now, you need to add the task that will start the OPC UA server.
static void opcua_thread(void *arg){
//The default 64KB of memory for sending and receicing buffer caused problems to many users. With the code below, they are reduced to ~16KB
UA_UInt32 sendBufferSize = 16000; //64 KB was too much for my platform
UA_UInt32 recvBufferSize = 16000; //64 KB was too much for my platform
UA_UInt16 portNumber = 4840;
UA_Server* mUaServer = UA_Server_new();
UA_ServerConfig *uaServerConfig = UA_Server_getConfig(mUaServer);
UA_ServerConfig_setMinimal(uaServerConfig, portNumber, 0, sendBufferSize, recvBufferSize);
//VERY IMPORTANT: Set the hostname with your IP before starting the server
UA_ServerConfig_setCustomHostname(uaServerConfig, UA_STRING("192.168.0.102"));
//The rest is the same as the example
UA_Boolean running = true;
// add a variable node to the adresspace
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_Int32 myInteger = 42;
UA_Variant_setScalarCopy(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
attr.description = UA_LOCALIZEDTEXT_ALLOC("en-US","the answer");
attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US","the answer");
UA_NodeId myIntegerNodeId = UA_NODEID_STRING_ALLOC(1, "the.answer");
UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME_ALLOC(1, "the answer");
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_Server_addVariableNode(mUaServer, myIntegerNodeId, parentNodeId,
parentReferenceNodeId, myIntegerName,
UA_NODEID_NULL, attr, NULL, NULL);
/* allocations on the heap need to be freed */
UA_VariableAttributes_clear(&attr);
UA_NodeId_clear(&myIntegerNodeId);
UA_QualifiedName_clear(&myIntegerName);
UA_StatusCode retval = UA_Server_run(mUaServer, &running);
UA_Server_delete(mUaServer);
}
In your main function, after you initialize the TCP IP stack and all the hardware, you need to add the task:
//8000 is the stack size and 8 is priority. This values might need to be changed according to your project
if(NULL == sys_thread_new("opcua_thread", opcua_thread, NULL, 8000, 8))
LWIP_ASSERT("opcua(): Task creation failed.", 0);
And lastly, in the same file (or any actually) add:
void vApplicationMallocFailedHook(){
for(;;){
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName ){
for(;;){
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
And put a breakpoint in each of the vTaskDelay. These functions are called when there’s a problem in the heap or the stack. If the program gets here, you have a memory problem.
That’s it. Your OPC UA server should run smoothly. If not, as said before, check the discussion in https://github.com/open62541/open62541/pull/2511. If you still have problems, ask there so the discussion remains centralized.