The PLCnext Runtime © 2020-2021 Martin Boers

PLCnext Technology, PLCnext Engineer, PLCnext Store and Proficloud are registered trade marks of Phoenix Contact GmbH & Co. KG.

This book is not produced, supported or endorsed by Phoenix Contact GmbH & Co. KG or any of its associated companies.

While every effort has been made to ensure the accuracy of material in this book, the author accepts no responsibility for any errors or omissions.

The content of this book is subject to change without notice.

Acknowledgments

The following people have provided invaluable support in the creation of this book:

  • Dzmitry Ivaniuk at Savushkin R&D.

  • Everyone in the PLCnext Runtime support team at Phoenix Contact Electronics, including Frank Walde, Eduard Münz, Oliver Warneke, Heiko Hüllwegen and Gundula Breder.

Introduction

Welcome to The PLCnext Runtime, an introductory book about programming on the PLCnext Control platform.

What Is PLCnext Control?

PLCnext Control refers to a range of embedded computers from Phoenix Contact, which are designed for automating industrial processes. These controllers share some characteristics with popular single-board computers, but PLCnext Control devices includes features that make them particularly suitable for industrial applications.

The PLCnext Control range currently includes the following hardware variants from the AXC1, EPC2, RFC3 and BPC ranges:

  • AXC F 1152 (ARM® Cortex®-A9 2x 800 MHz)
  • AXC F 2152 (ARM® Cortex®-A9 2x 800 MHz, PCIe® connector)
  • AXC F 3152 (Intel® Atom™ x5-E3930 1.3 GHz Dual Core)
  • EPC 1502 (Intel® Celeron® N3350 1.10/2.40 GHz, 2GB RAM)
  • EPC 1522 (Intel® Celeron® N3350 1.10/2.40 GHz, 4GB RAM, 2x DB9 COM ports)
  • RFC 4072S (Intel® Core™ i5-6300U 2x 2.4 GHz + separate safety processors)
  • BPC 9102S (Intel® Core™ i7-10700TE 8x 2.4 GHz + separate safety processors)

Each of these hardware platforms runs custom firmware that is based on Linux kernel version 5.4 with the PREEMT-RT patch.

Who PLCnext Control Is For

PLCnext Control is ideal for software and systems engineers involved in the automation of industrial processes.

Controllers from the PLCnext Control range can perform the role of traditional PLCs4, however they also include features that will be familiar to software engineers with a more general programming background.

IEC 61131-3 Software Developers

Traditional PLCs generally can be programmed using any language defined by the IEC 61131-3 standard. Controllers from the PLCnext Control range are no different. For these developers, Phoenix Contact provides PLCnext Engineer software.

Simulink® is software for graphical, model-based development of dynamic systems. Simulink® models can be integrated into the PLCnext Engineer development environment using the PLCnext Target for Simulink software add-on.

Software engineers with experience in C/C++, Rust, C#, Java, Python, Javascript, HTML5, Go, etc

Custom software applications written in any popular programming language can be run on the controller. These applications can make use of PLCnext runtime services if required.

Systems integrators and network administrators

It is possible to simply install and configure pre-built applications on a PLCnext Control device, without any software engineering effort.

Project teams with some or all of the above skills

PLCnext Control devices includes unique features - such as the Global Data Space (GDS) and the Execution and Synchronisation Manager (ESM) - that make it possible to combine components written in different languages into a single project.

Who This Book Is For

This book is aimed at software developers who want to extend the functionality of a PLCnext Control device with their own software. The book contains program examples and references in a number of popular languages, but generally uses C++ to demonstrate the features of PLCnext Control. C++ is used because PLCnext Control provides a C++ programming framework, and many of the open-source projects that are suitable for PLCnext Control projects are also written in C++. However, software engineers with other programming skills should be able to apply the principles found in this book to their language of choice. For these programmers, appendix A gives references to language-specific resources.

This book does not cover PLCnext Engineer or programming in IEC 61131-3 languages, and in fact it is not necessary to read this book in order to become proficient in PLCnext Control programming using PLCnext Engineer. For IEC 61131-3 programmers, there are other resources from Phoenix Contact that will help you get started with PLCnext Engineer.

For systems integrators who don't want to write their own software, but who want to install and configure third-party software on a PLCnext Control - you will be most interested in Chapters 1 and 2.

For systems and network administrators who will be managing PLCnext Control devices - you will also find Chapters 1 and 2 useful.

What You Will Need

Obviously, you will need a controller from the PLCnext Control range. These are available for purchase from your local Phoenix Contact subsidiary, or from a number of online automation resellers. You will need to power the controller with a 24 VDC supply. A good option is the PLCnext Technology Starter Kit, which includes an AXC F 2152 controller, a 24 VDC power supply unit with pre-wired mains plug, and digital and analog input/output (I/O) modules.

All sections of this book apply to AXC F 1152 and 2152 devices, and most sections also apply to other PLCnext Control devices.

A PLCnext Control device is a target (in embedded programming terminology), and it requires a host. This book uses Debian 11 as the host machine, but any popular Linux distribution - or even Microsoft Windows - should also work. Windows commands are not shown in this book, so Windows users should consider installing Windows Subsystem for Linux to work along with this book.

The host machine must be connected to the internet. The host machine requires certain software development tools to be installed, and these will be described in the relevant sections of this book.

The controller must be connected to a local area network with access to both the internet and the host machine. Note that the controller does not include a wireless network adapter.

A knowledge of Ethernet networking would also be beneficial. The book "Ethernet Basics" by Phoenix Contact is recommended for this purpose.

Throughout this book, the PLCnext Control hardware will be referred to as either PLCnext Control device, or controller, or target. In this book, these terms are used interchangeably. Controllers from other manufacturers are often referred to with the term PLC, but in this book the term PLC will only be used to refer to those firmware components on the controller that implement real-time automation functions.

How to Use This Book

This book should be read from front to back. It is not intended to be an exhaustive reference; that is provided by the PLCnext Technology Info Centre. Instead, this book will draw on material in relevant sections of the Info Center, and elsewhere, to build up the readers knowledge in incremental steps.

Chapter 1 explains how to get started with a PLCnext Control device, from setting the IP address to writing your first "Hello, World!" programs in Python, C++ and Rust. Chapter 2 looks at some basic features of the Linux operating system (firmware) that runs on PLCnext Control devices. Chapter 3 introduces the PLCnext runtime by exploring the complete set of PLCnext runtime components that are installed with the firmware.

In Chapter 4, you will write your own PLCnext runtime extension component in C++. You will learn how extension components can use PLCnext runtime services, how they can provide their own services to other components, and how they can exchange data with other components through the global data space.

Chapter 5 introduces real-time programming on PLCnext Control devices. You will write a C++ program and configure the execution and synchronisation manager to run the program in a real-time PLC task. In Chapter 6 you will learn how a real-time program can read and write process data on Axioline I/O modules attached to the controller.

Chapter 7 looks at some additional tools that can help with PLCnext runtime programming.

Chapter 8 is for developers who want to port an existing runtime to a PLCnext Control device, or write a completely new runtime. You will learn how external runtimes can access the I/O that is connected to the controller, and how they can continue to utilise services provided by the PLCnext runtime.

A glossary of terms used in this book is available in the PLCnext Technology Info Center.

Command Line Notation

Throughout this book, you will see commands that must be entered into a terminal on either the host or the target.

Commands in a terminal on the host all start with $ (you don’t need to enter the $ character).

Commands in a terminal on the target all start with # (you don’t need to enter the # character).

Commands in a terminal running the python interpreter all start with >>> (you don’t need to enter the >>> characters).

Lines that don’t start with $, # or >>> typically show the output of the previous command.

Versions

This book will be kept up to date with the latest release of PLCnext Control firmware. As soon as a new version of PLCnext Control firmware is released, the book source code will be tagged with the version number of the firmware that has just been superseded.

Source Code

The source files used to generate this book can be found on GitHub.


1 AXC is short for Axioline Controller, indicating that the controller has a dedicated hardware interface to connect directly to the Axioline range of I/O modules. Axioline is derived from the terms AutomationWorx (AX), a brand name used by Phoenix Contact, and I/O, meaning Input/Output. The term line can be taken to refer to a line of products, but in this case it was inherited from an earlier range of Phoenix Contact products called Inline. The letter "F" after "AXC" is used to distinguish these devices from earlier Axioline controllers, but otherwise has no meaning.

2 EPC is short for Edge Personal Computer. EPCs combine real-time PLC features with popular Industrial Internet of Things (IIoT) applications like Node-RED and InfluxDB. EPCs are designed for industrial Edge computing applications.

3 RFC is short for Remote Field Controller. Unlike Axioline controllers, RFCs can only control remote I/O modules over a field bus like Profinet.

4 PLC is short for Programmable Logic Controller.

Getting Started

This chapter includes:

  • Setting the controller's IP address

  • Starting a shell session

  • Writing a Python program that prints Hello, world!

  • Installing a software development kit

  • Writing C++ and Rust programs that print Hello, world!

  • Updating the firmware

  • Exploring the file system

  • Installing software

Setting the IP Address

Once you have assembled everything you need to get started, your PLCnext Control device will be powered up and plugged in to an Ethernet network with access to the internet. On AXC F 1152 and AXC F 2152 PLCnext Control devices it doesn't matter which of the two Ethernet ports are used; these are switched internally.

The factory default IP address of every PLCnext Control device is 192.168.1.10/24, but you will probably want to change this to something that suits your local network.

This section shows how to change the IP address of the PLCnext Control device from the default value. In the remainder of this book, all examples will use the default IP address (192.168.1.10).

Here are some possible ways to change the IP address of the PLCnext Control device:

Use netnames

Netnames is a utility from Phoenix Contact that assists with the management of Profinet devices. Netnames is available for Linux 64 bit and Windows operating systems. The remainder of this section uses the Linux version of Netnames.

Firstly, make sure that netnames can be executed. From the netnames installation directory, run the following command:

$ chmod a+x netnames

By default, each PLCnext Control device acts as a Profinet device, and so can be assigned an IP address using netnames.

Run the following command, substituting eth0 with the name of your Ethernet adapter. You should get a response similar to the one shown.

$ sudo ./netnames -i eth0 -c identify
axc-f-2152-1   AXC F 2152   00B0 0142 00:A0:45:A0:09:D8   192.168.1.10   255.255.255.0   0.0.0.0

The MAC address and current IP address details of the PLCnext Control device are shown in the response.

If you get no response, make sure the name of the Ethernet adapter is correct. Also, be aware that the Discovery and Configuration Protocol (DCP) used by netnames is a link layer protocol, and so is not routable.

The IP address, subnet mask and default gateway can be changed using a command similar to the following:

$ sudo ./netnames -i eth0 -c setip -m 00:A0:45:A0:09:D8 -ip 192.168.178.10 -sm 255.255.255.0 -sg 192.168.178.1
Device responded: OK

Obviously the above command will need to include parameters that suit your own device and network. The IP address of the device should be in the same subnet as the host, and the default gateway should give the device access to the internet.

Your device should now be accessible from your host machine, which can be verified using ping:

