Advanced use of IComponent and IProgram

Using the PLCnext CLI (see PLCnext CLI ), Eclipse® Add-in or Visual Studio® extension 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.

IComponent and ComponentBase

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 implementing additional interfaces, which are described in the sections Advanced component features and IProgramComponent and IProgramProvider. To be able to use the classes, you have to include the corresponding header files.

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

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 configuration. The ACF or PLM calls the following IComponent operations:

  • void LoadConfig(void)
  • void SetupConfig(void)

With the following operations, the components are reset or stopped:

  • void ResetConfig(void)
  • void Dispose(void)

For further details on the operations, see the following sections.

Startup functions

Initialize

The component instance is initialized via the function specified below. For each instantiated component, the PLCnext Technology firmware calls the following function first:

virtual void Initialize(void);

Resources that have been allocated and initialized in the Initialize() function have to be released in the Dispose() function.

SubscribeServices

Thereafter, the firmware calls the following function for each instantiated component.

virtual void SubscribeServices(void);

Here, a component can obtain RSC services provided by the firmware.

LoadSettings

Subsequently, the LoadSettings() function is called. The path to the settings (settingsPath) can be specified for the respective component instance in its *.acf.config or *.plm.config configuration file. The format and content of this settings file has to be specified by the respective component (type). The PLCnext Technology firmware does not make any assumptions 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);

LoadConfig SetupConfig

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

virtual void LoadConfig(void);
virtual void SetupConfig(void);

Shutdown functions

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 function extensions. It is simply called at different times.

Dispose

A component is stopped after calling the following function:

virtual void Dispose(void);

Resources allocated by this component instance have to be released within this function.

For more features refer to section Advanced component features.

IProgramComponent and IProgramProvider

A component that can instantiate user programs additionally implements the interfaces IProgramComponent and IMetaComponent by means of derivation from the ProgramComponentBase class. The firmware requires these interfaces to instantiate programs and receive information about their ports.

The PLCnext CLI generates the necessary code. For this, a code is generated for each component, which then implements the IProgramProvider interface. Therefore the //#program and the //#component() directive have to be specified immediately before the class definition in the components (repectively programs) header file. By parsing this header file the PLCnext CLI knows which component can instantiate which program. 

//#program
//#component(SampleComponent)
class SampleProgram : public ProgramBase,

The corresponding header files are included 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 provides 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 PLCnext CLI generates the necessary code of the program provider.

The IProgramProvider makes the following function available for the instantiation of programs:

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 as 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
This name is necessary because a component can instantiate more than one program type.

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 theExecute() function that is called by the ESM task during each pass are available. The PLCnext CLI creates such a user program class that inherits the IProgram interface from the ProgramBase base class by means of derivation. Furthermore, with this interface the IN and OUT ports of the program are registered to the GDS. Using the directives which are explained in the following section, the PLCnext CLI generates the necessary registration code as well as the metadata configuration files (*.libmeta, *.compmeta, *.progmeta) required for PLCnext Engineer

To use the classes, you have to include the corresponding header files.

  • IProgram:            Arp/Plc/Esm/IProgram.hpp
  • ProgramBase:    Arp/Plc/Esm/ProgramBase.hpp

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 class member variables prefixed by dedicated PLCnext CLI directives according to the following pattern.

For example:

public:
//#port
//#attributes(Input|...)
//#name(...)
int16 myInPort;

With PLCnext CLI release 2019.0 LTS or newer, elementary data types, arrays, and structs can be used in port definitions.
For details, see Supported port data type connectors.

From PLCnext CLI release 2020.0 LTS, external libraries are searched as well.

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

Attribute Description
Input The variable is defined as IN port.
Output The variable is defined as OUT port.
Retain The variable value 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 can be accessed via the REST interface. However, the variable is not visible in the PLCnext Engineer HMI editor (implementation is planned for a future version of PLCnext Engineer). 
Proficloud The variable is visible for Proficloud (for OUT ports only).
Redundant
(from firmware 2022.0 LTS)
A variable with this attribute is synchronized from the PRIMARY controller to the BACKUP controller.
Only relevant for controllers with system redundancy capability, like RFC 4072R.

Attributes are separated by the | separator, e.g.: //#attributes (Input|Opc|Retain)

From the attributes, the PLCnext CLI generates the metadata. If a variable does not have an attribute, it can only be used internally by the program.

Provide variables you want to connect via the GDS with an Input or Output attribute, depending on the data direction. You can use all other attributes to control the initialization behavior or enable visibility in OPC UA® or a PLCnext Engineer visualization. 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.

As of firmware release 2021.6, it is also possible to create a program that creates ports dynamically at runtime as an alternative to the static ports. The program registers the corresponding ports with the GDS in the constructor. All instances of this program type must have the same memory structure. Further details can be found in the DynamicPorts example.

Advanced component features

IControllerComponent

To use this class, you have to include the corresponding header file.

  • IControllerComponent: Arp/System/Acf/IControllerComponent.hpp

A component created with the PLCnext CLI automatically derives from the IComponent interface. A user program or a function extension may need individual lower-priority 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 create and start individual, lower-priority threads the programs delegate tasks to.
  • The Stop() function is called when the PLC application is stopped, before the programs are deleted. At this point, the component's threads can be stopped and deleted.

If the component is managed by the ACF (Function Extension):

  • 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() function is called before the components are deleted. At this point the component's threads can be stopped and deleted.

PLC events

