5Structure of a C++ program

inset_35.jpg 

 

If you want to consult the PLCnext Technology SDK C++ header files in parallel while reading this section, first install the necessary tools as described in Creating programs with C++.

inset_40.jpg 

 

When naming PLCnext Technology components, observe the naming conventions in PLCnext Technology naming conventions.

With PLCnext Technology, programs that were created in different programming languages can be used together. For instantiation and calling by the PLCnext Technology firmware, the programs have a uniform basis. This basis applies to all programming languages. PLCnext Technology follows an object-oriented approach. The following base classes are relevant for creating a C++ program for the PLCnext Technology platform.

Library

LibraryBase class

The LibraryBase class is the smallest unit that can be downloaded. It represents an *.so file (shared object). One “Library” can instantiate one or more “Components”.

Component

ComponentBase class

The ComponentBase class is a collection of functions (programs) within the PLCnext Technology platform.
One “component” can instantiate one or more “programs”. The “Program” instances can interact via their shared “Component” instance.

Program

ProgramBase class

With PLCnext Technology, instances of the ProgramBase class can be performed in real time.
Here, the IN and OUT ports are published.

The base classes are stored in the Phoenix Contact SDK. Derive from these base classes to create your application.

Some sample applications programmed in C++ can be found at https://github.com/plcnext.

5.1“ILibrary” and “LibraryBase”

inset_27.jpg 

 

Using the PLCnCLI (see Section 6.1, “PLCnCLI (PLCnext Command Line Interface)”) or the Eclipse® add-in creates the meta configuration files (libmeta, compmeta, progmeta) required for PLCnext Engineer as well as the following functions with a functional implementation during compiling. If you have special requirements that go beyond this, the following descriptions will help you understand the functions.

In a shared object (*.so), there can be exactly one library class (singleton pattern). The PLCnext Technology firmware initializes the *.so after it has been loaded by calling the following function:

 

extern "C" ARP_CXX_SYMBOL_EXPORT ILibrary& ArpDynamicLibraryMain(AppDomain& appDomain);

 

This function is implemented in such a way that the library class is created and returned as an interface. The library object implements the “ILibrary” interface by means of derivation from the “LibraryBase” class. “ILibrary” is defined in header Arp/System/Acf/ILibrary.hpp, and “LibraryBase” in Arp/System/Acf/LibraryBase.hpp. The “LibraryBase” class particularly implements a “ComponentFactory” with the “IComponentFactory” interface.