$ ping -c 3 192.168.178.10
PING 192.168.178.10 (192.168.178.10) 56(84) bytes of data.
64 bytes from 192.168.178.10: icmp_seq=1 ttl=64 time=5.73 ms
64 bytes from 192.168.178.10: icmp_seq=2 ttl=64 time=4.87 ms
64 bytes from 192.168.178.10: icmp_seq=3 ttl=64 time=18.4 ms

--- 192.168.178.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2010ms
rtt min/avg/max/mdev = 4.877/9.690/18.464/6.214 ms

Use the Display (RFC only)

RFC controllers come with an integrated touch-screen display, which can be used to set the controller's IP address.

Use PLCnext Engineer

For Windows users, it is also possible to set the IP address of the PLCnext Control device using PLCnext Engineer software. Refer to the guide "Getting started with PLCnext Engineer" for instructions on how to do this.

Edit the interfaces file

Once the device is accessible over the network via ssh (for example), it is possible to change the IP address of the device by editing the file /etc/network/interfaces directly on the device.

Starting a Shell Session

The PLCnext Control device is running a secure shell (ssh) daemon so, once the device is accessible on the local area network, you can go ahead and open a shell session on the device from the host:

$ ssh admin@192.168.1.10
The authenticity of host '192.168.1.10 (192.168.1.10)' can't be established.
ECDSA key fingerprint is SHA256:uUhWsqX6TQy/KDZ4rdydja8zws7zCBLF5CI2/wm5owQ.
Are you sure you want to continue connecting (yes/no)?

You are requesting to log on to the PLC as the user admin. This user is set up by default on every PLCnext Control. The warning about the authenticity of the host is normal when using ssh to connect to a device for the first time. By answering yes to the question, the following appears:

Warning: Permanently added '192.168.1.10' (ECDSA) to the list of known hosts.
admin@192.168.1.10's password:

... and after entering the default password (printed on the housing of the PLC), the PLC's command prompt will appear:

admin@axcf2152:~$

Hello, World!

Let's write our first PLCnext Control program!

Your First Program

Every PLCnext Control device comes with Python already installed.

# python3
Python 3.7.2 (default, Nov 12 2019, 23:37:48)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

Here, you can see the version of Python that is installed on the device. The Python command prompt >>> is now waiting for further input. Enter the following command:

>>> print ("Hello, World!")
Hello, World!

Press Ctrl-D to exit from the Python interpreter and return to the command prompt.

Close the shell session:

# exit

You have just written your first PLCnext Control program!

Your Second Program

You probably don't want to be limited to Python when building applications. Python scripts are quick and easy to run on a PLCnext Control device because the firmware includes a Python interpreter. Other interpreted languages (like Javascript) need their own interpreter. Similarly, Java bytecode and .NET CLI code need their own runtimes. It is beyond the scope of this book to describe how to program a PLCnext Control device in every language. Appendix A includes a list of resources that demonstrate how to use PLCnext Control devices with a number of popular programming languages and frameworks, including contributions from members of the PLCnext Community.

Native applications can be built for a PLCnext Control device using C/C++ or Rust, for example, with the help of the appropriate Software Development Kit (SDK). Next, we will see how to install the SDK for a PLCnext Control device on your host machine, and then write "Hello, World!" programs for that controller in C/C++ and Rust.

Installing a Software Development Kit

You can build native applications for a PLCnext Control device - either from third-party open-source code, or from your own code. To do this, you must install a software development kit (SDK) on the host, corresponding to the firmware that is running on the PLCnext Control device. The SDK contains the build tools and other resources required to create native applications for the target.

PLCnext Command Line Interface

Software development kits for PLCnext Control targets are installed and managed using a command-line interface (CLI), called the PLCnext CLI or plcncli. It is possible to install SDKs on the host without using plcncli, but plcncli provides important features that will be required in later chapters of this book. For this reason, it is highly recommended to install and manage SDKs using plcncli.

To install plcncli on your host:

  • Download the file PLCnext Technology C++ tool chain for Linux from the Phoenix Contact website. For example, the file PLCnCLI_SDK_2021.6_Linux_AXC_F_2152.tar.gz is for the AXC F 2152 running firmware version 2021.6.

  • Extract the files from the archive.

  • In a terminal window, navigate to the directory where the files were extracted.

  • Optional: See what commands are available

  • Run the script to set up plcncli on the host, specifying the directory where the package should be installed.

    $ ./PLCnCLI_Setup.sh --target ~/plcncli
    

    Read the terms and conditions carefully and, if you agree with them, type y.

  • Create a symbolic link to the plcncli executable, as suggested by the installation message.

  • Check that the installation has been successful.

    $ plcncli
    plcncli 21.6.0 (21.6.0.726)
    Copyright (c) 2018 PHOENIX CONTACT GmbH & Co. KG
    

PLCnext SDK

  • In a terminal window, navigate to the directory where the files were extracted from the archive.

  • Use plcncli to install the SDK. You are free to specify any destination directory you want, using the -d option.

    $ plcncli install sdk -p pxc-glibc-x86_64-axcf2152-image-sdk-cortexa9t2hf-neon-axcf2152-toolchain-2021.6.sh -d /opt/pxc/sdk/AXCF2152/2021.6
    (todo: response))
    

    You may need to change the permissions on the SDK installation file to give the current user execute privilege.

  • Check what SDKs and targets have been installed.

    $ plcncli get sdks
    {
       "sdks": [
          {
             "path": "/opt/pxc/sdk/AXCF2152/2021.6"
          }
       ]
    }
    
    $ plcncli get targets
    {
       "targets": [
          {
             "name": "AXCF2152",
             "version": "21.6.0.46",
             "longVersion": "2021.6.0 (21.6.0.46)",
             "shortVersion": "21.6.0",
             "available": null
          }
       ]
    }
    

Note that the above responses are in JSON format, which makes it easier to integrate plcncli operations into an automated workflow if required.

If you need to build applications for different PLCnext Control hardware and/or firmware variants, then it is possible to use plcncli to install multiple SDKs on the host. To do this, simply repeat the installation procedure for each additional SDK. There is no need to install plcncli again.

Alternative SDK installation methods

If you are writing C++ applications in Eclipse or Visual Studio, then it is possible to install PLCnext SDKs through those IDEs. All SDK installation methods are described in the PLCnext Info Center.

Hello Again, World!

Once you have an SDK installed on your host machine, your PLCnext Control device can greet the world in C++ or Rust.

C++

On the host machine, create a file called main.cpp with the following contents:

#include <iostream>

int main() {
    std::cout << "Hello World!" << std::endl;
    return 0;
}

When cross-compiling open-source C/C++ code using the GNU Build System, it is typical - and often necessary - to define an environment variable called SDKROOT. This variable contains the path where header files, shared libraries, build tools etc. for the target platform can be found. Defining this variable is not necessary for this simple example, but let's do it anyway. In this case, we define SDKROOT as the directory in the SDK that contains the sysroot directory. For example:

$ export SDKROOT=/opt/pxc/sdk/AXCF2152/2021.6

The PLCnext SDK includes a bash script that sets up the build environment, so that standard build commands will result in binaries for the target platform. Execute this bash script using the source command:

$ source ${SDKROOT}/environment-setup*

Now we can compile and link the program using the g++ compiler that comes with the PLCnext SDK:

$ $CXX main.cpp -O -o say_hello  # Build with optimisations (-O) to avoid a warning.

Check that the resulting executable has been built for the correct platform:

$ file say_hello
say_hello: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, BuildID[sha1]=b4a827997b3375494f750fa890c8796ff293d463, for GNU/Linux 3.2.0, with debug_info, not stripped

Copy the executable to the PLCnext Control device:

$ scp say_hello admin@192.168.1.10:~

Run the program on the PLCnext Control device:

$ ssh admin@192.168.1.10
# ./say_hello
Hello World!

Congratulations! You have just run your first C++ program on a PLCnext Control device.

Rust

The build process for a simple Rust program targeting a PLCnext Control device is described in the first step of the rust-sample-runtime tutorial in Github.

Building on target

The software development process for PLCnext Control devices - cross-compiling on a host, then deploying to a target - will be familiar to those with experience in embedded systems software development, but other programmers often wonder why they cannot build their PLCnext Control applications directly on the target device. The reason is that the resources required to run development and build tools are often orders of magnitude greater than those required to run the final application, and the types of applications for which PLCnext Control is designed cannot justify the cost of these additional resources (e.g. memory and CPU power). For this reason, PLCnext Control should be considered more like an embedded device rather than a full-featured computer.

Accessing PLC I/O

After writing their first program, one of the first questions most PLCnext Control programmers ask is: How can an application read and write inputs and outputs from/to the I/O modules attached to the PLCnext Control device?

Before you learn how to do this, you must learn about the PLCnext runtime. But first, you should know about some basic features of the operating system that runs on every PLCnext Control device.

Operating System

The firmware on a PLCnext Control includes standard Linux features that will be familiar to systems and network administrators. These features are described in the PLCnext Technology Info Centre.

This chapter describes operating system features that are likely to be useful to developers of PLCnext Control applications. Some of these features are common to all Linux-based operating systems, and some are specific to PLCnext Control devices:

  • Security
  • Users and Privileges
  • Firmware, including installing updates
  • File system
  • Installing software

Security

As is the case with all computer systems, it is ultimately the responsibility of the end user to protect their equipment against all potential threats.

The security tools provided with PLCnext Control firmware are described in the PLCnext Technology Info Centre. These tools will be familiar to administrators of Linux devices.

In addition, users should always follow general IT security principles like those published by Phoenix Contact (PDF download).

Users and Privileges

Much of the information below also appears in the PLCnext Info Center.

admin user

By default, the admin user is granted the privilege to run a number of commands as a super-user. You can see the complete list of these commands as follows:

admin@axcf2152:~$ sudo -l
Password:
User admin may run the following commands on axcf2152:
    (ALL) /usr/bin/passwd
    (ALL) /sbin/ifconfig
    (ALL) /bin/date
    (ALL) /etc/init.d/plcnext
    (ALL) /etc/init.d/openvpn
    (ALL) /etc/init.d/ntpd
    (ALL) /etc/init.d/sshd
    (ALL) /usr/sbin/ipsec
    (ALL) /usr/sbin/swanctl
    (ALL) /etc/init.d/firewall
    (ALL) /usr/sbin/nft
    (ALL) /sbin/ldconfig
    (ALL) /sbin/shutdown
    (ALL) /sbin/reboot
    (ALL) /usr/sbin/tcpdump
    (ALL) /usr/sbin/update-rc.d
    (ALL) /usr/sbin/update-plcnext
    (ALL) /usr/sbin/recover-plcnext
    (ALL) /usr/bin/dpkg
    (ALL) /usr/bin/gdbserver
    (ALL) /usr/bin/gdbserver-plcnext.sh
    (ALL) /usr/bin/gdbserver-preprocess.sh
    (ALL) /usr/bin/gdbserver-prio-request.sh
    (ALL) /usr/bin/gdbserver-prio-set.sh
    (ALL) /usr/bin/gdbserver-start-program-wrapper.sh
    (ALL) /usr/sbin/update-axcf2152
    (ALL) /usr/sbin/recover-axcf2152
    (ALL) /usr/bin/gdbserver-plcnext.sh
    (ALL) /usr/bin/gdbserver-preprocess.sh
    (ALL) /usr/bin/gdbserver-prio-request.sh
    (ALL) /usr/bin/gdbserver-prio-set.sh
    (ALL) /usr/bin/gdbserver-start-program-wrapper.sh
    (ALL) /usr/sbin/sdcard_state.sh