A component can register to PLC events. These events inform about changes of the PLC state. In order to become informed about a certain change, the component has to provide a dedicated method and has to register this method to the Plc domain (PlcDomainProxy, see API documentation). It is required that the component unregisters all these methods in the Dispose() method.

The following headers need to be included (#include):

  • Arp/Plc/Commons/Domain/PlcStartKind.hpp
  • Arp/Plc/Commons/Domain/PlcDomainProxy.hpp

Additionally it is convenient to state:

  • using namespace Arp::Plc::Commons::Domain;

PLC events are fired when the PLC state changes. In case of a change one event is fired at the beginning of the change (e.g. PLCLoading) and the other is fired at the end of the change (e.g. PLCLoaded). Depending on the event the methods have to match to the following signatures:

void PlcLoading();   // Loading of PLC configuration starts
void PlcLoaded();    // PLC configuration has been loaded
void PlcStarting(PlcStartKind startkind);
void PlcStarted();
void PlcStopping();
void PlcStopped();
void PlcUnloading();
void PlcUnloaded();
void PlcChanging();  // Download Changes by PLCnext Engineer
void PlcChanged();

Once a method is declared in the component class, it can be registered to a PLC event. It is recommended to register (using the += operator) within the component’s Initialize() method. To prevent segmentation faults it is required to unregister such method before the component is deleted. Therefore, it is recommended to unregister (using the -= operator) within the Dispose() method, for example:

void ExampleComponent::Initialize()
{
…
    PlcDomainProxy& plcDomainProxy = PlcDomainProxy::GetInstance();
    // register Plc event handler – do not forget to unregister in the Dispose() method
    plcDomainProxy.PlcStarting += make_delegate(this, &ExampleComponent::OnPlcStarting);
…
}
void ExampleComponent::Dispose()
{
…
    PlcDomainProxy& plcDomainProxy = PlcDomainProxy::GetInstance();
    // unregister all Plc event handlers (required!)
    plcDomainProxy.PlcStarting -= make_delegate(this, &ExampleComponent::OnPlcStarting);
…
}

Component ports

Similar to a program, a component may have ports, too. Program ports are mainly intended for real-time data exchange between the program instances and from/to fieldbus I/Os. The data exchange is performed by configured GDS connectors.

To ensure data consistency, these connectors take advantage of the ESM tasks to which the program instances are associated. But since component instances are not associated to an ESM task, connectors to/from component ports behave as connectors to/from resource global variables. If a component port is connected to a program port, its value is transferred within the context of the ESM task that is associated to the program instance.

If a component port is connected to another component port or a fieldbus I/O, its value is transferred every 50 ms in the context of the GLOBALS task. Information on the connection via IN and OUT ports and via resource-global variables can be found in section Fieldbus connection.

Another main difference between program ports and component ports are the initial values and the behavior in case of a cold and warm start. While programs are deleted and created newly during cold and warm start, a component remains in memory:

  • A PLM component is deconstructed and constructed newly when a PLC project is loaded (new download from PLCnext Engineer or restart of the controller).
  • An ACF component is deconstructed when the firmware is stopped and constructed newly when the firmware is started.

Consequently, the component ports keep their values during cold and warm start, regardless whether the retain attribute is set or not. If it is intended to initialize component ports during warm or cold start (in order to achieve similar behavior as program ports) such initialization has to be programmed explicitly with the component class.

Note: Anyhow the Extended retain handling also restores the values of retentive component ports when the supply power returns after a power loss.
For initialization it is recommended to register the component to the OnPlcStarting event. Please note, that the OnPlcStarting event has an input parameter of data type PlcStartKind which informs about the different start types. The following example shows the basic code in the method that is registered to the PlcStarting event.

Example:

void ExampleComponent::OnPlcStarting(PlcStartKind startKind)
      {
          // this event handler is called before starting the PLC
        if (startKind == PlcStartKind::Warm ||
            startKind == PlcStartKind::RestoreWarm ||
            startKind == PlcStartKind::Cold)
        {   // initialize non-retain ports here anyhow (in all 3 cases)
            // if necessary, further member variables can be initialized, too
            …         
        if (PlcStartKind::Cold == startKind)
            {   // initialize retain ports here
                …
            }
        }
        // no special action required in case of PlcStartKind::Hot
    }

Component ports may be accompanied by attributes like program ports (see table in section Creating IN and OUT ports)

  • The attributes Input and Output can be used to arrange the port as IN or OUT port in the Port List of PLCnext Engineer (Port List editor at the PLCnext node of the controller in the PLANT view)
  • The attribute Retain is evaluated by the Extended retain handling to store and recover the value in case of power loss or when a Restore is performed.
    Note:  If it is intended to initialize component ports during warm or cold start (in order to achieve similar behavior as program ports) such initialization has to be programmed with the component class.
  • The attribute Opc adds the component port to the namespace of the  OPC UA server.
  • The attributes Ehmi and Proficloud have no effect for component ports.

PLCnext CLI generates the code of the component’s method RegisterComponentPorts() from PLM as well as for ACF components. Using this method the component ports are announced to the GDS. In case of PLM components, the PLM calls this method but the ACF does not call this method for ACF components. Consequently, the code of the ACF component itself has to call this method. This enables access to the component ports by GDS (connectors), extended retain handling and OPC UA. A good approach is to call this method in the PlcLoading event, for example:

void ExampleComponent::OnPlcLoading()
      {
          this->RegisterComponentPorts();  // method code itself is generated by PLCnext CLI
      }

It is not required to unregister these ports because the GDS forgets all ports when the PLC application is unloaded or resetted.


• Published/reviewed: 2024-09-24   ☀  Revision 073 •