Simple Extension Component
The simplest PLCnext component is a C++ class that inherits Arp::System::Acf::ComponentBase
.
The C++ source files (.hpp
and .cpp
) for a simple component, called MyComponent
, can be downloaded here:
At this point MyComponent
is a valid PLCnext Runtime component, but it contains no functionality - this will be added later.
Library Singleton
Named component instances are created by the PLCnext runtime from a singleton that inherits Arp::System::Acf::LibraryBase
.
The C++ source files (.hpp
and .cpp
) for a singleton - called MyLibrary
- that creates named instances of MyComponent
can be downloaded here:
Note that both MyComponent
and MyLibrary
are defined in a namespace called MyNamespace
.
Building the Library
You can build a shared object library containing both these classes (MyComponent
and MyLibrary
) using the SDK that you installed earlier.
The following procedure uses the build tools CMake
(version 3.19 or above) and Ninja
, so make sure these are also installed on the host machine. The PLCnext CLI installs an older version of CMake which does not support the cmake-presets feature used in this procedure.
Build the library as follows:
-
On your host machine, create a project directory. Under this directory, create a sub-directory called
src
. -
Copy the four source files to the
src
directory. -
In the project root directory, create a
CMakeLists.txt
file containing the following text:
cmake_minimum_required(VERSION 3.13)
project(MyProject)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
################# create target #######################################################
set (WILDCARD_SOURCE *.cpp)
set (WILDCARD_HEADER *.h *.hpp *.hxx)
file(GLOB_RECURSE Headers CONFIGURE_DEPENDS src/${WILDCARD_HEADER})
file(GLOB_RECURSE Sources CONFIGURE_DEPENDS src/${WILDCARD_SOURCE})
add_library(MyProject SHARED ${Headers} ${Sources})
#######################################################################################
################# project include-paths ###############################################
target_include_directories(MyProject
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>)
#######################################################################################
################# include arp cmake module path #######################################
list(INSERT CMAKE_MODULE_PATH 0 "${ARP_TOOLCHAIN_CMAKE_MODULE_PATH}")
#######################################################################################
################# set link options ####################################################
# WARNING: Without --no-undefined the linker will not check, whether all necessary #
# libraries are linked. When a library which is necessary is not linked, #
# the firmware will crash and there will be NO indication why it crashed. #
#######################################################################################
target_link_options(MyProject PRIVATE LINKER:--no-undefined)
#######################################################################################
################# add link targets ####################################################
find_package(ArpDevice REQUIRED)
find_package(ArpProgramming REQUIRED)
target_link_libraries(MyProject PRIVATE ArpDevice ArpProgramming)
#######################################################################################
################# install ############################################################
string(REGEX REPLACE "^.*\\(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*$" "\\1" _ARP_SHORT_DEVICE_VERSION ${ARP_DEVICE_VERSION})
install(TARGETS MyProject
LIBRARY DESTINATION ${ARP_DEVICE}_${_ARP_SHORT_DEVICE_VERSION}/$<CONFIG>/lib
ARCHIVE DESTINATION ${ARP_DEVICE}_${_ARP_SHORT_DEVICE_VERSION}/$<CONFIG>/lib
RUNTIME DESTINATION ${ARP_DEVICE}_${_ARP_SHORT_DEVICE_VERSION}/$<CONFIG>/bin)
unset(_ARP_SHORT_DEVICE_VERSION)
#######################################################################################
- Also in the project root directory, create a
CMakePresets.json
file containing the following text:
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 19,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"hidden": true,
"displayName": "Default Config",
"description": "Default build using Ninja generator",
"generator": "Ninja",
"toolchainFile": "$env{PLCNEXT_SDK_ROOT}/toolchain.cmake",
"binaryDir": "${sourceDir}/bin/$env{ARP_DEVICE}_$env{ARP_DEVICE_VERSION}",
"installDir": "${sourceDir}/deploy",
"cacheVariables": {
"ARP_DEVICE": "$env{ARP_DEVICE}",
"ARP_DEVICE_VERSION": "$env{ARP_DEVICE_VERSION}",
"CMAKE_BUILD_WITH_INSTALL_RPATH": true
}
},
{
"name": "build-windows-AXCF2152-2021.9.0.40",
"inherits": "default",
"displayName": "AXCF2152-2021.9.0",
"environment": {
"ARP_DEVICE": "AXCF2152",
"ARP_DEVICE_VERSION": "2021.9.0 (21.9.0.40)",
"PLCNEXT_SDK_ROOT": "C:\\SDK\\AXCF2152\\2021.9"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": {
"value": "$env{PLCNEXT_SDK_ROOT}/toolchain.cmake",
"type": "FILEPATH"
}
}
},
{
"name": "build-linux-AXCF2152-2021.9.0.40",
"inherits": "default",
"displayName": "AXCF2152-2021.9.0",
"environment": {
"ARP_DEVICE": "AXCF2152",
"ARP_DEVICE_VERSION": "2021.9.0 (21.9.0.40)",
"PLCNEXT_SDK_ROOT": "/opt/pxc/sdk/AXCF2152/2021.9"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
}
}
],
"buildPresets": [
{
"name": "build-linux-AXCF2152-2021.9.0.40",
"displayName": "AXCF2152-2021.9.0",
"configurePreset": "build-linux-AXCF2152-2021.9.0.40"
}
],
"testPresets": [
{
"name": "default",
"configurePreset": "default",
"output": {
"outputOnFailure": true
},
"execution": {
"noTestsAction": "error",
"stopOnFailure": true
}
}
]
}
You may need to change the ARP targets and SDK paths in this file to suit the setup of your development machine.
-
From the project root directory, configure, build and deploy the project:
$ cmake --preset=build-linux-AXCF2152-2021.9.0.40
$ cmake --build --preset=build-linux-AXCF2152-2021.9.0.40 --target all
$ cmake --build --preset=build-linux-AXCF2152-2021.9.0.40 --target install
You will see from the output that a shared object library, libMyProject.so
, has been created. This contains the component called MyNamespace::MyComponent
and the singleton called MyNamespace::MyLibrary
.
-
On the PLC, create a project directory e.g.
/opt/plcnext/projects/MyProject
, and alib
subdirectory. -
Copy the shared object library from the host to the target:
$ scp deploy/AXCF2152_21.9.0.40/Release/lib/libMyProject.so admin@192.168.1.10:~/projects/MyProject/lib
Instantiating the Component
Now that the shared object library containing the extension component is on the target, the PLCnext runtime must be instructed to create an instance of MyComponent
.
- On the host, in the project root directory, create a file named
MyProject.acf.config
, containing the following text:
<?xml version="1.0" encoding="UTF-8"?>
<AcfConfigurationDocument
xmlns="http://www.phoenixcontact.com/schema/acfconfig"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.phoenixcontact.com/schema/acfconfig.xsd"
schemaVersion="1.0" >
<Processes>
<Process name="MyProcess" settingsPath="$ARP_ACF_SETTINGS_FILE$" />
</Processes>
<Libraries>
<Library name="MyProject" binaryPath="$ARP_PROJECTS_DIR$/MyProject/lib/libMyProject.so" />
</Libraries>
<Components>
<Component name="MyComponentInstance" type="MyNamespace::MyComponent" library="MyProject" process="MyProcess" />
</Components>
</AcfConfigurationDocument>
-
Copy this ACF configuration file from the host to the target:
$ scp MyProject.acf.config admin@192.168.1.10:~/projects/Default
The PLCnext runtime will automatically load this configuration file, since the
Default.acf.config
file in the same directory includes all files that match the pattern*.acf.config
. The ACF configuration file forMyProject
instructs the PLCnext runtime to:- Create a new child process called
MyProcess
. - Load the shared object library and name it
MyProject
. This name can be considered an alias, or shorthand reference, to the shared object library. This name does not have any relationship to the name of the class in the shared object library that inheritedLibraryBase
. - Create an instance of
MyNamespace::MyComponent
, calledMyComponentInstance
, from the library namedMyProject
, in the process namedMyProcess
.
The
process
parameter is optional and, if omitted, the component instance will be created in the main PLCnext Runtime process (calledMainProcess
). - Create a new child process called
-
Restart the PLCnext runtime:
# sudo /etc/init.d/plcnext restart && tail -f -n 0 /opt/plcnext/logs/Output.log (result)
Among the messages that appear in the Output.log
file, you should see the following:
INFO - Process 'MyProcess' started successfully.
INFO - Library 'MyProject' in process 'MyProcess' loaded.
INFO - Component 'MyComponentInstance' in process 'MyProcess' created.
Your first PLCnext runtime extension component instance is now running!