These sudo privileges are granted using configuration files in the /etc/sudoers.d directory.

Extending admin privileges

In some cases it may be required to grant the admin user the privilege to execute more commands than those listed above. This can be done by adding one or more files to the /etc/sudoers.d directory.

In the extreme case, it is possible to grant the admin user the right to execute all commands on the controller, by adding a file to the /etc/sudoers.d directory containing the following line:

admin ALL=(ALL) ALL

Note that it is not recommended to edit or delete any file that is installed with the firmware.

root user

In some cases it may be required to switch to the root user. In this case, a root user password must first be set, using the following command:

admin@axcf2152:~$ sudo passwd root

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

Password: *Enter admin user password*

Changing password for root
Enter the new password (minimum of 5 characters)
Please use a combination of upper and lower case letters and numbers.
New password: *Enter root user password*
Re-enter new password: *Confirm password*

You can now switch to the root user using the command su root, or simply su.

Creating an ssh session as root

In some cases, it may be required to connect directly to the controller as root, using ssh or its associated utilities (scp, sftp, etc).

In order to open an ssh session on the controller as root, the ssh daemon must be configured to accept logins from that user. To do this, log in to the controller as admin, switch to the root user using the su command, and edit the /etc/ssh/sshd_config file using your favourite editor.

The controller includes vi (vim) and nano text editors. There are many online resources available if you need to learn how to use one these editors.

In the sshd_config file, remove the comment symbol # from this line:

#PermitRootLogin yes

Save the file, exit from the editor, and restart the ssh daemon:

root@axcf2152:~# /etc/init.d/sshd restart

Logging in without a password

For activities that require frequent login to a PLCnext Control device, e.g. during application development and testing, it can become tedious to repeatedly enter the same password. It is possible to use key-based SSH authentication to eliminate this chore, without compromising security.

The following article from a Phoenix Contact technical support site describes how to implement this standard Linux feature for the admin user on a PLCnext Control device:

How to set up key-based SSH authentication to a PLCnext Control device.

Setting the Time

Like with other Linux distributions, it is possible to set the date and time on a PLCnext Control device using the date command.

By default, each PLCnext Control device is configured to use Coordinated Universal Time (UTC). You should set the date and time on your device to the correct UTC time. This will - among other things - avoid problems with security certificates, which often require the time on the device to be more or less correct.

Network Time Protocol

If the device has access to a Network Time Protocol (NTP) server, either locally or on the internet, then it is possible to use that NTP server to maintain the correct time on the PLCnext Control device. Like with other Linux distributions, this can be achieved by editing the file /etc/ntp.conf on the device.

Information on how to set the time on a PLCnext Control device using various methods is available in the PLCnext Info Center.

Setting the Time Zone

For applications that use local time rather than universal time, you may want to set the time zone on your PLCnext Control device using the procedure in the following article:

How to set the time zone on a PLCnext Control device.

The release notes for firmware version 2021.9 state that "Setting local time zones is not fully supported". The procedure in the above article involves editing a system configuration file, which may cause problems during future firmware upgrades.

Firmware

The PLCnext Control firmware includes both the Linux kernel and factory-installed software from Phoenix Contact. A PLCnext Control device can be updated with a different firmware version at any time, if required. In general, newer firmware will only add non-breaking features and bug-fixes to older versions, so applications that are designed for a specific firmware version should (generally) be able to run on newer firmware versions without modification.

Firmware Release Schedule

PLCnext Control firmware is generally released four times a year; the first is a Long Term Support (LTS) version, and the other three are "feature" releases for those who want the very latest features as early as possible. The firmware release history and future release schedule is shown in the PLCnext Info Center.

There is also some information on upcoming firmware features in the PLCnext Info Center.

Checking the Firmware Version

It is important to know the firmware version that is running on the device for a number of reasons:

  • When building native applications, you must use a software development kit (SDK) that is compatible with the firmware version that will run the application.

  • When installing third-party applications that have been built specifically for PLCnext Control devices, you may need to select a version that is compatible with the firmware running on your device.

  • When looking for assistance in the PLCnext Community, it will often help to know what firmware version is currently running on the device.

You can check the version of firmware currently running on the device by listing the contents of the arpversion file in the /etc/plcnext/ directory.

# cat /etc/plcnext/arpversion
Arpversion: 21.6.0.46
GIT Commit Hash: 8f3c6754f20bab7c5cd88aa4de07a797c3153516
Build Job: "jenkins-PLCnext-Yocto_Targets-Yocto_AXCF2152-release%2F21.6.x-46"

The firmware version is shown in the Arpversion field - in this case, 21.6.0.46.

Upgrading the Firmware

New versions of PLCnext Control firmware are released regularly. If the firmware on your device is not the latest, you may want to upgrade it to get the benefit of new features and bug fixes. The firmware on a PLCnext Control device can be upgraded by following these steps:

  • Download the firmware update file (ZIP archive) for your controller from the Phoenix Contact website.

  • Extract the .raucb file from the archive.

    Now you can guess (correctly) that PLCnext Control devices use RAUC for firmware updates.

  • Copy the .raucb file to the device:

    $ scp axcf2152-bundle-base-axcf2152.raucb admin@192.168.1.10:~
    
  • Open a shell session:

    $ ssh admin@192.168.1.10
    
  • Update the firmware:

    # sudo /etc/init.d/plcnext stop
    Password:
    Stopping service plcnext
    plcnext stopped
    #
    # admin@axcf2152:~$ rauc install axcf2152-2022.0.0.13-LTS-beta.raucb
    installing
    0% Installing
    0% Determining slot states
    20% Determining slot states done.
    20% Checking bundle
    20% Verifying signature
    40% Verifying signature done.
    40% Checking bundle done.
    40% Checking manifest contents
    60% Checking manifest contents done.
    60% Determining target install group
    80% Determining target install group done.
    80% Updating slots
    80% Checking slot rootfs.0
    90% Checking slot rootfs.0 done.
    90% Copying image to rootfs.0
    100% Copying image to rootfs.0 done.
    100% Updating slots done.
    100% Installing done.
    Installing `/opt/plcnext/axcf2152-2022.0.0.13-LTS-beta.raucb` succeeded
    #
    # sudo reboot
    

After the device restarts, open a new shell session and check the firmware version.

But There's More!

There are actually three firmware copies lurking on a PLCnext Control device, and all three may be different versions. The above procedure only replaces one of these three firmware installations. You will learn more about this in the next section.

Other Ways to Upgrade Firmware

There are currently many other ways to upgrade the firmware on the controller, including:

All the methods listed above require that the PLCnext runtime is running. If this is the case, then firmware upgrades should be performed using one of these methods rather than using the rauc command.

Disk Partitions

The internal storage in a PLCnext Control device is divided into a number of partitions, including:

  • Boot partition.
  • Device data partition.
  • 3 x Root File System (RFS) partitions.
  • User data partition.

Device Data

Device data includes data that is specific to an individual device, e.g. security certificates and other data that is tied to the specific trusted platform module (TPM) chip in the device. If this data is damaged, the device may become inoperable and unrecoverable.

Root File Systems

There are always three root file systems on a PLCnext Control device:

  1. Active file system.

  2. Inactive file system. This is a "standby" file system that is used in case the device cannot boot using the active file system.

  3. Recovery file system. Used to replace the other two file systems during a type 2 reset (described in the next section).

Each file system is located on a different partition on the device's internal storage.

When a device is shipped from the factory, all three root file systems are the same version - usually from the latest LTS firmware version that was available when the device was manufactured.

The firmware update procedure described in the previous section actually proceeds as follows:

  • The new root file system is extracted from the firmware image and installed on the inactive boot partition.
  • When the device restarts next, the active and inactive partitions are swapped, so the device uses the file system from the newly installed firmware.

The status of the root file systems can be seen using the following command:

# rauc status --detailed
=== System Info ===
Compatible:  axcf2152_v1
Variant:    
Booted from: rootfs.0 (A)

=== Bootloader ===
Activated: rootfs.0 (A)

=== Slot States ===
o [rootfs.1] (/dev/mmcblk0p3, ext4, inactive)
bootname: B
boot status: good
      slot status:
          bundle:
              compatible=axcf2152_v1
              version=2021.9
              description=Update container for axcf2152
              build=20210930133602
          checksum:
              sha256=12c4bc62a65e6abd4479d1d2b930cba2bde3fc5412f6fc94b3d29761e33d2661
              size=575712256
          installed:
              timestamp=2020-03-11T13:35:02Z
              count=1
          activated:
              timestamp=2020-03-11T13:35:02Z
              count=1
          status=ok

x [rootfs.0] (/dev/mmcblk0p2, ext4, booted)
bootname: A
mounted: /media/rfs/ro
boot status: good
      slot status:
          bundle:
              compatible=axcf2152_v1
              version=2022.0
              description=Update container for axcf2152
              build=20211029212448
          checksum:
              sha256=0a27a82bb7f4c02449c3c78dadb1a3d300de9c861a40f27ea55db7c3af013558
              size=598693888
          installed:
              timestamp=2021-11-08T08:59:15Z
              count=1
          activated:
              timestamp=2021-11-08T08:59:16Z
              count=1
          status=ok

This shows that the active root file system contains firmware version 2022.0, and that the inactive root file system contains firmware version 2021.9.

The PLCnext Info Center gives this explanation:

If the boot process failed several consecutive times, the inactive and the active boot partition will change their roles, too. This behavior has been implemented to keep the PLCnext Control device accessible even if the firmware update fails. The behavior can also occur when the boot process is interrupted e.g. by power loss. In this case you will observe that the controller boots with its previously installed firmware version. To prevent such unintended firmware downgrades, Phoenix Contact recommends that after a successful firmware update, the same firmware should be installed once again. This way both the active and the inactive boot partition contain the same firmware version.

User Data

The user data partition includes:

  • All user-configurable files that are installed with the firmware.
  • Any files added to the device by the user.

If a removeable SD card is inserted into the PLCnext Control device, then the user data partition on the internal storage device will not be used, and user data will instead be stored on the removeable SD card. External SD cards are currently available in sizes up to 32GB, which provides a much greater storage capacity than the user data partition in internal memory.

File System

Files in a PLCnext Control are arranged in an overlay file system.

The firmware is installed in the lower (read only) directory. All files created by users, and any changes to files in the lower directory, are stored in the upper (read/write) directory.

Storage Media

Each PLCnext Control device includes internal, non-volatile, solid-state memory. The PLC is also supplied with an empty SD card slot.

The firmware will only recognise SD cards specifically designed for PLCnext Control devices.

The firmware file system, mounted as the read-only lower directory, is always stored in internal memory. This includes all three of firmware images described in the previous section. Information on the firmware file system is available in the PLCnext Technology Info Centre.