The PLCnext Technology firmware can thus create instances of the components when loading the PLC program. In order for the PLCnext Technology firmware to be able to deter­mine the necessary information (names of components and program types, program ports), the library class also has to implement the “IMetaLibrary” interface (#include “Arp/Plc/Commons/Meta/IMetaLibrary.hpp”). This interface then requires implemen­tation of the “ITypeInfoProvider” interface (#include “Arp/Plc/Commons/Meta/TypeInfoProvider.hpp”), whereby the firmware learns the names of the components and program types as well as their IN and OUT ports.

The “MetaLibraryBase” class (#include “Arp/Plc/Commons/Meta/MetaLibraryBase.hpp”) therefore expands the “LibraryBase” class by these interfaces.

The component instances the PLCnext Technology firmware creates is defined in the *.acf.config and *.plm.config files.

“AppDomain” and “IApplication

If you use the Eclipse® add-in, the required header files are included automatically.

“AppDomain”: Arp/System/Core/AppDomain.hpp

“IApplication”: Arp/System/Acf/IApplication.hpp

The PLCnext Technology firmware is distributed among several operating system pro­cesses.

“AppDomain”

An “AppDomain” represents such an operating system process. If a library is to be used in several processes, singleton implementation is performed for each process. The “AppDomain” class is used for this. Since the PLCnext Technology firmware uses the same mechanism for instantiation, initialization and administration of user components as it does for core components, the “AppDomain” is also used here.

“IApplication”

The operating system process is represented by the “IApplication” interface.

5.2“IComponent” and “ComponentBase”

inset_43.jpg 

 

Using the PLCnCLI (see Section 6.1, “PLCnCLI (PLCnext Command Line Interface)”) or the Eclipse® add-in creates the meta configuration files (libmeta, compmeta, progmeta) required for PLCnext Engineer as well as the following functions with a functional implementation during compiling. If you have special requirements that go beyond this, the following descriptions will help you understand the functions.

The basic integration of a component into PLCnext Technology is implemented by means of derivation from the “ComponentBase” class or via the “IComponent” interface. There are specialized components for various purposes. These specializations are performed by im­plementing additional interfaces, which are described in the following subsections. To be able to use the classes, you have to include (#include) the corresponding header files (.hpp).

“IComponent”: Arp/System/Acf/IComponent.hpp

“ComponentBase”: Arp/System/Acf/ComponentBase.hpp

The following “IComponent” operations are called by the ACF or PLM for each component:

void Initialize(void) 

void SubscribeServices(void) 

void LoadSettings(const String& settingsPath) 

void SetupSettings(void) 

void PublishServices(void) 

In the second phase, the project configuration is loaded and set up. If an exception occurs here, the project configuration is unloaded again and the controller starts with an empty con­figuration. The ACF or PLM calls the following “IComponent” operations:

void LoadConfig(void) 

void SetupConfig(void) 

Initialize

The component instance is initialized via the functions specified below. These have to be implemented in the component class. For each instantiated component, the PLCnext Technology firmware calls the following function first:

 

virtual void Initialize(void);

 

SubscribeServices

Resources that have been allocated and initialized for the component in the Initialize() function have to be enabled in the Dispose() function. Thereafter, the firmware calls the following function for each instantiated component.

 

virtual void SubscribeServices(void);

 

Here, a component can obtain RSC services that have already been registered.

LoadSettings

Subsequently, the LoadSettings() function is called. The path to the settings (settingsPath) can be specified for the respective component instance in the *.acf.config configuration file. The format and content of this configuration file have to be specified by the respective component (type). The PLCnext Technology firmware does not make any as­sumptions regarding these.

 

virtual void LoadSettings(const String& settingsPath);

 

SetupSettings

Once the settingsPath has been specified, the settings can be applied. The following function is used for this:

 

virtual void SetupSettings(void);

 

PublishServices

The PLM does not call the PublishServices function. Creating and registering RSC ser­vices is reserved for the core components.

 

virtual void PublishServices(void);

LoadConfig/SetupConfig

When the project is subsequently loaded, the following functions are called:

 

virtual void LoadConfig(void);

virtual void SetupConfig(void);

 

ResetConfig

The configuration of the components is reset with the following function:

 

virtual void ResetConfig(void);

 

The interface is identical for the user program and the internal user component. It is simply called at different times.

The PLC manager manages components that make user programs available. The PLC manager configures them in a file referenced by “/opt/plcnext/projects/De­fault/Plc/Plm/Plm.acf.config”.

Internal user components are managed by the ACF (Application Control Framework) and configured respectively in a file referenced by “/opt/plcnext/Default/De­fault.acf.config”. The ACF generates these components when booting the firmware.

Dispose

A component is stopped after calling the following function:

 

virtual void Dispose(void);

5.2.1“IProgramComponent” and “IProgramProvider”

A component that can instantiate user programs implements the “IProgramComponent” and “IMetaComponent” interfaces by means of derivation from the “ProgramComponent­Base” class. The PLCnext Technology firmware requires these interfaces to instantiate pro­grams and receive information on their ports.

The PLCnCLI generates the necessary code. For this, a code is generated for each compo­nent, which then implements the “IProgramProvider” interface. If the //#program directive is prefixed to the header of a program definition, the PLCnCLI knows which component can instantiate which program. The next directive //#component() is used to name the corre­sponding component.

 

//#program

//#component(SampleComponent)

class SampleProgram : public ProgramBase,

 

The corresponding header files (.hpp) are included (#include) so that the classes can be used.

“IProgramComponent”: Arp/Plc/Esm/IProgramComponent.hpp

“IProgramProvider”: Arp/Plc/Esm/IProgramProvider.hpp

The component implements the “IProgramComponent” interface by means of derivation from the “ProgramComponentBase” class. In addition, the component creates a private member variable that is passed to the “ProgramComponentBase” class in the constructor of the component. This member variable implements the “IProgramProvider” interface by means of derivation from the “IProgramProviderBase” class.

The “ProgramProvider” makes the following function available for the instantiation of pro­grams:

 

IProgram::Ptr CreateProgramInternal(const String& programName, const String& programType)

 

The PLCnext Technology firmware calls this function when loading the PLC program.

In the process, the following parameters are passed on in the way they are defined in the *.esm.config files:

Attribute

Description

programName

Instance name of the program

The instance name is configured in the PLCnext Engineer task editor or manually via the *.esm.config file.

programType

Class name of the program

For each component, the *.compmeta and *.progmeta files describe the programs (type) a component (type) can generate.

5.2.2“IProgram” and “ProgramBase”

An instantiated user program implements the “IProgram” interface. As a result, a constructor to which the instance name is passed on and the Execute() function that is called by the ESM task during each pass are available. The PLCnCLI creates such a user program class that inherits the “IProgram” interface from the “ProgramBase” base class by means of deri­vation. Furthermore, the PLCnCLI enables reporting the IN and OUT ports of the program to the GDS.

Creating IN and OUT ports

The header file of your program (e.g., MyProgram.hpp) must contain the port definition. The ports are created as public according to the following pattern.

For example:

 

public:

  //#port

  //#attributes(Input| ...)

  //#name(...)

  int myInPort;

 

A port is specified by an added attribute. The following attributes are available:

Table 5-1 Attributes for port definition

Attribute

Description

Input

The variable is defined as IN port.

Output

The variable is defined as OUT port.

Retain

The variable name is retained in case of a warm and hot restart
(only initialized in case of a cold restart).

Opc

The variable is visible for OPC UA.

Ehmi

The variable is visible for the PLCnext Engineer HMI.

Proficloud

The variable is visible for PROFICLOUD (for OUT ports only).

Multiple attributes are separated by the “|” separator.
E.g.: //#attributes (Input|Opc|Retain)

From the attributes, the PLCnCLI generates the meta data. If a variable does not have an attribute, is can only be used internally by the program.

Provide variables you want to connect via the GDS with an Input or Output attribute, de­pending on the data direction. You can use all other attributes to control the initialization be­havior or enable visibility in OPC UA or a PLCnext Engineer visualization system. You can also use these additional attributes without the Input or Output attributes. This way, the variable is visible but cannot be connected via the GDS. The Proficloud attribute only works in conjunction with the Output attribute.

The variable name is also used as the port name. If you want to name the port differently in the GDS, you can use the //#name() directive to enter a different port name.

To be able to use the classes, you have to include (#include) the corresponding header files (.hpp).

“IProgram”: Arp/Plc/Esm/IProgram.hpp

“ProgramBase”: Arp/Plc/Esm/ProgramBase.hpp

5.2.3“IControllerComponent”

A component created with the PLCnCLI is automatically derived from the “IControllerInterface” interface. An internal user component may need individual lower-pri­ority threads to perform longer tasks outside of the ESM task. To this end, the component can implement the “IControllerComponent” interface in addition to “IComponent”.

The “IControllerComponent” interface defines the following two functions:

 

void Start (void);

void Stop (void);

 

If the component is managed by the PLC manager (“User Program”):

During a cold, warm or hot restart of the PLC application, the Start() function is called after all program objects of the component have been generated. This is an ideal time to start individual, lower-priority threads the programs delegate tasks to.

The Stop() function is called when the PLC application is stopped, before the pro­grams are destroyed. At this point, the created threads can be destroyed again.

If the component is managed by the ACF (Automation Component Framework) (“Internal User Component”):

When the firmware is started (boot or /etc/init.d/plcnext start), the Start() function is called after all components have been generated. This is an ideal time to start threads which perform the component tasks.

When the firmware is stopped (/etc/init.d/plcnext stop), the Stop() func­tion is called before the components are destroyed.

To use this class, you have to include (#include) the corresponding header file (.hpp).

“IProgram”: Arp/System/Acf/IControllerComponent.hpp

5.3Several component types in the same library

inset_33.jpg 

 

Using the PLCnCLI (see Section 6.1, “PLCnCLI (PLCnext Command Line Interface)”) or the Eclipse® add-in creates the meta configuration files (libmeta, compmeta, progmeta) required for PLCnext Engineer as well as the following functions with a functional implementation during compiling. If you have special requirements that go beyond this, the following descriptions will help you understand the functions.

Initially, the Eclipse® add-in creates one component type in a library. If several component types are to be instantiated in the same library, each component type has to be added to the factory. To this end, every component type is introduced to the factory by calling the AddFactoryMethod() function in the constructor of the library object. The PLCnCLI gener­ates the required code if the //#component directive is prefixed in the header of the class definition:

 

//#component

class SampleComponent : public ComponentBase,

 

There are two ways to instantiate components:

“Internal User Components”, managed by the ACF, are instantiated by the
Default.acf.config (projects/Default/Default.acf.config) file.

“User Programs”, managed by the PLM, are instantiated by the
Plm.config (/opt/plcnext/projects/Default/Plc/Plm/Plm.config) file.
When used as PLCnext Engineer library, this is performed automatically by PLCnext Engineer, but it can also be done manually (see Section Task configuration via configuration files ).

5.4PLM (Program Library Manager)

The PLM (Program Library Manager) is part of the PLC manager (see PLC manager). It loads and unloads components during the runtime of the PLCnext Technology firmware. The PLM controls the entire service life of the component instance in accordance with the states of the controller and changes to these states by means of the PLCnext Engineer commands:

Cold or warm restart

Hot restart

Reset

Download (a reset is implicitly performed prior to the download, however, not during Download Changes)

5.4.1Functions

The PLM takes on the role of creating, configuring and destroying the components that can instantiate user programs. The application components are controlled as follows:

Table 5-2 “IComponent” (PLM) functions

Calling “IComponent”

User action in PLCnext Engineer

void Initialize()

Restart

Send project

void SubscribeServices()

Restart

Send project

void LoadSettings(const string & set­tingsPath)

Reboot

Send project

void SetupSettings()

Restart

Send project

void PublishServices()

The function is only called by a firmware component, not by the PLM.

void LoadConfig()

Restart

Cold restart

Warm restart

Send project

void SetupConfig()

Reboot

Cold restart

Warm restart

Send project

void ResetConfig()

Reboot

Cold restart

Warm restart

Send project

void Dispose()

Send project

5.4.2Configuration

PLM configuration

The PLM system is configured via a configuration file. “ConfigSettings” provides the path to the configuration file of the application libraries and components.

Configuration of applica­tion programs

The AcfConfigurationDocument format is used to configure components that can instantiate user programs. The Library, Component (name, type, library, isEnabled), and Settings (path) elements are evaluated by the PLM (see Section Configuration files).

5.5ACF (Application Component Framework)

The (ACF) Application Component Framework is the foundation for the PLCnext Technology platform architecture. The ACF is a framework that enables component-based platform development and the configurative composition of the firmware for the devices. It can con­figuratively distribute the firmware to one or more processes. The ACF enables the config­urative integration of user functions into the system. This way, you can extend PLCnext Technology devices with your own functions.

The ACF loads the various shared object files, and starts and manages the components contained therein in the desired sequence. In PLCnext Technology, components are instan­tiated like classes, i.e., several instances of one component type can exist. Instantiation is performed via configuration files for the ACF.

5.5.1Libraries

ACF libraries are loaded dynamically by the ACF via configuration. They serve as the con­figurative extension of the system by means of platform or user functions. The ACF libraries must be available as dynamic libraries (“shared object” or *.so) and contain one or more ACF components. This enables the user to construct the firmware configuratively and add functions.

ACF libraries must be implemented in accordance with a particular template. The specified template is necessary to enable the ACF to access the code dynamically. Implementation consists of the following elements:

Library class (derived from “LibraryBase”), implemented as a singleton. A library single­ton is an instance or function that exists exactly once per library, e.g., “ComponentFactory”.

At least one component class that implements the “IComponent” interface.

“ComponentFactory”

See “ILibrary” and “LibraryBase” and “IComponent” and “ComponentBase”.

Loading libraries

A library is added to the ACF configuration as shown in the following example:

ACF configuration – adding a library

 

<?xml version="1.0" encoding="utf-8"?>

<AcfConfigurationDocument

   xmlns="http://phoenixcontact.com/schema/acfconfig“

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

   xsi:schemaLocation=“http://www.phoenixcontact.com/schema/acfconfig“

   schemaVersion=“1.0“ >

 

   <Libraries>

      <Library name="MyLibraryACF.Library“

      binaryPath="$ARP_PROJECTS_DIR$/MyLibrary/libs/libMyLibraryACF.so“ />

   </Libraries>

 

</AcfConfigurationDocument>

The following table lists explanations of the individual attributes:

XML element

Description

<Libraries>

List of libraries
Here, libraries are listed that are to be loaded to create components.

<Library>

Definition of a library

name

The name of the library referenced in a component configuration.

binaryPath

The binary path of the library that is to be loaded.

5.5.2Components

Components are classes in the sense of object-oriented programming. They are a part of a library and provide a public interface to the library. They therefore facilitate access to librar­ies with coherent functionality, and can be instantiated once or several times.

ACF components enable the PLCnext Technology platform to be extended configuratively. The ACF components must be implemented in accordance with a particular template. The ACF components must register with the “ComponentFactory” of the library. Furthermore, they must implement the “IComponent” interface in order for the ACF to dynamically inte­grate them into the firmware (see Section 5.2 ““IComponent” and “ComponentBase”“).

ACF components are instantiated once or several times. They are assigned a system-wide unique instance name.

Adding a component

Components are added to the ACF configuration as shown in the following example code:

Adding a component to the ACF configuration (example):

 

<?xml version="1.0" encoding="utf-8"?>

<AcfConfigurationDocument

   xmlns="http://phoenixcontact.com/schema/acfconfig“

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

   xsi:schemaLocation=“http://www.phoenixcontact.com/schema/acfconfig“

   schemaVersion=“1.0“ >

      <!-- Components included into /opt/plcnext/projects/Default/Default.acf.config
         are managed by ACF while
         Components included into /opt/plcnext/projects/Default/Plc/Plm/Plm.config
         are managed by PLM
      -->

   <Components>

      <Component name="ACFDemo" type=“MyLibraryACF.MyComponentACF“ library="MyLibraryACF"
       settings=“/opt/plcnext/projects/MyLibrary/MySettings.xml“ />

   </Components>

 

</AcfConfigurationDocument>

 

The following table lists explanations of the individual attributes:

XML element

Description

<Components>

List of components

Here, components are listed that are to be created.

<Component>

Definition of a component

name

Name of the component

It must be unique throughout the entire system because all resources in the system are addressed via the component name.

type

Type name of the component

The name is composed of
<namespace>.<class>
A library can provide several component types. Typically, C++ type names are used here. The name is used in the code to register compo­nents with the “ComponentFactory”. This is the only dependency be­tween the system configuration and the source code.

library

The name of the library that contains the component and was defined under <Libraries>.

<Settings path>

Path to the component settings file

If the component requires a project configuration, the path to the compo­nent-specific project configuration must be specified in the settings file.

5.5.3Configuration

ACF configuration

As there is just one mechanism for the dynamic integration of components, the ACF config­uration contains both the firmware settings and the project configuration for the user com­ponents. The difference is in the storage location. The respective *.acf.config file is either in the directory for the system settings or for the project configuration.

Project configuration

Transferring the project configuration to the controller can be done by PLCnext Engineer, other development tools (e.g. Eclipse® or Simulink® extension) or manually. Adding user components to the system is done by configuration files of the distinct components as well as the ACF configuration files (*.acf.config). These add the user components to the system. An ARP_PROJECTS_DIR environment variable must be defined in the ACF settings for the project directory (/opt/plcnext/projects). PLCnext Engineer requires this for downloading the project. If an ACF system or service component has a project configuration, the config­uration path must be specified in the component settings (*.settings, XML element <ConfigSettings> with path attribute).

The project directory contains at least two subdirectories: “Default” and “PCWE”. The addi­tional directory structure is constructed in the same way as the Arp component hierarchy.

PLCnext_Projektverzeichnis.png

Figure 5-1 Project directory

PCWE directory
The “PCWE” folder is intended for the download via PLCnext Engineer. Files that are stored here will be overwritten during the next download via PLCnext Engineer (see Section 2.7.4 “Generating configuration files with PLCnext Engineer“).

Default directory:
The “Default” folder is intended for storing further configuration files and for manual, configurative extension of the platform components (see Section 2.7.5 “Manual config­uration“).

5.6Common classes

Common classes provide functions that may be helpful for programming. The PLCnext Technology-specific common classes are made available via the PLCnext Technology SDK. With the help of the SDK, it is possible to generate high-level-language programs in C++ for the PLCnext Technology framework. The SDK provides Arp firmware header files for this (“ARP SDK“). With the help of the ARP SDK, you can use common classes in your program. If you want to use a class, integrate it in your program via an #include command. Further information on the common classes and their applications is available directly in the code commentary.

The useful common classes mentioned in the following section are part of the Phoenix Contact SDK, for example. The classes are encapsulated in namespaces accord­ing to their subject areas.

5.6.1Threading

During threading, parts of a program are executed in parallel. The “Threading” namespace provides methods for separating a program into several strings for simultaneous execution, thus improving the performance of the overall system.

inset_34.jpg 

 

NOTE: Error due to changed priority

You can select a priority between 0 and 99. In order to not disturb the structure of the real-time threads, Phoenix Contact recommends priority 0. Otherwise, the stability of the firmware cannot be guaranteed. Programs in ESM tasks are intended for performing time-critical tasks.

“CpuAffinity”

“CpuAffinity” is a bit mask in which one bit is available per processor core. The least signif­icant bit represents processor core 1. If this bit is set, the scheduler may execute the thread on this processor core. Several bits can be set simultaneously. In this case, the scheduler decides on which processor core the thread is started, and whether it is executed during runtime by another processor core. If the value of the parameter is 0, the scheduler can ex­ecute the thread on every available processor core.

“Thread”

One instance of this class is used to manage one thread, respectively. You must specify the function or method that is to be executed in a thread during instantiation. If the Thread::Start method is called, the thread is executed.

The “Thread” class selects a low priority as standard. Phoenix Contact recommends retain­ing this priority in order not to endanger the priority structure of the various firmware and op­erating system tasks.

“WorkerThread”

In contrast to the “Thread” class, an instance of the “WorkerThread” class is executed cycli­cally, as soon as the WorkerThread::Start method is executed. You can define cyclic ex­ecution of the thread via the idletime parameter. The value is specified in ms.

“ThreadSettings”

The “ThreadSettings” class is an auxiliary class for passing on the following thread param­eters to a constructor of the “Thread” class:

Name

Priority

CPU affinity (which CPU has been released for the execution of the task)

Stack size (byte size)

“Mutex”

Using the “Mutex” class, you can prevent data of several threads being changed simultane­ously. The “Mutex” class instances can have two states:

Locked 

Unlocked 

Once the Mutex:Lock method has been performed, i.e., the call from the method returns, the data is protected against modification by other threads. This state is retained until the instance calls the Unlock() command, therefore rescinding the locked state. Therefore, a call is blocked until the thread which is in the Lock state is released again. To prevent a “Deadlock”, i.e., a state in which a locked thread cannot be unlocked again, you can use the “LockGuard” class. This class automates Lock and Unlock of a “Mutex” instance.

“RwLock”

“RwLock” provides a locking mechanism for increasing performance. This class is useful if several read accesses, but not many write accesses are necessary. The difference be­tween “Mutex” class instances and this class is that the instances of the “RwLock” class per­mit several simultaneous read accesses to the locked structure. Write access at the same time, however, is not allowed. Instances of this class are therefore suitable for all data that is often read but only rarely updated.

5.6.2“Ipc” (inter-process communication)

The “Ipc” namespace (inter-process communication) encapsulates classes which can be used to enable the communication between various processes on the same controller.

“Semaphore”

Using the “Semaphore” class, semaphores are implemented in order to synchronize pro­cesses or threads. In principle, semaphores are integer counters. If one of the various Wait methods is called, the internal counter is lowered by one. If the current value of the counter is zero when a Wait method is called, the call is blocked until a different thread on the same semaphore instance calls the Post method (the Post method increases the internal counter).

“MessageQueue”

Using the “MessageQueue” class, data can be exchanged between processes in the form of messages. The names of “MessageQueue” instances must begin with an “/” because otherwise, the call from the constructor will lead to an exception. The name of a queue cor­responds to the file path in Linux.

5.6.3“Chrono”

The “Chrono” namespace contains classes and functions with which the temporal se­quences within an application can be controlled and influenced. This includes high-resolu­tion measurement of time elapsed so far, and also triggering of actions after a predeter­mined period of time.

“Timer”

The “Timer” class is a high-resolution chronometer for interval-based execution of methods. Instances of this class are used to execute one or more methods periodically in a defined interval. The “Timer” class calculates the next point in time at which the method is to be called. You only have to implement the method or methods that are to be called via the Timer definition.

5.6.4“Io”

The “Io” namespace encapsulates all the functions necessary for working with files and fold­ers within the file system of the underlying operating system.

“FileStream”

The “FileStream” class is for stream-based editing (opening, writing, reading) of files. The various values in the class define, for example, whether a file is to be overwritten, an already existing file is to be opened, or a new file is to be created.

5.6.5“Net”

The “Net” namespace encapsulates all the classes and functions that enable network-based communication between processes on the same or separate controllers. For pro­cesses that run on the same controller, preferably use the functions from the “Ipc” name­space (see Section “Ipc” (inter-process communication)).

“Socket”

The “Socket” class is an interface for Ethernet-based communication. Use instances of this class to establish an Ethernet-to-peer connection. Currently, the UPD and TCP protocols are supported (IPv4 only).

5.6.6“Runtime”

The “Runtime” namespace encapsulates functions for manipulation of individual processes that are managed by the Arp firmware.

“SharedLibrary”

Using the “SharedLibrary” class, shared libraries (.so files) are dynamically published or re­loaded in applications during runtime. If a library is successfully reloaded with the SharedLibrary::Load method, the symbols contained (global variables, classes, meth­ods, functions, etc.) are then known in the current program. The memory area can be re­quested with GetFunctionAddress in order for the functions of the library to be used in the program currently running.

Process

The “Process” class is a high-level API for creating and managing new processes.

5.7“Template Loggable”

PLCnext Technology provides a log file on the controller file system in which information on the system behavior of the PLCnext Technology firmware, warnings, error messages, and debugging messages are logged. You will therefore find valuable information that can help you in finding the causes of problems.

“Template Loggable”

You can include the “Template Loggable<>” template class to automatically apply a tag to log messages. A tag can be used to determine from which component/program/... the mes­sage originates.

To use this class, you have to include (#include) the corresponding header file (.hpp).

“Loggable”: Arp/System/Commons/Logging.h

The following log levels are supported. For each log level, a suitable method can be called.

Info

Warning

Error

Fatal

Example call:

 

log.info („Info!“);

 

Static call

Alternatively, you can also perform logging without the “Loggable” class. Messages can be written without creating a special logger using the root logger with the static “Log” class. The “root” tag is assigned to the message. The source of the message is thus not visible in the log file.

 

Log::Error („Error!“);

 

It is also possible to pass on and format variables. Placeholders in the form of {x} are used for the variables, where x is the index of the variable.

 

(„Variable a={0} b={1}“, a, b);

 

Diagnostic log file

The “Output.log” diagnostic log file contains status information, warnings, error messages, and debugging messages. You will find the file in the /opt/plcnext/logs folder on the file sys­tem of your controller. The file system is accessed via the SFTP protocol. The SFTP client software is required for this (e.g., WinSCP) (see Section Directories of the firmware components in the file system ).

The diagnostic log file is configured in such a way that the messages are overwritten once the maximum file size is reached. When an error occurs, it is therefore recommended that the file is called and evaluated as soon as possible.

The diagnostic log file contains the following message types:

Error & Fatal: If messages of the “Error” or “Fatal” type are issued, the controller is stopped. The errors mainly arise during startup or during execution of a user program.

Warning: Warnings indicate potentially occurring errors.

Information: The core components issue messages of type “Information”. These pro­vide an overview of the system status.

 

Example: “Output.log” diagnostic log file:

A

B

C

D

E

18.05.07

08:24:15.830

MyLibrary.MyComponent

INFO  -

'MyComponent' invoked of object with in­stance

name 'MyLibrary.MyComponent-1'

18.05.07

08:24:15.831

Arp.Plc.Plm.Internal.PlmMan­ager

INFO  -

Component 'MyLibrary.MyComponent-1' from library 'MyLibrary' created.

18.05.07

08:24:15.831

MyLibrary.MyComponent

INFO  -

'Initialize' invoked of object with instance name 'MyLibrary.MyComponent-1'

18.05.07

08:24:15.832

MyLibrary.MyComponent

INFO  -

'SubscribeServices' invoked of object with instance name 'MyLibrary.MyComponent-1'

18.05.07

08:24:15.832

MyLibrary.MyComponent

INFO  -

Component 'AcfDemo' not found!

18.05.07

08:24:15.833

MyLibrary.MyComponent

INFO  -

'LoadSettings' invoked of object with in­stance name 'MyLibrary.MyComponent-1'

18.05.07

08:24:15.833

MyLibrary.MyComponent

INFO  -

'SetupSettings' invoked of object with in­stance name 'MyLibrary.MyComponent-1'

18.05.07

08:24:15.834

MyLibrary.MyComponent

INFO  -

'LoadConfig' invoked of object with instance name 'MyLibrary.MyComponent-1'

18.05.07

08:24:15.834

MyLibrary.MyComponent

INFO  -

'SetupConfig' invoked of object with in­stance name 'MyLibrary.MyComponent-1'

18.05.07

08:24:15.988

MyLibrary.MyComponent.MyPro­gram

INFO  -

Added Port 'zaehler (of Data Type 8)' of in­stance MyLibrary.MyComponent-1/MyProgram1

18.05.07

08:24:15.989

MyLibrary.MyComponent.MyPro­gram

INFO  -

Added Port 'zaehler (of Data Type 8)' of in­stance MyLibrary.MyComponent-1/P1

18.05.07

08:24:16.121

Arp.Io.Axioline.AxiolineCom­ponent

INFO  -

Axioline: Load configuration.

18.05.07

08:24:16.127

Arp.Io.Axioline.AxiolineCom­ponent

INFO  -

AxiolineComponent::LoadPlc() Path=/opt/plc­next/projects/PCWE/Io/Arp.Io.AxlC/links.xml

A

Date of the message in the format DD.MM.YY

B

Time of the message in the format hh:mm:ss.ms

C

Component that triggers the message

D

Message type (log level)

E

Message, e.g., info text, error message, debugging message

5.8Using RSC services

You have the option of using the already registered RSC services of the SDK (Software De­velopment Kit) via the “ServiceManager”. It acts as the RSC API and is used to request ser­vices.

Use the #include command in the header file to include the “ServiceManager” class and the desired service interface (e.g., “IDeviceStatusService“).

Example header file: #include ServiceManager.hpp:

 

#pragma once

#include "Arp/System/Core/Arp.h"

#include "Arp/System/Acf/ComponentBase.hpp"

#include "Arp/System/Acf/IApplication.hpp"

#include "Arp/System/Acf/IControllerComponent.hpp"

#include "Arp/System/Commons/Logging.h"

#include “Arp/System/Rsc/ServiceManager.hpp”

#include "Arp/System/Commons/Threading/WorkerThread.hpp"

#include "Arp/Device/Interface/Services/IDeviceStatusService.hpp"

 

Initialize a pointer to the object matching the desired service by calling the GetService() method of “ServiceManager”.

Pass on the name of the corresponding interface as the template argument.

You can place this call in the SubscribeServices() method of your component, for exam­ple:

Header file (.hpp):

 

class ExampleComponent : public .....

{

   ...

private: //services

   IDataAccessService::Ptr dataAccessService;

   ...

}

 

Source file (.cpp):

 

#include....

   using namespace Arp::System::Rsc;

   using namespace Arp ...

void ExampleComponent::SubscribeServices()

{

 

   // Get service handle

   this->deviceStatusService = ServiceManager::GetService<IDeviceStatusService>();

}

inset_37.jpg 

 

Please note that execution of RSC services can take some time (in particular Axioline and PROFINET services). For this reason, avoid direct calls from ESM tasks.

RSC services are available for the following areas. You will find more detailed descriptions in the sections specified.

Axioline services: Read and write access to data and information of Axioline devices (see Section RSC Axioline services)

PROFINET services: Read and write access to data and information of PROFINET de­vices (see Section RSC PROFINET services)

Device interface services: Access to information and properties of the operating sys­tem and controller hardware (see Section RSC device interface services)

GDS services: Read and write access to the GDS data (see Section RSC GDS services)

5.8.1RSC Axioline services

inset_41.jpg 

 

Please note that execution of RSC services can take some time. For this reason, avoid direct calls from ESM tasks.

inset_42.jpg 

 

The Axioline RSC services are only available to PLCnext Technology controllers that support an Axioline local bus.

The Axioline component can be extended via interfaces for Axioline services. You can use one interface for acyclic communication (PdiRead, PdiWrite). This interface is available via the RSC protocol. Parameterization data, diagnostics information, and status informa­tion (PDI = Parameters, Diagnostics and Information) of an Axioline device can be read or written with the RSC service. Acyclic communication is suitable for the exchange of data that does not recur cyclically.

5.8.1.1“IAcyclicCommunicationService”

The “IAcyclicCommunicationService” RSC service in the “Arp/Io/Axioline/Services” name­space for acyclic communication makes the following methods available:

PdiRead:
Enables parameters, diagnostics and information of an Axioline device to be read

PdiWrite 
Enables parameters, diagnostics and information of an Axioline device to be written

 

PdiResult PdiRead(const PdiParam& pdiParam, std::vector<uint8>& data)

PdiResult PdiWrite(const PdiParam& pdiParam, const std::vector<uint8>& data)

 

Parameters are necessary for executing PdiRead and PdiWrite. The PdiParam structure is used for transmitting the input parameters. The structure has the following elements:

Parameter

Description

uint16 Slot

Device number

uint8 Subslot

Subdevice number

uint16 Index

Object index

uint8 Subindex

Object subindex

The return values are written to the PdiResult structure. The structure has the following el­ements:

Parameter

Description

uint16 ErrorCode

Error code

uint16 AddInfo

Error code, further information

Data that is read (PdiRead) and data that is to be written (PdiWrite) is transferred in a type unit8 vector (data). A description of the data (maximum size, type, etc.) of the object de­scription is available in the data sheet of the respective Axioline module.

You need the following headers to use the service. Include them via the #include com­mand:

Arp/Io/Axioline/Services/IAcyclicCommunicationService.hpp

Arp/Io/Axioline/Services/PdiParam.hpp

Arp/Io/Axioline/Services/PdiResult.hpp

System-specific information on the Axioline F system is available in the PLCnext Engineer online help system, as well as in the user manuals “Axioline F: System and installation” (UM EN AXL F SYS INST) and “Axioline F: Diagnostic registers and error messages” (UM EN AXL F SYS DIAG).
The user manuals can be downloaded at phoenixcontact.net/qr/2404267/manual. Further information is also available in the data sheets of the respective Axioline modules.

5.8.2RSC PROFINET services

inset_38.jpg 

 

Please note that execution of RSC services can take some time. For this reason, avoid direct calls from ESM tasks.

The PROFINET component can be extended via interfaces for PROFINET services. You can use one interface for acyclic communication (RecordRead, RecordWrite). The inter­face is available via the RSC protocol. The parameter data, diagnostics information, and status information (PDI = Parameters, Diagnostics and Information) of a PROFINET device can be read or written with the RSC service. Acyclic communication is suitable for the ex­change of data that does not recur cyclically.

5.8.2.1“IAcyclicCommunicationService”

The “IAcyclicCommunicationService” RSC service in the “Arp/Io/ProfinetStack/Control­ler/Service” namespace for acyclic communication makes the following methods available:

RecordRead:
Enables parameters, diagnostics and information of a PROFINET device to be read

RecordWrite 
Enables parameters, diagnostics and information of a PROFINET device to be written

 

RecordResult RecordRead(const RecordParam& recordParam, std::vector<uint8>& data)

RecordResult RecordWrite(const RecordParam& recordParam, const std::vector<uint8>& data)

Parameters are necessary for executing RecordRead and RecordWrite. The “Record­Param” structure is used for transmitting the input parameters. The structure provides two ways of addressing a module:

Version 1: Addressing via the ID

Version 2: Addressing via an address that is made up of DeviceName, Slot, and Sub­slot

If one version is selected for addressing, the parameters of the other version must be 0 or empty.

The “RecordParam” structure has the following elements:

Parameter

Description

uint16 Id

Node ID of the submodule
The ID is assigned automatically. It can be viewed in PLCnext Engineer (version 1). For this, open the submodule list of the PROFINET submodule node.

PCWE_Submodulliste.png

To determine the ID, you can also use the AddressToID method of the IAddressConversionService.hpp service.

RscString<512>
DeviceName

Device name
Name of the device that is to be addressed (version 2)

uint16 Slot

Device number (version 2)

uint8 Subslot

Subdevice number (version 2)

uint16 Index

Object index

uint8 Length

Maximum data amount
Specifies the maximum amount of data to be written in bytes (“RecordRead”) or the amount of data that is to be written to an object (“RecordWrite”).

The return values are written to the “RecordResult” structure. The structure has the param­eters below. Further information on the parameters is available in the PROFINET specifica­tion (version 2.3).

boolean ServiceDone

uint8 ErrorCode

uint8 ErrorDecode

uint8 ErrorCode1

uint8 ErrorCode2

unit16 AddData1

unit16 AddData2

Table 5-3 ErrorCode1

Error code

Description

0

No errors

0xF0

Internal error
The error can be evaluated more precisely via ErrorCode2.

Further error codes

If a value other than 0xF0 or 0 is displayed in the ErrorCode1 element, the error is de­scribed in the ErrorCode, ErrorDecode, ErrorCode1, ErrorCode2, AddData1, and AddData2 fields by the PROFINET specification.

Table 5-4 ErrorCode2

Error code

Description

0x03

“InvalidAddress”
ID or DeviceName/Slot/Subslot is invalid.

0x15

“RecordReadFailed”
RecordRead failed. This error can occur, for example, if several devices simultane­ously attempt to execute “RecordRead” on one device.

0x16

„RecordWriteFailed”
RecordWrite failed. This error can occur, for example, if several devices simultane­ously attempt to execute “RecordWrite” on one device.

0x17

“Timeout”
No confirmation was received.

Data that is read (“RecordRead”) or data that is to be written (“RecordWrite”) is transferred in a type unit8 vector (“data”). A description of the data (maximum size, type, etc.) of the ob­ject description can be found in the PROFINET specification (version 2.3).

You need the following headers to use the service. If necessary, include them via the #include command:

Arp/Io/ProfinetStack/Controller/Services/IAcyclicCommunicationService.hpp

Arp/Io/ProfinetStack/Controller/Services/
AcyclicCommunicationServiceProxyFactory.hpp

Arp/Io/ProfinetStack/Controller/Services/RecordParam.hpp

Arp/Io/ProfinetStack/Controller/Services/RecordResult.hpp

5.8.3RSC device interface services

inset_36.jpg 

 

Please note that execution of RSC services can take some time. For this reason, avoid direct calls from ESM tasks.

The device interface services provide a range of functions for accessing properties of the operating system and the controller hardware. You can call the information with the follow­ing interfaces and defined parameters. The following headers are required to use the ser­vice. Integrate these via #include, if necessary:

Arp/Device/Interface/Services/IDeviceInfoService.hpp

Arp/Device/Interface/Services/IDeviceStatusService.hpp

5.8.3.1“IDeviceInfoService”

The “IDeviceInfoService” RSC interface enables read access to device information. The status value of a parameter is read with the GetItem() method. The status values of several parameters are read with the GetItems() method.

 

RscVariant<512> GetItem(const RscString<512>& identifier)

void GetItems(GetItemsIdentifiersDelegate identifiersDelegate, GetItemsResultDelegate resultDele­gate)

 

The following parameters are available for calling information:

Table 5-5 “IDeviceInfoService” - parameters

Parameter

Data type

Description

General.DeviceClass

UInt32

The “DeviceClass” parameter specifies the device class. Cur­rently, only “ProgrammableLogicController” is supported.
0: Undefined
1: ProgrammableLogicController
2. BusCoupler
3: Switch

General.VendorName

String

The “VendorName” parameter indicates the name of the manufac­turer.

General.ArticleName

String

The “ArticleName” parameter indicates the device name.

General.ArticleNumber

String

The “ArticleNumber” parameter indicates the order number of the device.

General.SerialNumber

String

The “SerialNumber” parameter indicates the serial number of the device.

General.Firmware.Version

String

The “FirmwareVersion” parameter indicates the firmware version of the device. Here, 5-level notation (Major, Minor, Patch, Build, Status) is used.

General.Firmware.VersionMajor

Byte

“FirmwareVersionMajor”
Info: The firmware version year is indicated without the first two digits. E.g., “2019” is indicated as “19”.

General.Firmware.VersionMinor

Byte

“FirmwareVersionMinor”

General.Firmware.VersionPatch

Byte

“FirmwareVersionPatch”

General.Firmware.VersionBuild

UInt32

“FirmwareVersionBuild”

General.Firmware.VersionStatus

String

“FirmwareVersionStatus”

General.Firmware.BuildDate

String

“FirmwareBuildDate”
ISO 8601 format <YYYY>-<MM>-<DD>

General.Firmware.BuildTime

String

“FirmwareBuildTime”
ISO 8601 format <hh>:<mm>:<ss>

General.Hardware.Version

String

The “HardwareVersion” parameter indicates the hardware version of the device.

General.Fpga.Version

String

The “FPGAVersion” parameter indicates the FPGA version of the device. Here, 3-level notation (Major, Minor, Patch) is used.

General.Fpga.VersionMajor

Byte

“FPGAVersionMajor”

General.Fpga.VersionMinor

Byte

“FPGAVersionMinor”

General.Fpga.VersionPatch

Byte

“FPGAVersionPatch”

General.UniqueHardwareId

String

Sha256 Hash (32byte) hexadecimal coded as string

General.SPNS.Fpga.Version

String

FPGA version of the SPNS (only for devices with integrated safety controller)

General.SPNS.Fpga.VersionMajor

Byte

SPNS FPGA version Major

General.SPNS.Fpga.VersionMinor

Byte

SPNS FPGA version Minor

General.SPNS.Fpga.BuildVersion

Unsigned32

SPNS FPGA build version

General.SPNS.Firmware.Version

String

SPNS firmware version

General.SPNS.Firmware.
VersionMajor

Byte

SPNS firmware version, Major

General.SPNS.Firmware.
VersionMinor

Byte

SPNS firmware version, Minor

General.SPNS.Firmware.BuildVersion

Unsigned32

SPNS firmware build version

Interfaces.Ethernet.Count

Byte

The “NoOfNetworInterfaces” parameter indicates the number of network interfaces.

Interfaces.Ethernet.{adapterIndex}.
{port}.Mac

String

The “Mac” parameter indicates the MAC address of the selected network interface.

AA:BB:CC:DD:EE:FF
adapterIndex= 1, 2, ... port = 0 for the interface, MAC port = 1, 2, ... for the MAC port

5.8.3.2“IDeviceStatusService”

This RSC interface enables read access to status information. The status value of a param­eter is read using the GetItem() method. The status values of several parameters are read using the GetItems() method. Use the deviceStatusService.GetItem(“Parameters”) method to call status information.

 

RscVariant<512> GetItem(const RscString<512>& identifier)

void GetItems(GetItemsIdentifiersDelegate identifiersDelegate, GetItemsResultDelegate resultDele­gate)

 

The following parameters are available for calling information:

Parameter

Data type

Description

Status.DeviceHealth

Byte

The “DeviceHealth” parameter indicates the operating status of the device.
0: OK
1: WARNING
2: ERROR

Status.Cpu.Load.Percent

Byte

The “CPULoad” parameter indicates the complete processor load of the device as a percentage.
0% ... 100%

Status.Cpu{0}.Load.Percent

Byte

The “CPULoad{Core}” parameter indicates the processor load of the selected processor core of the device as a percentage.
0% ... 100%
0x64 = 100%
Core = 0, 1, 2, etc.

Status.Memory.Usage.Percent

Byte

The “MemoryUsage” parameter indicates the complete memory usage of the device as a percentage.
0% ... 100%
0x64 = 100%

Status.ProgramMemoryIEC.
Usage.Percent

Byte

The “ProgramMemoryUsage” parameter indicates the program memory usage of the IEC runtime of the device as a percentage.
0% ... 100%
0x64 = 100%

Status.DataMemoryIEC.
Usage.Percent

Byte

The “DataMemoryUsage” parameter indicates the data memory usage of the IEC runtime of the device as a percentage.
0% ... 100%
0x64 = 100%

Status.RetainMemory.Usage.Percent

Byte

The “RetainMemoryUsage” parameter indicates the complete re­tain memory usage of the device as a percentage.
0% ... 100%
0x64 = 100%

Status.RetainMemoryIEC.
Usage.Percent

Byte

The “RetainMemoryUsage” parameter indicates the complete re­tain memory usage of the device as a percentage.
0% ... 100%
0x64 = 100%

Status.Board.Temperature.Centigrade

Int32

The “BoardTemperature” parameter indicates the temperature of the interior of the device in °C.

Status.Board.Humidity

Byte

The “BoardHumidity” parameter indicates the relative humidity in the device.
0% ... 100%

Status.Cpu.Temperature.Centigrade

Int32

The “CPUTemperature” parameter indicates the temperature of the processor in °C (only for RFC 4072S).

Status.KeySwitch.Position

Byte

The “KeySwitch” parameter indicates the position of the run/stop switch (only for RFC 4072S controller).
0: Switch in stop position
1: Switch in run position

Status.RamDisk.{RamDiskIndex}.
Usage.Percent

Byte

The parameter indicates the memory usage of the RAM disc(s) as a percentage (only for RFC 4072S controller).
0% ... 100%
RamDiskIndex = 1, 2,.... Number of the RAM disc (currently, only one RAM disc is supported, so that the index is always 1)

Status.RamDisk.{RamDiskIndex}.
Usage

UInt32

The parameter indicates the absolute memory usage of the RAM disc(s) (only for RFC 4072S controller).
RamDiskIndex = 1, 2,....Number of the RAM disc (currently, only one RAM disc is supported, so that the index is always 1)

You can also call status information on the LED states via the “IDeviceStatusService” inter­face.

The colors of the LEDs are represented as follows, normally in the high word (HW) of the return value:

 

public enum LedColor : ushort

{

   Green = 1,

   Yellow = 2,

   Red = 4

};

 

The states of the LEDs is represented as follows, normally in the low word (LW) of the return value:

 

public enum LedStates : ushort

{

   Off = 0,

   On = 1,

   Flashing_0_5_Hz = 2,

   Flashing_2_Hz = 3,

   Alternating_0_5_Hz = 4,

   Alternating_2_Hz = 5

}

Table 5-6 LEDs of the IEC runtime system

Parameter

Data type

Description

Status.Leds.Runtime.Run

UInt32

Runtime RUN LED (HW = color, LW = status)

Status.Leds.Runtime.Fail

UInt32

Runtime FAIL LED (HW = color, LW = status)

Status.Leds.Runtime.Debug

UInt32

Runtime DEBUG LED (HW = color, LW = status)

Table 5-7 Axioline LEDs

Parameter

Data type

Description

Status.Leds.Axio.D

UInt32

AXIO master D LED (HW = color, LW = status)

Status.Leds.Axio.E

UInt32

AXIO master E LED (HW = color, LW = status)

Table 5-8 PROFINET LEDs

Parameter

Data type

Description

Status.Leds.Pnio.Bf_C

UInt32

Pnio controller BF LED (HW = color, LW = status)

Status.Leds.Pnio.Bf_D

UInt32

Pnio device BF LED (HW = color, LW = status)

Status.Leds.Pnio.Sf

UInt32

Pnio controller SF LED (HW = color, LW = status)

You can also call status information on the network states via the “IDeviceStatusService” in­terface:

Table 5-9 Network interface

Parameter

Data type

Description

Status.Interfaces.Ethernet.
{adapterIndex}.{port}Baudrate

Byte

The “Interface Baudrate” parameter indicates the current speed of the interface.
1: 10 Mbps
2: 100 Mbps
3: 1000 Mbps
adapterIndex = 1, 2, ...
port = 1, 2, ...

Status.Interfaces.Ethernet.
{adapterIndex}.{port}.Duplex

Byte

The “Interface Duplex Mode” parameter indicates the current du­plex mode of the interface.
1: Half duplex
2: Full duplex
adapterIndex = 1, 2, ...
port = 1, 2, ...

Status.Interfaces.Ethernet.
{adapterIndex}.{port}.Link

Byte

The “Interface Link Status” parameter indicates the link status of the interface.
0: linkDown
1: linkUp
adapterIndex = 1, 2, ...
port = 1, 2, ...

5.8.4RSC GDS services

inset_39.jpg 

 

Please note that execution of RSC services can take some time. For this reason, avoid direct calls from ESM tasks.

5.8.4.1“IDataAccessService”

During runtime, the internal user components have read and write access to the GDS data. The service enables asynchronous reading and writing of one or more ports or even internal variables. For this process, you need the name of the port(s) that is/are to be read. The data read can be written to a database, for example.

You need the following header to use the “IDataAccessService” service. If necessary, in­clude it via the #include command:

Arp/Plc/Gds/Services/IDataAccessService.hpp

Service functions for direct data access:

Read(): This function is used to read the values of the variable addresses passed on.

ReadSingle(): This function is used to read the value of the variable address passed on. Only simple variables are supported (no arrays or structures).

Write(): This function is used to write the values passed on to the variables of the vari­able address passed on.

WriteSingle(): This function is used to write the value passed on to the variables of the variable address passed on. Only simple variables are supported (no arrays or structures).

ReadItem       ReadSingle(const RscString<512>& portName)

         Read(ReadPortNamesDelegate portNamesDelegate, ReadResultDelegate resultDelegate)

DataAccessError         WriteSingle(const WriteItem& data)

            Write(WriteDataDelegate dataDelegate, WriteResultDelegate resultDelegate)

Each port has a unique name within the GDS that is made up as described in Section GDS configuration using configuration files. You need the complete name (URI) of a port in order to address it. The following port addresses are valid, for example:

ComponentName-1/ProgramName-1.Variable_Name

ComponentName-1/Global_Variable_Name

ComponentName-1/ProgramName-1.Array_Variable_Name

ComponentName-1/ProgramName-1.Array_Variable_Name[index]

ComponentName-1/ProgramName-1.Array_Variable_Name[startIndex:endIndex]

ComponentName-1/ProgramName-1.Struct_Variable_Name.Element1.Leaf

ComponentName-1/ProgramName-1.Struct_Variable_Name.Element1.LeafArray

ComponentName-1/ProgramName-1.Struct_Variable_Name.Element1.
LeafArray[index]

The following variable types can be read and written:

Primitive

DateTime

StaticString

IecString

Enum

Struct

Pointer

Array

5.8.4.2“ISubscriptionService”

The “ISubscriptionService” offers an alternative to the read functions of “IDataAccessService”. The variables of which the values are to be read are only registered once and can then be read continuously. The data can be read more rapidly, and, due to the elimination of variable addressing, also recorded consistently with the task cycle. All vari­ables in the same ESM task are recorded in the same cycle (with the exception of the “DirectRead” subscription type). In addition, “ISubscriptionService” provides time stamps that can be used to assign a clear recording time to each value.

To use the “ISubscriptionService” class, you require the following header file which was de­clared in the “Arp::Plc::Gds::Services” namespace:

 

#include „Arp/Plc/Gds/Services/ISubscriptionService.hpp“

If necessary, include it via the #include command.

Below you will find a description of how to create, start, and read a subscription.

Creating a subscription

First, you have to create a subscription.

To create a subscription, use the following function of the “ISubscriptionService”:

 

uint32       CreateSubscription(SubscriptionKind kind)

 

With this function, the type of the desired subscription is passed on. Select one of the fol­lowing four types:

Table 5-10 Subscription types

Type

Description

“DirectRead”

The “DirectRead” subscription records the values directly when the ReadValues() function is called within the context of the thread to be called. The “HighPerformance”, “RealTime”, and “Recording” subscription types collect the values within the context of the assigned ESM task. The data is read directly from the respective variable. A copy process is only implemented if data is also queried. The read data can originate from different task cycles.

This subscription can deliver the best performance with the lowest impact on real time.

Possible use: Asynchronous data acquisition of non-time-critical data.

“HighPerformance”

The “HighPerformance” subscription uses a double buffer which contains the last written data of a variable. The buffer enables almost simultaneous writing and reading of data.

The “HighPerformance” type is consistent with the task cycle. It uses the least memory and shows the least impact on real time (compared with the “RealTime” and “Recording Subscription” types).

Possible use: Standard type for acquiring data.

“RealTime”

The “RealTime” subscription uses a quad buffer which contains the last written data of a vari­able. The buffer minimizes access times during reading and writing.

The “RealTime” type is consistent with the task cycle. It guarantees fast data access but re­quires four times the amount of memory.

Possible use: The subscription is suitable for variables running in very fast tasks and if fast ac­cess to read files is required.

“Recording”

The “Recording” subscription uses a ring buffer that can store several data items of a variable. This type is consistent with the task cycle and has a low impact on real time. However, depend­ing on the ring size, it requires a lot of memory. By default, the ring size is set to 10. It can be configured if the CreateRecordingSubscription() function is used instead of CreateSubscription().

Possible use: The subscription is suitable for variables running in tasks that are faster than the task of the user but the user still needs all values.

Adding variables

Add the desired variables to the subscription by calling one of the following functions:

 

DataAccessError AddVariable(uint32 subscriptionId, const RscString<512>& variableName)

 

or

 

void AddVariables(uint32 subscriptionId, AddVariablesVariableNamesDelegate variableNamesDelegate, AddVariablesResultDelegate resultDelegate);

 

Both functions can be called repeatedly for the same subscription. As with “IDataAccessService”, the variables are addressed via their complete name. Some exam­ples are shown in the following table:

Table 5-11 Examples of variable addressing

Variable addressing

Description

ComponentName-1/ProgramName-1.Variable_Name

Program variable

ComponentName-1/Global_Variable_Name

Component variable (global variable)

ComponentName-1/ProgramName-1:Array_Variable_Name[index]

Array element

ComponentName-1/ProgramName-1:Struct_Variable_Name.Element1.Leaf

Structure element (leaf)

ComponentName-1/ProgramName-1:Struct_Variable_Name.Element1.LeafArray[index]

Array element from a structure

ComponentName-1/ProgramName-1:Array_Variable_Name[startIndex:endIndex]

An extract of array elements

Once a variable was added successfully to the subscription, the
DataAccessError::None value is returned. In case of an error, the following return values might be returned:

Table 5-12 Return values in the event of an error

Name

Description

None

No error

NotExists

The variable does not exist in the system.

NotAuthorized

The user does not have sufficient authorization.

TypeMismatch

During writing, the value type is not suitable for the respective port.

PortNameSyntaxError

The port address is syntactically incorrect.

PortNameSemanticError

The port address is semantically incorrect.

IndexOutOfRange

The address contains an array index that is outside the array.

NotImplemented

The variable or service function has not yet been implemented.

NotSupported

The variable is not supported.

CurrentlyUnavailable

The service is currently unavailable.

UnvalidSubscription

The specified subscription was not found or is invalid.

The following types are currently supported:

Primitive

DateTime

String (currently, only StaticString and IecString are supported)

StaticString

IecString

Enum

Struct

Pointer

Array

Subscribe/unsubscribe

Once you created a subscription and configured it with variables, you can activate it with Subscribe.

To activate the subscription, call the following service function:

 

DataAccessError Subscribe(uint32 subscriptionId, uint64 sampleRate);

 

With call of the function, copying of the variable starts (exception: for the “DirectRead” type, no data is automatically recorded).

Use the sampleRate parameter to indicate in which time grid the values are to be recorded. The sampleRate can only be a multiple of the interval time of a cyclic ESM task. If a sampleRate that does not correspond to this interval time is passed on, the “SubscriptionValue” rounds the value to the next faster value.

Example: Variables from task A and task B are to be recorded:

Interval time for task A: 10 ms

Interval time for task B: 8 ms

If you specify 50 ms for sampleRate, the following is actually recorded:

Variables from task A at 50 ms (every fifth cycle)

Variables from task B at 48 ms (every sixth cycle)

If you specify value 0 for sampleRate, all the data is recorded in the interval of the respective task.

Variables from task A at 10 ms

Variables from task B at 8 ms

 

When a subscription was started, you can pause it via Unsubscribe.

For this, call the following service function:

 

DataAccessError Unsubscribe(uint subscriptionId);

 

If a subscription pauses, no new data is recorded. Existing data is available in the subscrip­tion.

To restart recording, call Subscribe again.

GetVariableInfos

This service function shows which variables are currently recorded. Therefore, the function only returns information once Subscribe is called.

The function only returns information about variable sorting. This information is decisive for reading the data. Each subscription internally sorts the variables, e.g., by assignment to the ESM task. The data of added variables is therefore not read in the order in which is was added. The “Read” functions only provide the raw values of the variables but do not give in­formation on which variable the value is assigned to. At this point, service function informa­tion is the only option to assign the values to the respective variables. Variable information is returned in the same order as data for the “Read” functions. Therefore, variable informa­tion has to be read before the “Read” functions of a subscription are called for the first time.

A matching “Info” function is available for each “Read” function.

To query all currently recorded variables, call the following function:

 

DataAccessError GetVariableInfos(uint32 subscriptionId, GetVariableInfosVariableInfoDelegate variableInfoDelegate);

 

To query the data, call the associated “Read” function:

 

DataAccessError ReadValues(uint32 subscriptionId, ReadTimeStampedValuesValuesDelegate valuesDelegate)

 

If, in addition to the variable values, you also require the time stamps of value acquisi­tion, call the following function:

 

DataAccessError GetTimeStampedVariableInfos(uint32 subscriptionId, GetTimeStampedVariableInfosVariableInfoDelegate variableInfoDelegate)

 

Information on time stamps is returned in addition to information on the variables. A variable with the name “timestamp” and of the “Arp.Plc.DataType.Int64” data type is always returned as the first element of an ESM task. This is followed by all information of the variable that is associated with the ESM task and can be assigned to the time stamp. If there are variables of several ESM tasks in the subscription, an additional time stamp is returned for each task, which is followed by the associated information. Here, information is also returned in the same order as the data that is returned with the following “Read” function:

 

DataAccessError GetRecordInfos(uint32 subscriptionId, GetRecordInfosRecordInfosDelegate recordInfosDelegate);

 

ReadValues

Once you have started the recording of a subscription, you can query the acquired data. Dif­ferent “Read” functions are available for this. The “Read” functions only return the variable values. The values are not assigned to the respective variable. For assigning the values to the respective variables, you have to call the corresponding “Info” function once (see Sec­tion GetVariableInfos).

The following “Read” service functions are available:

 

DataAccessError ReadValues(uint32 subscriptionId, ReadTimeStampedValuesValuesDelegate valuesDelegate)

 

This function returns all the values in a static order. The GetVariableInfos() function is used for assigning the variables.

Example:

Added variables from task A: a1, a2

Added variables from task B: b1

ReadValues:

Object[]

a2
a1
b1

The following function returns all the values in a static order, including the associated time stamps:

 

DataAccessError ReadTimeStampedValues(uint32 subscriptionId, ReadTimeStampedValuesValuesDelegate valuesDelegate);

 

The time stamps are always located before the associated variable values. The number of time stamps always corresponds to the number of ESM tasks the variables originate from. By means of the “DateTime” class, which is defined in the Arp namespace and in the Arp/System/Core/DateTime.hpp header file, the value of the “timestamp” variable can be converted into a time stamp. The GetVariableInfos() function is used for assigning the variables.

Example:

Added variables from task A: a1, a2

Added variables from task B: b1

ReadValues:

Object[]

Timestamp task A
a2
a1
Timestamp task B
b1

The following function returns all the values packed in records:

 

DataAccessError ReadRecords(uint32 subscriptionId, uint16 count, ReadRecordsRecordsDelegate recordsDelegate);

 

A record, also called data record, only contains the variable data from an ESM task and the associated time stamp. The time stamp is always located before the associated variable values. The variable order is always static and does not change during operation. An ESM task record is created for each ESM task. It contains all the corresponding data records of the respective ESM task. Depending on the subscription configuration, an ESM task record can contain several data records.

E.g.: A subscription of the “Recording” type with a task interval of 100 ms and a capacity of 10 returns 10 data records after one second. The time stamps are 100 ms apart. However, a subscription of the “HighPerformance”, “RealTime”, or “DirectRead” type always returns one data record only. By means of the “Read” function, you can read all the subscription data of the “Recording” type at once.

In addition to the static order of variables in the data records, the order of the ESM task re­cords is static, too. By means of the GetTimeStampedVariableInfos() function, each value can be assigned a variable. The variable information describes exactly the first ESM task record and all the data records contained therein, from the first time stamp to the final variable information associated with this time stamp.

By means of the “DateTime” class, which is defined in the Arp namespace and in the Arp/System/Core/DateTime.hpp header file, the value of the “timestamp” variable in the re­spective data records can be converted into a time stamp.

Example:

Added variables from task A: a1, a2

Added variables from task B: b1

 

Task A recorded 2 records.

Task B recorded 1 record.

 

ReadRecords:

Object[] (ESM task records)

Objects[] (ESM task record A)

Objects[](data record cycle 1)

Timestamp
a2
a1

Objects[](data record cycle 2)

Timestamp
a2
a1

Objects[] (ESM task record B)

Objects[](data record cycle 1)

Timestamp
b1

Changing a subcription

You can modify two things of an existing subscription:

The sampling interval the subscription used to record data.

The variables that are to be recorded.

The subscription type can only be changed if you delete the subscription and create it again.

To change the sample rate, proceed as follows:

Stop the subscription by calling Unsubscribe.

Execute the subscription by calling Subscribe and entering the new sample rate as the parameter.

Several functions are available for changing the variables to be recorded:

To delete a variable from a subscription, call the following function:

 

DataAccessError RemoveVariable(uint32 subscriptionId, const RscString<512>& variableName),

 

To add a variable to a subscription, call the following function:

 

DataAccessError AddVariable(uint32 subscriptionId, const RscString<512>& variableName)

 

To add several variables to a subscription, call the following function:

 

void       AddVariables(uint32 subscriptionId, AddVariablesVariableNamesDelegate variableNamesDelegate, AddVariablesResultDelegate resultDelegate);

 

The changes can be made during operation and also to a subscription that is currently re­cording. To apply the changes, execute Resubscribe.

To execute Resubscribe, call the following service function:

 

DataAccessError Resubscribe(uint32 subscriptionId, uint64 sampleRate);

 

Once Resubscribe was called, the changes are applied to the subscription. As the internal memories are rebuilt, data can be lost.

Deleting a subscription

If a subscription is no longer required, you have to delete it. If you do not delete the subscrip­tion, is will be retained until the next restart of the PLC application (warm or cold restart).

To delete a subscription, call the following function:

 

DataAccessError DeleteSubscription(uint32 subscriptionId)

 

The internally reserved memory is enabled and the subscription ID becomes invalid.

5.9Notifications

The notification manager is used for registering, sending and receiving notifications be­tween components of a controller. The header files required for using the notification man­ager are provided via the PLCnext Technology SDK (see Section 6.1, “PLCnCLI (PLCnext Command Line Interface)”). The SDK contains classes for the Notification manager, classes as a basis for user-defined user data (payload), as well as the payload classes of the PLCnext Technology firmware. If you want to use a class, integrate it into your program via an #include command, (e.g., #include <Arp/System/Nm/NotificationManager.hpp>). Further information on the classes and their applications is available directly in the code commentary.

Defining user data

To enable correct interpretation of the user data of a notification, the structure has to be de­fined. Use template class
“Arp/System/Nm/SpecializedPayload.hpp” for this. User data with up to 50 fields can be used as a character string.

Example code with two fields:

 

// Example payload with two payload fields

 

#include "Arp/System/Nm/SpecializedPayload.hpp"

 

// Use the class name as template parameter for SpecializedPayload

class UserLoggedInPayload

   : public Arp::System::Nm::SpecializedPayload<UserLoggedInPayload>

{

public:

 

   // Inherit base class constructors

   using SpecializedPayload::SpecializedPayload;

 

   // Define a convenient constructor to create the payload

   // This constructor will be called by a variadic template and perfect forwarding when

   // a notification is sent. Therefore this constructor defines the signature of the

   // function to send a notification.

   // Pass a format string for a user visible representation of the payload to the base

   // class' constructor. This format string will be used by the NotificationLogger to

   // serialize the payload.

   UserLoggedInPayload(const String& username, const String& permissions)

   : SpecializedPayload("User logged in: {0} (permissions={1})")

   {

      // Store the values of the parameters in the underlying payload data structure

      this->SetFieldvalue(this->fieldIndexUsername, username);

      this->SetFieldvalue(this->fieldIndexPermissions, permissions);

   }

   // Add getters for a type-safe access to the payload fields

   String GetUsername() const

   {

      return this->GetFieldValueAs<String>(this->fieldIndexUsername);

   }

 

   String GetPermissions() const

   {

   return this->GetFieldValueAs<String>(this->fieldIndexPermissions);

   }

 

private:

   // Define the fields of the payload type. Each field is accessed by an index.

   // The type infomation is used to ensure only valid information is set to the

   // payload fields.

   // Use direct member intialization here to ensure these members are also intialized

   // when the inherited constructors are used.

   // The order of these declarations matters, so don't change them.

 

   const size_t fieldIndexUsername = this->AddField<String>();

   const size_t fieldIndexPermissions = this->AddField<String>();

};

Registering and sending notifications

Before a notification can be sent, it must be registered with the Notification manager. If a component that registered a notification is no longer available, it has to unregister the noti­fication first. Both processes are implemented using the “NotificationRegistration Proxy Ob­ject”.

Example:

 

// in UserManager

NotificationManager& nm= NotificationManager::GetInstance();

auto UserLoggedInRegistration = nm.CreateNotificationRegistration<UserLoggedInPayload>(

   "Arp.System.Um.Login", "UserManager", Severity::Info);

 

UserLoggedInRegistration.SendNotification("hans", "admin");

 

// notification is deregistered automatically in destructor of UserLoggedInRegistration

Metadata of a notification

To register a notification, the sender must specify metadata information:

notificationName:
The notification name defines under which name the notification is published. The parts of a name are separated by ”.” and should correspond, up to the component level, to the name of the component to be sent, e.g., “Arp.System.Um.UserLogin”, “Arp.Plc.Domain.PlcStateChanged”.

senderName:
The name of the component sending the notification.

Severity:
Information on the severity of the notification. Processing is not affected by the severity.

Info: General information

Warning: Warning for the user

Error: Error without serious impact

Critical: Error with medium impact

Fatal: Error with serious impact

PayloadTypeId:
Unique identification for the user data type.

The NotificationNameId is returned:
The ID is used to for identify an instance of a notification. It is required for sending and unregistering a notification.

The status of a notification registration can have the following states:

Subscribed: A recipient subscribed but has not yet been registered by a component. Subscribing to a notification that has not yet been registered or has again been unreg­istered is only possible during startup of the firmware and startup of the controller. Oth­erwise, an exception will be triggered.

Registered: Registered by a component

Unregistered: Unregistered by a component

The GetNotificationRegistration() method returns the above meta information about a notification.

Receiving notifications

To receive notifications, the recipient must subscribe to the notification name with the Notification manager. The NotificatiaonSubscriber object is used for receiving notifications. If a recipient is no longer to receive notifications, it has to unsubscribe from theses notifications. To make sure that this is done at the end of the recipient's life cycle, it is recommended to use a proxy object like the one described for registering of notifications.

Example:

 

// in eHMI

void HandleUserLogins(const Notification& notification)

{

   auto payload = notification.GetPayloadAs<UserLoggedInPayload>();

   // do something with payload.GetUsername() and payload.GetPermissions()

}

 

// in some long living object of eHMI

NotificationManager& nm = NotificationManager::GetInstance();

auto UserLoggedIn = nm.CreateNotificationSubscriber("Arp.System.Um.Login");

UserLoggedIn.OnNotification += make_delegate(HandleUserLogins);

 

// notification is unsubscribed automatically in destructor of UserLoggedIn

Querying information

The INotificationManagerInfo interface is the interface for querying information about the NotificationManager. The following options are available:

GetNotificationName(): This method returns the notification name.

GetNotificationNameId(): This method returns the ID of a notification name.

GetNotificationRegistration(): This method returns the information about a noti­fication made available during registration (see Metadata of a notification.)

GetAllKnownNotificationNameIds(): This method returns a list of NotificationNameIds of all known notifications, independent of their status.

GetNotificationsNameIdsByStatus(): This method returns a list of NotificationNameIds of notifications with a specific status.