If the PLC boots without an SD card present, the internal memory is used as the primary storage media for the user file system, since it is the only memory available. If the PLC boots with an SD card present, the SD card is used as the primary storage media for the user file system.

Please note the SD card handling guidelines in the user manual for your PLCnext Control device.

In relation to the upper directory, the behaviour of the system when booting depends on the presence or absence of an external SD card, and the presence or absence of a user file system on the primary storage media:

Primary
media
User file system absent on primary mediaUser file system present on primary media
SD card absentInternal
memory
User file system is created in internal memory
and mounted as upper directory.
Existing file system in internal memory
is mounted as upper directory.
SD card presentSD cardUser file system is moved from internal memory to
the SD card and mounted as upper directory.
Existing user file system on SD card
is mounted as upper directory.
Any internal user file system is deleted.

Note that in the case shown in the lower right corner of the above table, data loss may occur.

More information on this topic is available in the PLCnext Technology Info Centre.

Deleting and Restoring File Systems

The following procedures will affect the complete firmware and/or user file system in a PLCnext Control device:

Firmware Update

During a firmware update, the contents of the firmware file system are replaced with new firmware files. The user file system remains unaffected, and so (for example) the IP address of the PLC will be retained, passwords will be unaffected, and user-installed files will not be deleted.

Note: Since manual changes to firmware files are stored in the upper directory, any firmware files that were changed manually before a firmware update will remain in the upper directory and will continue to mask the corresponding file in the new firmware file system. This may adversely affect the operation of the PLC after a firmware update. It is therefore recommended that firmware files are never manually changed or deleted.

Type 1 Reset

A type 1 reset deletes the entire contents of the user file system on the primary storage device. The contents of the firmware file system remain unaffected.

To perform a type 1 reset, open a secure shell session on the PLC, and issue the following command:

# sudo recover-plcnext 1

Type 2 Reset

A type 2 reset:

  • Deletes the entire contents of the user file system on the primary storage device, just like a type 1 reset.

  • Replaces the firmware file system with a factory-default firmware version.

Note that a type 2 reset may not restore the device to its factory default state, since some user files may have been stored outside the upper directory. The most common way this happens is when using an OCI engine like Docker, Balena or Podman, which store files outside the overlay file system by default.

To perform a type 2 reset, open a secure shell session on the PLC, and issue the following command:

# sudo recover-plcnext 2

Warning: After either a type 1 or a type 2 reset, all user data will be deleted and the PLC's IP address and admin password will be restored to factory defaults.

On some devices, it is also possible to perform a type 1 or a type 2 reset using a button on the front of the PLCnext Control. See the user manual for details.

Backups

It is highly recommended to take regular backups of the user file system.

Phoenix Contact does not currently provide a standard backup/restore procedure for user data, however you can prepare your own procedure using the examples on the PLCnext Github page.

Installing Software

Now that you are familiar with some of the features of the Linux operating system on PLCnext Control devices, lets see how you can extend the functionality of the controller by installing third-party software.

Those familiar with other Linux distributions will know about package managers like apt. Unfortunately neither apt nor any eqiuvalent package manager is available on PLCnext Control devices by default. PLCnext Control firmware does include dpkg, but it does not include a database of installed software, so dpkg cannot know if any dependencies listed in a .deb package have already been installed with the firmware.

Various users of PLCnext Control devices have documented their own procedure for adding a package mananger to PLCnext Control devices, including apt, ipkg and guix. None of these are entirely problem-free, so if you decide to try this yourself- proceed with caution.

The PLCnext Store

The recommended way to add packages to a PLCnext Control device is through the PLCnext Store. In order to install and use a package from the PLCnext Store:

  • The package you need must be available in the PLCnext Store.

  • The package must be installed and started from a web page, either remotely (at plcnextstore.com) or on the controller's local web-based management page. Package installation from the PLCnext Store cannot currently be automated.

  • In most cases, in order to use a package from the PLCnext Store, the PLCnext Runtime must be running on your PLCnext Control device at all times.

To learn more about the PLCnext Store, visit the PLCnext Store Info Center.

OCI Containers

Containerised design has become popular in IT applications. OCI containers can also be run on PLCnext Control devices, using one of the following popular container engines:

Phoenix Contact has published a Getting Started guide for installing Balena and Docker on PLCnext Control devices.

Build It Yourself

  • Directly install a pre-built binary, if available. This must be built for a compatible architecture (e.g. armv7 32 bit).

  • Cross-compile an open-source project using a PLCnext Control SDK.

PLCnext Runtime

PLCnext Control firmware was introduced briefly in the previous chapter. PLCnext Control firmware features can be broadly categorised as either:

  1. Features commonly available on general-purpose Linux distributions, or
  2. Features developed by Phoenix Contact, that are not available on other Linux distributions.

The second group of features includes:

  • commands like update-plcnext and recover-plcnext that you saw previously, and
  • software components that are started and controlled by the PLCnext runtime.

This chapter will look at:

  • Controlling the PLCnext runtime
  • PLCnext components
  • Component instances
  • Disabling features of the PLCnext runtime

You will see the acronym Arp used extensively throughout this chapter. Arp is short for Automation Runtime Platform, and is another name for the PLCnext runtime.

Controlling the PLCnext Runtime

When the device boots, it automatically runs the script in the file /etc/init.d/plcnext. This starts the PLCnext runtime.

We can stop the PLCnext runtime any time, using the same script:

# sudo /etc/init.d/plcnext stop
Stopping service plcnext
plcnext stopped

Start the PLCnext runtime, and watch entries being added to the default log file:

# sudo /etc/init.d/plcnext start && tail -f -n 0 /opt/plcnext/logs/Output.log
Starting service plcnext
Set plcnext exports
plcnext started (bus system is axioline)
12.08.21 19:42:14.764 Arp.System.Acf.Internal.ApplicationBase     INFO  - ArpVersion: 2021.6.0 (21.6.0.46)
  :

You can see lots of activity in the Output.log file when the PLCnext Runtime starts up. The Output.log file is an important source of information - for example, if your PLCnext Control device ever behaves unexpectedly, the Output.log file will often help you to identify the problem.

But what does the PLCnext Runtime do? Basically, the PLCnext runtime turns an otherwise ordinary device into a PLC, and at the same time turns that device into much more than an ordinary PLC.

You have already used one feature of the PLCnext Runtime, when you set the IP address using netnames. Netnames is used to manage Profinet devices, and the PLCnext Runtime starts a component that makes the controller behave like a Profinet device. See what happens when we try to use netnames when the PLCnext Runtime is stopped:

# sudo /etc/init.d/plcnext stop

On the host machine:

$ sudo ./netnames -i eth0 -c identify
(no result)

We can still ping the PLC:

$ ping 192.168.1.10

... because ICMP messages are handled by the Linux operating system, not by the PLCnext runtime. This is also why we can maintain a remote shell session on the PLC, regardless of whether the PLCnext runtime is running or stopped.

Start the PLCnext Runtime again:

# sudo /etc/init.d/plcnext start

... and netnames will again be able to discover the PLC. You may need to wait for up to a minute for the PLCnext runtime to finish starting before netnames will see it.

Diagnosing problems

  • Output.log file

    • different log levels
  • OPC UA logging

  • Notification Logger (WBM)

  • syslog

  • LTTng

Some hints: https://github.com/savushkin-r-d/PLCnext-howto/tree/master/HowTo%20logging

PLCnext Runtime Components

Factory-installed software components that make up the PLCnext runtime include:

  • Hardware components
  • I/O and Fieldbus components
  • System components
  • PLC components
  • Service components

An overview of these component categories is given in the PLCnext Technology Info Center. The categories are somewhat arbitrary, but during startup all the components in one category are generally started before any components in the next category, and vice-versa during shutdown.

Components are defined in C++ shared object libraries, which are located in the /usr/lib directory on the PLCnext Control device. The tables below list these shared object libraries, the components they define, and links to more information on the function that each component implements.

Hardware Components

These components provide access to device hardware. They typically wrap Linux device drivers, making these devices available to other PLCnext runtime components via internal RSC services.

  • Library filename prefix: libArp.Hardware.
  • Component namespace: Arp::Hardware
Library filenameComponent type nameService description
IdentificationData.soIdentificationData::IdentificationDataComponentDevice ID data
FanControl.soFanControl::FanControlComponentFan control operations
Fpga.soFpga::FpgaComponentFPGA information
OsControl.soOsControl::OsControlComponentOperating system operations
RealTimeClock.soRealTimeClock::RealTimeClockComponentReal time clock operations
ResourceMonitor.soResourceMonitor::ResourceMonitorComponentCPU, memory and partition monitoring
DeviceHmi.soDeviceHmi::DeviceHmiComponentDevice LEDs, buttons and switches
Sensors.soSensors::SensorsComponentDevice temperature, humidity and power monitoring
Nim.soNim::NimComponent
Nim::NetloadLimiterComponent
Ethernet information, IP configuration and IP status
Network load limiter (public)
ExternalSDCard.soExternalSDCard::ExternalSDCardComponentInformation on the external SD card license, status and settings
ExternalPci.soExternalPci::ExternalPciComponentExternal PCI state information

Device Components

These components provide device-specific abstractions for some low-level hardware services.

  • Library filename prefix: libArp.Device.
  • Component namespace: Arp::Device
Library filenameComponent type nameService description
HmiLed.soHmiLed::HmiLedComponentLED management (internal)
Interface.soInterface::DiComponentDevice control (public)
Device information (public)
Device settings (public)
Device status (public)

I/O Components

These components provide access to physical I/O modules through a variety of standard industrial networks.

  • Library filename prefix: libArp.Io.
  • Component namespace: Arp::Io
Library filenameComponent type nameFunctionCondition
Axioline.soAxioline::AxiolineComponentAxioline master1ARP_COMPONENT_AXIOLINE
Interbus.soInterbus::InterbusComponentInterbus master1ARP_COMPONENT_INTERBUS
EthernetIP.soEthernetIP::EthernetIPComponentEthernet/IP device1ARP_COMPONENT_ETHERNETIP
ProfinetStack.soProfinetStack::System::SystemComponent
ProfinetStack::Controller::ControllerComponent
ProfinetStack::Device::DeviceComponent
Profinet system
Profinet controller
Profinet device
ARP_COMPONENT_PROFINET or ARP_COMPONENT_PNC or ARP_COMPONENT_PND

1 Only available on AXC devices.

System Components

These components implement low-level PLCnext runtime features. They may provide an API, but are not generally designed to be configurable by end users.

  • Library filename prefix: libArp.System.
  • Component namespace: Arp::System
Library filenameComponent type nameFunction
Commons.Services.soCommons::Services::ComponentFile system info (public)
Directory service (public)
File service (public)
Device ID validator (public)
Trust store management (internal)
Identity store management (internal)
Security.Services.soSecurity::Services::ComponentSystem security
Watchdog.soWatchdog::SystemWatchdogComponentSystem watchdog configuration and monitoring
RscGateway.soRscGateway::RscGatewayComponentRemote service call gateway
Um.soUm::UmComponentUser manager

PLC Components

These components implement features you would expect to find on PLCs.

Library filename prefix: libArp.Plc. Component namespace: Arp::Plc

Library filenameComponent type nameFunction
Domain.soDomain::PlcDomainProxyComponent
Domain::PlcManagerComponent
Domain::PlcDomainComponent
Related to I/O
?
?
Meta.soMeta::MetaDomainComponent
Meta::MetaComponent
Meta::MetaControllerComponent
GDS traverser (public)
GDS browser (internal)
Eclr.soEclr::EclrComponent
Eclr::EclrServicesComponent
Eclr::ArpDomainComponent
Embedded Common Language Runtime
Plm.soPlm::PlmComponentManages real-time C++ programs
Esm.soEsm::EsmComponent
Esm::EsmControllerComponent
Execution and Synchronisation Manager
Gds.soGds::GdsComponentManages the global data space
Retain.soRetain::RetainComponentManages retentive variables
Fbm.soFbm::FbmComponent
Fbm::FbIoComponent
Fieldbus manager

Service Components

These components generally provide high-level, non real time features that are easily configurable by end users.

Library filename prefix: libArp.Services. Component namespace: Arp::Services

Library filenameComponent type nameFunction
Alarms.soAlarms::AlarmsComponentAlarm dispatcher
Fwm.soFwm::FwmComponentFirewall manager
Wbm.soWbm::WbmComponentWeb-based management
Ehmi.soEhmi::EhmiComponentEmbedded HMI
OpcUAServer.soOpcUAServer::OpcUAServerComponentOPC UA server
NotificationLogger.soNotificationLogger::NotificationLoggerComponentNotification logger
TraceController.soTraceController::TraceControllerComponentLTTng trace controller
NmUtilities.soNmUtilities::NmPlcStateListener::NmPlcStateListenerComponentNotification manager
DataLogger.soDataLogger::DataLoggerComponentData logger
AppManager.soAppManager::AppManagerComponentPLCnext Store app management
Fwu.soFwu::FwuComponentFirmware update
LinuxSyslog.soLinuxSyslog::LinuxSyslogComponentsyslog-ng interface
ProfiCloudV3.soProfiCloudV3::ProfiCloudV3ComponentProficloud
Wcm.soWcm::WcmComponentWeb configuration management
Logging.soLogging::LoggingComponent
Logging::LogManagerComponent
Logging::LogProviderComponent
General event logger
SpnsProxy.soSpnsProxy::SpnsProxyComponentSafety controller (SPNS) communications

Component Instances

When the PLCnext runtime starts, the following happens:

  1. One or more processes are started.
  2. The libraries containing PLCnext component definitions are loaded into the specified process(es).
  3. Component instances are created in the specified processes.

The configuration information in this section appears in the following files:

  • /etc/plcnext/device/Libraries.acf.config
  • /etc/plcnext/device/MainProcess.acf.config
  • /etc/plcnext/device/ExternalIoProcess.acf.config
  • /etc/plcnext/device/AXCF2152.acf.config

Runtime Processes

Most component instance are created in the main PLCnext runtime process, which is called MainProcess. In the AXC F 2152, one additional process is created, in which I/O component instances related to Axioline, Ethernet/IP and Interbus are created.

Process nameLocal TCP port
MainProcess41121
ExternalIoProcess41123
LocalIoProcess141124

1 Only on AXC devices.

Since multiple Interbus and Profinet hardware interfaces are possible on an AXC F 2152 device, these additional processes allow the corresponding I/O networks to be dynamically assigned to the the correct hardware interface.

Component Libraries

The following table show the alias given to each component library by the PLCnext runtime.

Libraries can be loaded conditionally, based on the value of a boolean environment variable. If no condition is specified, the library is always loaded.

Filename
(libArp.*.so)
Alias
(Arp.*.Library)
Condition
System.System.
UmUm
RscGatewayRscGateway
Commons.ServicesCommons.Services
Security.ServicesSecurity.Services
FwuFwu
WatchdogSystemWatchdogARP_COMPONENT_SYSTEM_WATCHDOG
SysWatchdogSysWatchdog
Hardware.Hardware.
NimNim
IdentificationDataIdentificationData
FanControlFanControl
FpgaFpga
OsControlOsControl
RealTimeClockRealTimeClock
ResourceMonitorResourceMonitor
DeviceHmiDeviceHmi
SensorsSensors
ExternalSDCardExternalSDCard
ExternalPciExternalPciARP_COMPONENT_EXTERNAL_PCI
Device.Device.
HmiLedHmiLed
InterfaceInterface
Plc.Plc.
EclrEclr
PlmPlm
EsmEsm
GdsGds
DomainDomain
MetaMeta
FbmFbm
RetainRetainARP_COMPONENT_RETAIN
Services.Services.
FwmFwm
WbmWbm
EhmiEhmi
OpcUAServerOpcUAServer
AlarmsAlarms
ProfiCloudProfiCloudARP_COMPONENT_PROFICLOUD
ProfiCloudV3ProfiCloudV3ARP_COMPONENT_PROFICLOUD_V3
NotificationLoggerNotificationLogger
TraceControllerTraceController
NmUtilitiesNmUtilities
DataLoggerDataLogger
AppManagerAppManagerARP_COMPONENT_APPMANAGER
LinuxSyslogLinuxSyslogARP_COMPONENT_LINUXSYSLOG
WcmWcm
LoggingLogging
SpnsProxySpnsProxy
Io.Io.
ProfinetStackPnARP_COMPONENT_PROFINET
or ARP_COMPONENT_PNC
or ARP_COMPONENT_PND
Axioline.so1AxlARP_COMPONENT_AXIOLINE
Interbus.so1IbMARP_COMPONENT_INTERBUS
EthernetIP.so1EthernetIPARP_COMPONENT_ETHERNETIP

1 Only available on AXC devices.

Component Instances

Each component created by the PLCnext runtime:

  • Is created in a specific process. If no process is specified, the component is created in the main PLCnext Runtime process ("MainProcess").
  • Can optionally be passed settings information. In the table below, all settings information represent files relative to the directory /etc/plcnext/device.
  • Can be created conditionally, based on the value of a boolean environment variable. If no condition is specified, the component instance is always created.
Instance name
(Arp.*)
Type
(Arp::*)
ProcessSettings information
(/etc/plcnext/device/*
Condition
System.System::System/
Commons.ServicesCommons::Services::Component
Security.ServicesSecurity::Services::Component
Watchdog.MainProcessWatchdog::SystemWatchdogComponentWatchdog/Watchdog.settingsARP_COMPONENT_SYSTEM_WATCHDOG
RscGatewayRscGateway::RscGatewayComponentRscGateway/RscGateway.settings
UmUm::UmComponentUm/Um.settings
FwuFwu::FwuComponent
WcmWcm::WcmComponentWcm/Wcm.settingsARP_COMPONENT_WCM
Hardware.Hardware::Hardware/
IdentificationDataIdentificationData::IdentificationDataComponentIdentificationData/IdentificationData.settings
FanControlFanControl::FanControlComponentFanControl/FanControl.settings
FpgaFpga::FpgaComponentFpga/Fpga.settings
OsControlOsControl::OsControlComponentOsControl/OsControl.settings
RealTimeClockRealTimeClock::RealTimeClockComponentRealTimeClock/RealTimeClock.settings
ResourceMonitorResourceMonitor::ResourceMonitorComponentResourceMonitor/ResourceMonitor.settings
DeviceHmiDeviceHmi::DeviceHmiComponentDeviceHmi/DeviceHmi.settings
SensorsSensors::SensorsComponentSensors/Sensors.settings
NimNim::NimComponentNim/Nim.settings
NetloadLimiterNim::NetloadLimiterComponentNim/NetloadLimiter.settingsARP_COMPONENT_NETLOAD_LIMITER
ExternalSDCardExternalSDCard::ExternalSDCardComponentExternalSDCard/ExternalSDCard.settings
ExternalPciExternalPci::ExternalPciComponentExternalPci/ExternalPci.settingsARP_COMPONENT_EXTERNAL_PCI
Device.Device::Device/
HmiLedHmiLed::HmiLedComponent
InterfaceInterface::DiComponentInterface/Di.settings
Plc.Plc::Plc/
ManagerDomain::PlcManagerComponent
DomainDomain::PlcDomainComponentDomain/PlcDomain.settings
MetaDomainMeta::MetaDomainComponent
MetaMeta::MetaComponentMeta/Meta.settings
MetaControllerMeta::MetaControllerComponent
EclrEclr::EclrComponentEclr/Eclr.settingsARP_COMPONENT_ECLR
EclrServicesEclr::EclrServicesComponentARP_COMPONENT_ECLR
Eclr.ArpDomainEclr::ArpDomainComponentARP_COMPONENT_ECLR
PlmPlm::PlmComponentPlm/Plm.settings
EsmEsm::EsmComponentEsm/Esm.settings
EsmControllerEsm::EsmControllerComponent
GdsGds::GdsComponentGds/Gds.settings
RetainRetain::RetainComponentRetain/Retain.settingsARP_COMPONENT_RETAIN
FbmFbm::FbmComponentFbm/Fbm.settings
DomainProxy.ExternalIoProcessDomain::PlcDomainProxyComponentExternalIoProcess
DomainProxy.LocalIoProcess1Domain::PlcDomainProxyComponentLocalIoProcess
Io.Plc::Plc/
FbIo.PnCFbm::FbIoComponentFbIo.PnC/FbIo.settingsARP_COMPONENT_PROFINET
or ARP_COMPONENT_PNC
FbIo.PnDFbm::FbIoComponentFbIo.PnD/FbIo.settingsARP_COMPONENT_PROFINET
or ARP_COMPONENT_PND
FbIo.AxlC1Fbm::FbIoComponentFbIo.AxlC/FbIo.settings
FbIo.IbM1Fbm::FbIoComponentFbIo.IbM/FbIo.settings
FbIo.EthernetIP1Fbm::FbIoComponentFbIo.EthernetIP/FbIo.settingsARP_COMPONENT_ETHERNETIP
Io.Io::Io/
PnSProfinetStack::System::SystemComponentExternalIoProcessPnS/PnS.settingsARP_COMPONENT_PROFINET
or ARP_COMPONENT_PNC
or ARP_COMPONENT_PND
PnCProfinetStack::Controller::ControllerComponentExternalIoProcessPnC/PnC.settingsARP_COMPONENT_PNC
PnDProfinetStack::Device::DeviceComponentExternalIoProcessPnD/PnD.settingsARP_COMPONENT_PND
AxlC1Axioline::AxiolineComponentAxlC/AxlC.settingsARP_COMPONENT_AXIOLINE
IbM1Interbus::InterbusComponentLocalIoProcessIbM/IbM.settingsARP_COMPONENT_INTERBUS
EthernetIP1EthernetIP::EthernetIPComponentExternalIoProcessEthernetIP/EthernetIP.settingsARP_COMPONENT_ETHERNETIP
Services.Services::Services/
AlarmsAlarms::AlarmsComponent
FwmFwm::FwmComponentFwm/Fwm.settingsARP_COMPONENT_FWM
WbmWbm::WbmComponentWbm/Wbm.settingsARP_COMPONENT_WBM
EhmiEhmi::EhmiComponentEhmi/ehmi.settingsARP_COMPONENT_EHMI
OpcUAServerOpcUAServer::OpcUAServerComponentOpcUA/opcua.settingsARP_COMPONENT_OPC_UA
ProfiCloudProfiCloud::ProfiCloudComponentProfiCloud/Proficloud.settingsARP_COMPONENT_PROFICLOUD
ProfiCloudV3ProfiCloudV3::ProfiCloudV3ComponentProfiCloudV3/ProfiCloudV3.settingsARP_COMPONENT_PROFICLOUD_V3
NotificationLoggerNotificationLogger::NotificationLoggerComponentNotificationLogger/NotificationLogger.settings
TraceControllerTraceController::TraceControllerComponentTraceController/TraceController.settingsARP_COMPONENT_TRACECONTROLLER
NmUtilities.NmPlcStateListenerNmUtilities::NmPlcStateListener::NmPlcStateListenerComponent
DataLoggerDataLogger::DataLoggerComponentDataLogger/dataLogger.settingsARP_COMPONENT_DATALOGGER
AppManagerAppManager::AppManagerComponentAppManager/AppManager.settingsARP_COMPONENT_APPMANAGER
LinuxSyslogLinuxSyslog::LinuxSyslogComponentLinuxSyslog/LinuxSyslog.settingsARP_COMPONENT_LINUXSYSLOG
LoggingLogging::LoggingComponentARP_COMPONENT_LOGGING
LogManagerLogging::LogManagerComponentARP_COMPONENT_LOGMANAGER
LogProviderLogging::LogProviderComponentARP_COMPONENT_LOGMANAGER
SpnsProxySpnsProxy::SpnsProxyComponentSpnsProxy/spnsproxy.settingsARP_COMPONENT_SPNSPROXY

1 Only on AXC devices.

Disabling Features

By default, the PLCnext runtime starts a large number of components. These components implement services or features on the device. In some circumstances it may be desirable to disable some of these features - for example, you want to free the resources that unused services would otherwise consume.

It is possible to disable some system features through the Web-Based Management (WBM) page of the device. This is described in the PLCnext Info Center:

https://www.plcnext.help/te/WBM/Configuration_System_Services.htm

In the next chapter, you will see how to add your own custom component to this list.

RSC Services

For PLCnext runtime features that are user configurable, configuration can usually be performed through either the web-based management interface, or modifying text files on the PLC.

Some PLCnext runtime features also provide an application programming interface (API) in the form of Remote Service Calls, or RSC Services.

A list of the RSC services that are available in the PLCnext runtime is given in Appendix B.

RSC services can be accessed at least in two ways:

  • Using an SDK to link to the relevant C++ shared object libraries. This provides local access to RSC services running on the same device as the C++ client.
  • Via gRPC, either locally via a Unix Domain Socket or remotely using Transport Layer Security (TLS).

In the next chapter, you will:

  • Write your own PLCnext runtime component.
  • Call RSC services from that component.

Extension Components

In the previous chapter, you saw how the PLCnext runtime uses factory-installed components to implement various functions.

It is possible to extend the PLCnext runtime with your own components. These are called Extension Components (also called Internal Function Extensions).

In this chapter, you will write your own extension component that is started by the PLCnext runtime, and learn more about the Application Component Framework (ACF) that makes this possible.

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 a lib 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 for MyProject 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 inherited LibraryBase.
    • Create an instance of MyNamespace::MyComponent, called MyComponentInstance, from the library named MyProject, in the process named MyProcess.

    The process parameter is optional and, if omitted, the component instance will be created in the main PLCnext Runtime process (called MainProcess).

  • 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!

Component Library Singleton

You won't need to worry too much about the MyLibrary singleton that you created with your extension component. This singleton provides a way for the PLCnext runtime to create named instances of your ACF component(s).

Each shared object file must only contain one singleton that inherits LibraryBase. This singleton can usually remain unchanged as you develop your components. The only time that this singleton must be modified is when you add or remove components from your library.

To add a component to the library:

  • Create the new component in your C++ project. These two source files contain the definition for a component named MyOtherComponent, which you can add to the src directory of the project you created in the previous section:

  • Add the header file for MyOtherComponent to the library .cpp file.

  • In the library constructor, add a call to the method componentFactory.AddFactoryMethod to register the new component.

    The new library source file can be downloaded here:

  • Rebuild the project and copy the resulting .so file to the target.

  • You can create an instance of MyOtherComponent by adding a new entry to the .acf.config file, as follows:

     <Component name="MyOtherComponentInstance" type="MyNamespace::MyOtherComponent" library="MyProject" process="MyProcess" />
    
  • Copy the new configuration file to the target and restart the PLCnext runtime. You will now see these entries in the Output.log file:

INFO  - Component 'MyComponentInstance' in process 'MainProcess' created.
INFO  - Component 'MyOtherComponentInstance' in process 'MainProcess' created.

Component Methods

After completing the previous sections, an instance of your PLCnext extension component - called MyComponentInstance - has been created by the PLCnext runtime. At the moment, this component instance is not doing much. The PLCnext runtime has called a number of the methods that you defined in the MyComponent class, but because these methods are empty, we don't see any evidence that this has happened. Let's write some messages to the Output.log file, so we can see when these methods are called.

One way that our component instance can write to the Output.log file is if it inherits a base class created from the template Arp::System::Commons::Diagnostics::Logging::Loggable.

The main steps to do this are:

  • Add the header file to the .hpp file of our component:

    #include "Arp/System/Commons/Logging.h"
    
  • Inherit the Loggable template class, by adding the following to the component class definition:

    private Loggable<MyComponent>
    

    The component class thereby inherits a private field - called log - of type Arp::System::Commons::Diagnostics::Logging::Log.

  • Add log messages where required.

    In this case, we have added a log message to every method in the component's .cpp file. The resulting source files are available here:

    You can use these files to update the project you created in the previous section.

  • Rebuild the project.

  • Copy the resulting library file to the target.

  • Restart the PLCnext runtime:

    # sudo /etc/init.d/plcnext restart && tail -f -n 0 /opt/plcnext/logs/Output.log
    

Among the messages that appear in the Output.log file, you should see the following:

INFO  - MyComponent::Initialize
INFO  - MyComponent::SubscribeServices
INFO  - MyComponent::LoadSettings - settings path = ''
INFO  - MyComponent::SetupSettings
INFO  - MyComponent::PublishServices
INFO  - MyComponent::LoadConfig
INFO  - MyComponent::SetupConfig

These methods are called, in sequence, when the component instance is created by the PLCnext runtime. Other methods are called when the PLCnext runtime shuts down, just before the component instance is destroyed. Some types of components even have methods that are called during normal PLCnext runtime operation, under certain circumstances. For example, it is possible for a component to be notified when the PLC component - which executes deterministic, real-time tasks - stops and restarts.

Each of these methods allows the component to perform one or more common functions, if required, at the appropriate time. For example, the SubscribeServices method, as the name suggests, is a good place for the component to subscribe to any RSC services that it needs to use.

The table below shows when ComponentBase methods are called on ACF components. It is based on a similar table in the PLCnext Info Center.

MethodCalled on an ACF component …
Initialize
SubscribeServices
LoadSettings
SetupSettings
PublishServices
During the first stage of ACF startup *
LoadConfig
SetupConfig
During the second stage of ACF startup *
ResetConfigDuring the first stage of ACF shutdown
DisposeDuring the second stage of ACF shutdown
PowerDownOnly when an unexpected power loss occurs,
and only on devices that have a power status sensor

In the above table, all the methods listed in the sections marked with * are called one after the other on each component instance in the order listed, so in practice it makes no difference in which of these method(s) the user chooses to implement their application-specific code.

You can see that only one of the standard component methods has a parameter. The const String& parameter on the LoadSettings method allows this method to receive a string value. This parameter is generally used by firmware components to get the path to a settings file - hence the parameter name. However, you can use this parameter to pass any string information to your component instance during startup:

  • In the .acf.config file, replace this line:

      <Component name="MyComponentInstance" type="MyNamespace::MyComponent" library="MyProject" process="MyProcess" />
    

    ... with these lines:

      <Component name="MyComponentInstance" type="MyNamespace::MyComponent" library="MyProject" process="MyProcess">
        <Settings path="Settings data" />
      </Component>
    
  • Copy the .acf.config file to the target, and restart the PLCnext runtime.

You should now see a line in the Output.log file with this message:

INFO  - MyComponent::LoadSettings - settings path = 'Settings data'

Using this method, it is possible to pass user-configurable String data to the component during component startup.

Using RSC Services

Previously you learned that some PLCnext runtime features provide an application programming interface (API) in the form of Remote Service Calls, or RSC Services.

In this section, our ACF component will call C++ API on the Device Info RSC service. The Device Info service provides access to Device Information. You can use a similar technique to access any RSC service in the PLCnext Runtime.

Here are the steps to use the C++ API on the Device Info RSC service:

  • Add the relevant header file to the .hpp file of the component:

    #include "Arp/Device/Interface/Services/IDeviceInfoService.hpp"
    
  • Declare a pointer to the Device Info service:

    IDeviceInfoService::Ptr deviceInfoServicePtr = nullptr;
    
  • In the component .cpp file, add the header for the RSC Service Manager:

    #include "Arp/System/Rsc/ServiceManager.hpp"
    
  • In the SubscribeServices method of the ACF component, get a pointer to the Device Info service:

    this->deviceInfoServicePtr = ServiceManager::GetService<IDeviceInfoService>();
    
  • In the SetupConfig method of the ACF component, use the Device Info service to read the device serial number, and log the value to the Output.log file:

    RscVariant<512> serialNumber = this->deviceInfoServicePtr->GetItem("General.SerialNumber");
    if (serialNumber.GetType() == RscType::String)
    {
       this->log.Info("Serial number of this device: {0}%", serialNumber.GetChars());
    }
    else this->log.Info("Error reading device serial number");
    
  • The above code is included in these source files:

    You can use these files to update the project you created in the previous section.

  • Rebuild the project.

  • Copy the resulting library file to the target.

  • Restart the PLCnext runtime:

    # sudo /etc/init.d/plcnext restart && tail -f -n 0 /opt/plcnext/logs/Output.log
    

Among the messages that appear in the Output.log file, you should see an INFO message from your component containing the serial number of your device.

Worker Threads

After completing the previous section, you may have your eye on another quite interesting RSC service - the Data Access service. This service provides access to Global Data Space (GDS) variables in the PLCnext runtime.

Many variables in the GDS will change over time, and it may be necessary for an ACF component to exchange data with GDS variables during the lifetime of the component. However, the only component methods we have seen so far are called during the startup or shutdown of the component. It would be useful to have a component method that is executed periodically during the complete lifetime of the component, so that (for example) data can be exchanged with GDS variables using the Data Access service.

The ARP provides a worker thread class that can be used for this purpose. An ACF component can create a worker thread and, in the thread constructor, specify:

  • an execution frequency, and
  • a method that will be executed on the worker thread at that frequency.

An example of how to use worker threads (and other types of ARP threads) is provided in Github.

ARP threads, including worker threads, are not designed to be used for "real time" processing. Worker threads should not be confused with Cyclic Tasks in a PLC. The execution period of a worker thread is not deterministic, and the actual execution frequency cannot be guaranteed.

For applications where a deterministic task cycle is not required, a worker thread may be a good solution.

Many of the C++ examples in Github use a worker thread to call various RSC services. For example, the Data Access example uses a worker thread to read and write GDS variable data using the Data Access service.

Creating GDS variables - Part 1

You have seen how the Global Data Space (GDS) provides a simple way to exchange data between otherwise isolated PLCnext runtime components.

It is possible for your own component instance to create variables in the GDS. These variables can then be accessed by other PLCnext runtime components using (for example) the Data Access RSC service, as discussed the previous section.

GDS variables can also:

  • be included in the OPC UA server address space,
  • have their value persisted (or "retained") across restarts of the PLCnext runtime,
  • have their value logged to a Proficloud Time Series Data (TSD) service,
  • be accessed through a REST API,
  • be used on web-based HMI pages,
  • be used to exchange data with physical I/O modules.

In order for a component to create its own variables in the GDS, the component must inherit from MetaComponentBase, rather than from ComponentBase. The library class must also inherit from MetaLibraryBase. Additional code must also be added to the library and the component class, e.g. the RegisterComponentPorts method must be implemented in the component.

Rather than making these changes to the source code manually, we will make use of the tool you probably used to install the SDK - the PLCnext Command Line Interface, or plcncli.

PLCnext CLI

You previously used the PLCnext CLI to install the SDK that you have been using to build your C++ projects for the target. However, the PLCnext CLI does more than just manage SDKs.

You can read more about the features of the PLCnext CLI in the PLCnext Info Center. These features include:

Code Generation

PLCnext CLI can automatically generate code in a number of scenarios.

Initial project setup

The PLCnext CLI can generate customised C++ source and configuration files for different types of projects, based on code templates in the Templates directory (in the plcncli installation directory). This is useful when starting a new C++ project that targets a PLCnext Control device.

The templates that are installed with the PLCnext CLI are also available in Github.

It is possible to create your own code templates that can be used with the PLCnext CLI. At the moment the required structure of PLCnext CLI templates is not documented, but this is planned for the future.

Add components and programs to a project

There are PLCnext CLI commands for adding C++ components and programs to an existing project. These commands are useful when your C++ project requires more than the single C++ component and/or program that are created by the PLCnext CLI in a new C++ project.

Generate 'intermediate' code

Intermediate code includes "boilerplate" C++ code that is required for a PLCnext runtime project, but which can be generated automatically e.g by parsing the C++ components and programs in that project. Intermediate files include:

  • The Library singleton that is required in a PLCnext runtime library.
  • Code required to create GDS variables for your C++ component and/or program in the Global Data Space.

You should not edit any of the intermediate files that are auto-generated by the PLCnext CLI. These files are regenerated whenever the plcncli generate command is executed, and at that point any manual changes to those files will be lost.

Managing build target(s)

Each C++ project generated by the PLCnext CLI includes a list of build targets for that project. This list can be managed using plcncli commands.

Building the project

The PLCnext CLI can build a C++ project for the selected target(s) with one command - plcncli build.

The next section will use the PLCnext CLI to create and build a C++ project containing one ACF component.

Creating GDS variables - Part 2

Now that we have seen how the PLCnext CLI can help us, let's use it to create a project containing an ACF component that creates some GDS variables.

  • Create a new project on the host system:

    $ plcncli new acfproject --name MyGdsProject --namespace MyNamespace --component MyComponent --verbose
    
  • Set the build target for the project:

    $ cd MyGdsProject
    $ plcncli set target --add --name AXCF2152 --version 2021.9
    

    If the target is ambiguous or cannot be found, you can use the following command to see the list of installed targets that the PLCnext CLI knows about:

    $ plcncli get targets
    
  • Edit the source code using your favourite editor. In this case we will use Visual Studio Code:

    $ code .
    

    You can see that the PLCnext CLI has created the following source and configuration files in the MyGdsProject directory:

    ├── CMakeLists.txt
    ├── external
    │   └── ADD_DEPENDENT_LIBRARIES_HERE.txt
    ├── How_to_deploy.txt
    ├── plcnext.proj
    └── src
        ├── MyComponent.cpp
        ├── MyComponent.hpp
        └── MyGdsProjectLibrary.acf.config
    
  • Add a GDS variable to the section indicated in the component .hpp file:

    //#port
    Arp::uint8 MyGdsVariable;
    

    GDS variables are also referred to as "ports". The PLCnext Info Center provides detailed information on GDS port definitions in C++ code, including the attributes that can be applied to GDS ports defined in this way.

  • Change the .acf.config file if necessary, e.g. the path to the shared object library file.

  • Save the edited file(s)

  • On the command-line, use the PLCnext CLI to generate the intermediate files for the project:

    $ plcncli generate code --verbose

    You can see that the PLCnext CLI has now created a lot of files in the MyGdsProject directory, including the following:

    ├── intermediate
    │   └── code
    │       ├── MyComponent.meta.cpp
    │       ├── MyGdsProjectLibrary.cpp
    │       ├── MyGdsProjectLibrary.hpp
    │       └── MyGdsProjectLibrary.meta.cpp
    

    These C++ source files contain additional code required for the PLCnext runtime to create instances of the C++ component and its GDS port(s).

  • Build the project:

    $ plcncli build --verbose
    

    This command uses CMake to build the project for all the specified targets.

  • Deploy the shared object library and the .acf.config file to the PLC as you've done previously.

  • Restart the PLCnext Runtime.

Your ACF component instance now includes a GDS port in the Global Data Space that can be accessed by other components using the Data Access RSC service.

Connecting GDS Variables

Consider the following question:

You have an ACF component that needs to know the current average CPU load of the device it's running on. The component starts a worker thread that executes a method periodically. How can that method read the average CPU load of the device?

Using what you've learned so far in this chapter, you might consider two possible solutions:

  1. Use the GetItem method on the Device Status RSC service to read the CPU load of the device, using the Status.Cpu.0.Load.Percent item specifier.

  2. Use the ReadSingle method on the Data Access RSC service to read the CPU load of the device from the GDS port variable named Arp.Plc.Eclr/DEVICE_STATE.CPU_LOAD_ALL_CORES.

Those are both perfectly acceptable solutions, but in this case there is a third, simpler solution.

The GDS component in the PLCnext Runtime provides the facility to "connect" any GDS OUT ports to any GDS IN port(s) of compatible type, without any code. Once these ports connections are configured, the PLCnext runtime will automatically transfer data from each IN port to each connected OUT port.

The steps to do this are:

  • Starting with the example in the previous section, add a worker thread that periodically executes a method that prints the value of MyGdsVariable to the Output.log file.

  • In the project src directory, create a file named MyGdsProject.gds.config, containing the following:

    <?xml version="1.0" encoding="utf-8"?>
    <GdsConfigurationDocument xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" schemaVersion="1.0" xmlns="http://www.phoenixcontact.com/schema/gdsconfig">
      <ComponentTaskRelations />
      <Connectors>
        <Connector startPort="Arp.Plc.Eclr/DEVICE_STATE.CPU_LOAD_ALL_CORES" endPort="MyComponent1/MyGdsVariable" />
      </Connectors>
    </GdsConfigurationDocument>
    

    This configuration file will create a GDS port connector that will automatically transfer data between the startPort and the endPort. In this case the endPort is the GDS port on your component instance.

  • Build and deploy your project in the usual way.

  • Copy the GDS configuration file from the host to the target:

    $ scp src/MyGDSProject.acf.config admin@192.168.1.10:~/projects/Default/Plc/Gds
    

    The PLCnext runtime will automatically load all files in this destination directory that match the pattern *.gds.config (this is specified in the file /etc/plcnext/device/Plc/Gds/Gds.settings).

  • Restart the PLCnext runtime.

    You should now see the value of the CPU load appearing in the Output.log file.

A benefit of this file-based configuration technique is that the .gds.config file can include I/O ports - that is, GDS port variables that are used to exchange data with inputs and outputs on physical I/O modules. The Data Access RSC service, on the other hand, cannot exchange data directly with I/O port variables.

One disadvantage of this technique is that the names of all the GDS ports must be known at design-time, since variable names must appear in a .gds.config file. When using the Data Access RSC service, on the other hand, the name of each GDS port is passed as a String variable, and so can be specified at run-time.

Interactive Course

You can sharpen up your knowledge on the topics that have been covered in this chapter by working through the interactive online course Internal Function Extensions on the PLCnext Community website.

Real Time Programming

The previous chapter showed how to write PLCnext runtime applications that do not require real-time, deterministic performance. The priority of the process executing ACF components (including worker threads) is no higher than other threads or processes on the device, and as a result the "jitter" - or the variation in the execution period - is likely to be very high for worker threads. This is fine for many applications, but some applications require a more deterministic execution period, like is available on traditional PLCs.

The Linux kernel on PLCnext Control devices includes the PREEMPT-RT patch, so it is possible to execute code with real-time priority. On its own, the use of this feature requires extensive experience of C++ and POSIX thread handling.

Luckily for us, the PLCnext runtime includes a set of PLC components, which allow users to easily implement C++ applications that have the same deterministic performance as applications on traditional PLCs. One of these PLCnext runtime components is the Execution and Synchronisation Manager (ESM), which - as the name suggests - is responsible for scheduling and executing real-time tasks.

The ESM uses terminology from the IEC 61131 standard, which PLC programmers will be familiar with. For example, a program is a piece of code that will be executed in real time. One or more named program instances are created in a task. Tasks can be executed cyclically with a fixed period, or executed when other criteria are satisfied e.g. on a specified event. When a task is executed, the program instances defined in that task will be executed one after the other, in the order specified. It is possible for a PLC application to contain multiple tasks. On PLCnext Control devices, it is possible to assign tasks to one of multiple ESM instances. Each ESM instance corresponds to a core on the device CPU, so (for example) an AXC F 2152 with two CPU cores has two ESM instances in the PLCnext runtime.

There is more information about the ESM in the PLCnext Info Center.

In the previous chapter we saw how to configure the Global Data Space using a .gds.config file in XML format. The ESM is configured in a similar way, using an .esm.config file. This configuration file:

  • Defines real-time tasks.
  • Assigns each task to an ESM instance.
  • Defines real-time program instances.
  • Assigns program instances to tasks.

This chapter will discuss C++ programs, and look at how to execute these on a PLCnext Control device.

C++ Programs

PLCnext runtime programs created in C++ must inherit from ProgramBase.

Program instances are created using a special type of PLCnext component. While standard ACF components are only required to inherit from ComponentBase, components that can create program instances must also inherit from ProgramComponentBase. The corresponding library singleton must also inherit from ProgramLibraryBase. When the PLCnext runtime starts, this library is loaded not by the ACF, but instead is loaded by the Program Library Manager (PLM). The PLM creates non-real-time component instances in a similar way to the ACF, but it also has the ability to create instances of real-time programs.

A high-level comparison of the ACF and the PLM is given in the PLCnext Technology Info Center.

Like in the previous chapter, we can use the PLCnext CLI to generate a project template containing a C++ program:

  • Create a new project on the host system:

    $ plcncli new project --name MyProject --namespace MyNamespace --component MyComponent --program MyProgram --verbose

    In this case we are using the PLCnext CLI template called project, whereas in the previous chapter we used the template called acfproject. You can also see that there is now a program name specified in the command along with the component name.

  • Set the build target for the project:

    $ cd MyProject $ plcncli set target --add --name AXCF2152 --version 2021.9

  • Edit the source code using your favourite editor.

    You can see that the PLCnext CLI has created the following source and configuration files in the MyProject directory:

    ├── CMakeLists.txt
    ├── external
    │   └── ADD_DEPENDENT_LIBRARIES_HERE.txt
    ├── plcnext.proj
    └── src
        ├── MyComponent.cpp
        ├── MyComponent.hpp
        ├── MyProgram.cpp
        ├── MyProgram.hpp
    

    This project now includes both component and program source files, and does not include a .acf.config file.

  • Look at the MyComponent.cpp and MyComponent.cpp files.

    These files implement a program component, which looks quite similar to the ACF component you saw in the previous chapter. You can do almost everything in a program component that you can do in an ACF component, including declaring GDS port variables on the component.

  • Look at the MyProgram.hpp file.

    You can see from the comments that it is possible to declare GDS port variables on a program. This means that program instances can have GDS ports, and those ports can be connected to GDS ports on other components and programs. Just like in the previous chapter, these GDS connections are specified in a .gds.config file.

  • Look at the MyProgram.cpp file.

    The Execute method you see here will be called by a real-time task. That real-time task will be scheduled to run on one of the Execution and Synchronisation Manager (ESM) instances created by the PLCnext runtime. Every time the real-time task runs, the Execute method on all program instances in that real-time task will be executed in the order that those program instances appear in the task. If the task is a cyclic task, then the Execute method will be called at the period of the cyclic task.

You can now go ahead and fill the Execute method with your real-time-dependent code.

Real-time C++ Programming Guidelines

The Execute method in a C++ program must not contain any code that could compromise the strict deterministic performance of the system. The Execute method must complete within a "reasonable" period of time. This must be considered this when using time-consuming program constructs like loops.

Perhaps the most common mistake made in a developer's first real-time C++ program is to treat the Execute method like the main function in a C++ application, and include something like a while(true) loop in that method. Not only is this not necessary (the Execute method will be called at a fixed frequency from a cyclic task), this type of endless loop will immediately "crash" the PLC because the Execute method will never exit, and either the task watchdog timer or the system watchdog timer will be exceeded.

Here are some other tips for real-time C++ programming:

  • Never allocate or re-allocate memory in the Execute method. e.g.

    • don't use malloc
    • don't use new
    • don't assign a value to a string variable unless you know it will not result in memory re-allocation.
    • be careful when using classes like std::vector, since these can allocate memory automatically.
  • Never use std::mutex in a real-time C++ program. If mutexes are required, use Arp::Mutex.

  • Don't use third-party code (e.g. libraries) if that code does any of the above. Since third-party code is generally not designed with these limitations in mind, this means it is very unlikely that you will be able to use any third-party code directly in a C++ program without modification.

  • Do not call methods on RSC services from the Execute method.

To help make sure you keep within these guidelines:

  • Perform all memory allocation in the program constructor, e.g. allocating memory for the maximum expected size of all string and vector variables.
  • Check any operations that might re-allocate memory in the Execute method (e.g. vector resizing).
  • Consider doing non-real time work in the component that is associated with the program, and use Arp::Mutex to synchronise variable access between the component and the program.
  • If in doubt, don't use third party code in the Execute method.

Building and Deploying a Real-time C++ Application

Real-time C++ applications can be built using the PLCnext CLI, in a similar way to the ACF applications in the previous chapter:

  • On the command-line, use the PLCnext CLI to generate the intermediate files for the project:

    $ plcncli generate code --verbose
    
  • Generate the configuration files for the project:

    $ plcncli generate config --verbose
    

    This step - which is not required for ACF projects - creates configuration files that are used by the Program Library Manager.

  • Build the project:

    $ plcncli build --verbose
    

    This command uses CMake to build the project for all the specified targets.

Deploying

It is possible to deploy a real-time C++ project by copying binary and configuration files to specific directories on the target, in a similar way to the ACF projects in the previous chapter. This procedure, which is more complicated than for ACF projects, is described in the Github repository PLM/ESM/GDS Configuration without PLCnext Engineer.

There is a simpler way to generate the required configuration files and deploy them to the target, and that uses PLCnext Engineer software running on a Windows machine. To use your C++ project in PLCnext Engineer, it must be packaged as a PLCnext Engineer library. This can be done using the following PLCnext CLI command:

$ plcncli deploy --verbose

This will create a file with the extension .pcwlx, which is a PLCnext Engineer library file.

When this library is added to a PLCnext Engineer project, then the configuration of tasks, program instances and GDS port connections can all be done graphically in PLCnext Engineer. The complete project can also be deployed to the target from PLCnext Engineer. This process is described in a tutorial video from Phoenix Contact.

If your project does not preclude the use of PLCnext Engineer, then this is the easiest way to configure and deploy a real-time C++ application to a PLCnext Control device.

IEC 61131-3 Programs

In the previous section you saw how to deploy a real-time C++ project using PLCnext Engineer software.

Using PLCnext Engineer, it is also possible to write real-time programs in any or all of the four languages defined by the IEC 61131 standard - Ladder (LD), Function Block Diagram (FBD), Structured Text (ST) and Sequential Function Chart (SFC). Programs written in these languages run on the Embedded Common Language Runtime (ECLR), and programs written in these languages can run alongside - and exchange GDS data with - real-time programs and components written in C++.

ECLR

As the name suggests, the Embedded Common Language Runtime is based on Microsoft's Common Language Runtime (CLR), which is required to run C# and other .NET programs on Windows platforms. Unlike the CLR, the ECLR is specifically designed to run real-time, deterministic programs on embedded devices. The ECLR contains a sub-set of CLR libraries, along with specialised libraries added by Phoenix Contact.

Phoenix Contact provides a Visual Studio extension that helps write C# code that targets the ECLR. In this way, it is possible to write real-time programs, function blocks and functions in C#.

IEC 61131 and ECLR programming is outside the scope of this book, but there is plenty of information on these topics in the PLCnext Info Center.

Configuring I/O

Now that you know more about the PLCnext runtime and how to write C++ applications for PLCnext Control devices, we can return to the question that was asked at the end of Chapter 1: How can an application read and write inputs and outputs from/to the I/O modules attached to the PLCnext Control device?

The simplest way to do this is as follows:

  • Using PLCnext Engineer software, create a new project for your target device and configure the I/O modules that are connected to the controller via Axioline, Interbus and/or Profinet. Send the PLCnext Engineer project to the PLC.

  • In PLCnext Engineer, take note of the names of the GDS ports that are automatically created for the channels on each I/O module.

  • In a .gds.config file, connect the GDS ports from the I/O modules to corresponding GDS ports on your ACF component(s) and/or real-time program(s).

In this way, C++ components and programs can easily exchange data with field devices via the I/O modules attached to the PLCnext Control device.

IoConf

If your application precludes the use of PLCnext Engineer for some reason, then it is possible to generate the same I/O configuration files that PLCnext Engineer creates using a .NET class library called IoConf. The use of this tool is demonstrated in a tutorial on Github.

Axioline Master service

Another I/O configuration option is given in the Bus Conductor example on Github. This solution is suitable when the precise I/O configuration is not known at design-time, or if the configuration must be changed dynamically at run-time.

Other Programming Tools

So far we have been using the PLCnext CLI from the command-line, and using an integrated development environment (IDE) like Visual Studio Code simply to edit source files.

For those who prefer to use either Visual Studio (on Windows) or Eclipse IDE (on Windows or Linux), Phoenix Contact provides the following tools with each PLCnext CLI installation:

  • A PLCnext extension for Visual Studio (Windows only).

  • A PLCnext add-in for Eclipse IDE (Windows and Linux).

By using these tools, it is possible to perform most PLCnext CLI functions from within the IDE.

As well as assisting with C++ development for PLCnext Control devices, the Visual Studio extension also includes tools for writing C# code that targets the ECLR.

The use of these tools is described extensively in the PLCnext Info Center, and will not be covered in this book.

Extension Processes in C/C++

You have seen how to use the Application Component Framework to extend the PLCnext runtime with your own components. By using the ACF, these components are integrated tightly with the PLCnext runtime, where they are treated in exactly the same way as factory-installed components.

It is also possible to extend the PLCnext runtime with extension processes (also called External Function Extensions).

A high-level comparison of extension components (internal function extensions) and extension processes (external function extensions) is given in the PLCnext Technology Info Center.

Extension processes are useful for PLCnext Control functions that must be started and stopped with the PLCnext runtime - just like extension components - but that cannot or do not want to use the ACF. A typical example: When porting a third-party PLC runtime to the PLCnext Control platform, it may be impractical to re-design the source code into PLCnext runtime components.

One challenge faced by extension processes is that GDS ports cannot be created on extension process. So the question once again arises: How can an extension process read and write inputs and outputs from/to the I/O modules attached to the PLCnext Control device?

You have already seen how to read and write GDS variable data using RSC services, and RSC services can also be used to (indirectly) access PLC I/O from any process running outside the PLCnext runtime. However, as discussed previously, RSC services are only suitable for non-real time applications, like ACF components. This solution may be fine for any extension process that does not require deterministic I/O access.

For more demanding applications, the PLCnext runtime provides a mechanism for extension processes to read and write PLC I/O in a deterministic way. This deterministic access to PLC I/O makes extension processes ideally suited to third-party runtimes like Codesys, UAO Runtime and 4diac. In this case the solution involves the use of an ANSI-C interface. This type of solution is described in detail in the Sample Runtime tutorial on Github.

What Next

  • Revise what you've learned in this book.
  • Find out more about other parts of the PLCnext Technology ecosystem.
  • Get involved!

The Big Picture

This book has covered almost every part of the PLCnext Control platform, including an extensive step-by-step exploration of the PLCnext runtime. At this stage, it is beneficial to zoom out and take a look at how all the pieces of the PLCnext Control platform fit together. One of the best ways to do this is to work through the interactive online course PLCnext Technology Basics on the PLCnext Community website. In addition to the topics covered in this book, the course will introduce you to the other parts of the PLCnext Technology Ecosystem, including PLCnext Engineer and the PLCnext Store.

Get Involved!

PLCnext Community. This includes a discussion forum and a Makers Blog, both of which are open to public contributions.

Github. Contribute to open-source projects.

Appendix

Appendix A: PLCnext Control programming resources

General Development Guides

C++

Rust

Docker

Java

Node.js and Node-Red

Python

C#

Go

Appendix B: List of RSC Services

A complete list of public RSC services is available in the PLCnext Info Center.