While the notion of inheritance is used readily and frequently, interfaces are rather rarely applied. However, interfaces provide a number of benefits, which increase flexibility of the PLC programs and improve maintenance. The following post will introduce the possibilities of interfaces relating to the IEC 61131-3 standard.
Inheritance appears to be the best known concept of the object-oriented programming. This is certainly because most textbooks cover it as the first topic and in this way put emphasis on it. Objects from the real world are readily represented as classes, which depict an “is-a” relationship in this manner. A dog is a mammal; a mammal is a living organism. Thus, a human or a spider can be mapped fairly simply.
FUNCTION_BLOCK PUBLIC FB_LivingOrganism FUNCTION_BLOCK PUBLIC FB_Mammal EXTENDS FB_LivingOrganism FUNCTION_BLOCK PUBLIC FB_Dog EXTENDS FB_Mammal FUNCTION_BLOCK PUBLIC FB_Human EXTENDS FB_Mammal FUNCTION_BLOCK PUBLIC FB_Insect EXTENDS FB_LivingOrganism FUNCTION_BLOCK PUBLIC FB_Spider EXTENDS FB_Insect
Representation as a UML class diagram:
Since the term class is more common in object-oriented programming, it is preferred to the term function block in the following.
If one deals with the concept of object-oriented programming, one would notice that inheritance is its best known feature. It allows an easy definition of new classes (subclasses), which are derived from the existing classes (base classes). A subclass inherits all properties, methods, actions, inputs and outputs of the base class. They should be reprogrammed only if their inherited functionalities have to be extended or changed.
While actions, inputs and outputs are always inherited to the subclasses, inheritance of properties and methods can be influenced by the keywords PUBLIC, PROTECTED, INTERNAL and PRIVATE. In the case of defining a function block, creating subclasses, i.e. possibilities of inheritance can also be constrained (PUBLIC, INTERNAL, and FINAL).
However, inheritance brings along with it several disadvantages:
|●||Changes of base classes can affect the subclasses, which inherit from it. Subclasses and base classes are closely coupled to one another.|
|●||If base classes and subclasses are in one PLC library, the inheritance hierarchy of these blocks can not be changed by the user of the PLC library.|
|●||The IEC 61131-3 standard does not allow, as well as Java and C# multiple inheritance. A subclass always has a base class. Thus, it would be no longer simply be possible to create a function block for Spiderman with the abovementioned class hierarchy. Inheritance from FB_Human and(!) FB_Spider is not possible.|
|●||Subclasses are always dependent on their base classes. This can make it difficult to reuse a subclass.|
Base classes mutate easily to a “melting pot” of methods and properties, which are required in one or the other subclass. Such “super base classes” get eventually difficult to maintain. Therefore, I try to keep base classes as small as possible. The deeper you are in the inheritance hierarchy, the smaller the classes should be. Likewise, I try to keep the number of inheritance levels small. The maximal value of 6 for the depth of inheritance can serve here as a benchmark.
Interfaces deal with definition of methods and properties, which are implemented in one class. The classes that implement the same interface look identical from outside and can be treated equally. The exact implementation of the required methods and properties is not described by the interface. Each class, which implements the interface, can determine the internal structure itself.
The interface I_Sample contains the method M_DoFoo() with a return value of type BYTE.
INTERFACE I_Sample METHOD M_DoFoo : BYTE
Two function blocks implement the interface.
FUNCTION_BLOCK PUBLIC FB_A IMPLEMENTS I_Sample METHOD M_DoFoo : BYTE M_DoFoo := 100; RETURN; FUNCTION_BLOCK PUBLIC FB_B IMPLEMENTS I_Sample METHOD M_DoFoo : BYTE M_DoFoo := 200; RETURN;
Both function blocks have no other binding except the common interface. Nevertheless, both of them can be equally treated.
PROGRAM MAIN VAR aTest : ARRAY[1..2] OF I_Sample; fbA : FB_A; fbB : FB_B; a : INT; nIndex : INT; END_VAR aTest := fbA; aTest := fbB; FOR nIndex := 1 TO 2 DO a := aTest[nIndex].M_DoFoo(); END_FOR
The method M_DoFoo() of the function block FB_A is accessed in the first loop pass, the method M_DoFoo of FB_B is accessed in the second loop pass. The implementation of the methods is determined individually in each function block (FB_A and FB_B).
While inheritance is a “is-a” relationship, interfaces can be described as “behaves-as” relationship or “has-a” relationship.
This kind of binding reduces dependencies considerably. Thus, the FOR loop (line 12 … 14) deals with the interface I_Sample all along. The loop works against the interface I_Sample and not against a specific implementation. The function blocks FB_A and FB_B could always be exchanged for other function blocks, as long as these implement the interface I_Sample. There would be no need in any adjustment of a FOR loop.
Since an interface represents a data type, it can be transferred through the inputs or parameters of the methods to the function blocks. The function block calls the methods of the interfaces without concrete knowledge of executed functionality (polymorphism).
One function block can implement several interfaces. As a result, functionalities which are defined by interfaces can be combined in one function block. The class hierarchy stays very flat and the dependencies of the function blocks among themselves are minimal. By means of splitting a single assignment into several smaller, there exists no danger of covering all imaginable features in one super base class.
A PLC library makes the interfaces I_Light, I_Delayable and I_Dimmable available. Each interface has a corresponding implementation (FB_Light, FB_DelayedLight and FB_DimmingLight), which are also located in a PLC library.
|Interfaces||Function blocks, with the implemented interfaces|
FB_DelayedLight and FB_DimmingLight inherit from FB_Light. FB_Light contains the methods M_On() and M_Off(), as well as the property bControlLevel. This function block serves as a base class, because this base functionality is precisely equal for all function blocks.
Representation as a UML class diagram:
Statement of task:
On the basis of this PLC library, a programmer has to draft a function block, which contains functionalities of both FB_DelayedLight and FB_DimmingLight.
It would be appropriate to use the interfaces I_Delayable and I_Dimmable. The function block FB_Light can also be used as a base class. This brings along the advantage, that wherever I_Delayable, I_Dimmable and FB_Light are applied, their own block can also be replaced. There is a small example further on.
FUNCTION_BLOCK FB_MyLight EXTENDS FB_Light IMPLEMENTS I_Delayable, I_Dimmable
Thus, the function block has the following methods and properties:
The already existing function blocks can be used, so that the methods do not have to be entirely reprogrammed. For this, I have created instances of both FB_DimmingLight and FB_DelayedLight.
VAR fbDimmingLight : FB_DimmingLight; fbDelayedLight : FB_DelayedLight; END_VAR
I redirect the invocations to these instances in the respective methods:
METHOD M_SetControlLevel VAR_INPUT nControlLevel: BYTE; END_VAR fbDimmingLight.M_SetControlLevel(nControlLevel); IF (nControlLevel = 0) THEN fbDelayedLight.M_Off(); ELSE fbDelayedLight.M_On(); END_IF
Thus, the logic for dimming or switching on/off does not have to be developed for the second time.
Representation in CFC:
Methods and properties can not be directly graphically represented with the IEC 61131-3 standard. But if it is possible, a corresponding input or output can be created for each method and for each property. Changes in the inputs are forwarded to the respective methods and the states from the properties are assigned to the outputs.
FUNCTION_BLOCK FB_MyLight EXTENDS FB_Light IMPLEMENTS I_Delayable, I_Dimmable VAR_INPUT bRecallMinLevel : BOOL; bSetControlLevel : BOOL; nSetControlLevel : BYTE; END_VAR VAR_OUTPUT nControl : BYTE; END_VAR VAR rtrigRecallMinLevel : R_TRIG; rtrigSetControlLevel : R_TRIG; fbDimmingLight : FB_DimmingLight; fbDelayedLight : FB_DelayedLight; _nControlLevel : BYTE; END_VAR rtrigOn(CLK := bOn); IF (rtrigOn.Q) THEN M_On(); END_IF rtrigOff(CLK := bOff); IF (rtrigOff.Q) THEN M_Off(); END_IF rtrigRecallMinLevel(CLK := bRecallMinLevel); IF (rtrigRecallMinLevel.Q) THEN M_RecallMinLevel(); END_IF rtrigSetControlLevel(CLK := bSetControlLevel); IF (rtrigSetControlLevel.Q) THEN M_SetControlLevel(nSetControlLevel); END_IF fbDimmingLight(); fbDelayedLight(); _bControlLevel := fbDelayedLight.bControlLevel; IF (_bControlLevel) THEN _nControlLevel := fbDimmingLight.nControlLevel; ELSE _nControlLevel := 0; END_IF bControl := bControlLevel; nControl := nControlLevel;
Thus, the block can be easily used in CFC or the like:
Request of an interface at runtime
Instances of function blocks can be forwarded to other function blocks as parameters. The function block has therefore unlimited access to all methods, properties, and inputs and outputs of the forwarded block.
A PLC library has to be extended by a function block, which groups three lighting blocks together. All three blocks can be controlled by the inputs. Thus, all lights should be switched on or off by bAllOn and bAllOff. The input bAllCallMinLevel has to affect only the blocks, which implement the interface I_Dimmable. The control variables are read out through the outputs.
FUNCTION_BLOCK PUBLIC FB_RoomController VAR_INPUT bAllOn : BOOL; bAllOff : BOOL; bAllCallMinLevel : BOOL; refLight01 : REFERENCE TO FB_Light; refLight02 : REFERENCE TO FB_Light; refLight03 : REFERENCE TO FB_Light; END_VAR VAR_OUTPUT aControlLevel : ARRAY[1..3] OF BOOL; aDimmLevel : ARRAY[1..3] OF BYTE; END_VAR
It should be possible to forward all variants of the lighting blocks to the block. For this reason, FB_Light is used as a data type of the three lighting blocks. In this way, each block derived from FB_Light can be transferred at the inputs.
VAR fbRoomController : FB_RoomController; fbLight : FB_Light; fbDelayedLight : FB_DelayedLight; fbDimmingLight : FB_DimmingLight; END_VAR
The inputs bAllOn and bAllOff are therefore easily reassembled. If a positive edge is detected at input, the reference of the corresponding method is invoked. It has to be controlled before the invocation, whether the reference is valid.
IF (__ISVALIDREF(refLight01)) THEN refLight01.M_On(); END_IF IF (__ISVALIDREF(refLight02)) THEN refLight02.M_On(); END_IF IF (__ISVALIDREF(refLight03)) THEN refLight03.M_On(); END_IF
But how can the method M_RecallMinLevel() be called, if there exists a function block, which implemented the interface I_Dimmable? After all, the data type of the input is FB_Light, and it does not know the method M_RecallMinLevel().
The function _QUERYINTERFACE() enables type conversion from one interface reference to the other. If the conversion is successful, the function returns TRUE, and the second parameter contains a reference to the requested interface.
iLight : I_Light; iDimmableLight : I_Dimmable; IF (__ISVALIDREF(refLight01)) THEN iLight := refLight01; IF (__QUERYINTERFACE(iLight, iDimmableLight)) THEN iDimmableLight.M_RecallMinLevel(); END_IF END_IF
Thus, the implementation of an interface can be queried at runtime and afterwards can also be used.
Since the block FB_MyLight is derived from FB_Light, it can be used with FB_RoomController without adaptation. Furthermore, the method FB_RecallMinLevel() is called from FB_MyLight, because it implements the interface I_Dimmable.
The block FB_RoomController makes it not only possible to use the blocks from the PLC library (FB_Light, FB_DimmingLight and FB_DelayedLight), but also to forward directly its own blocks derived from FB_Light. If such function blocks implement the interface I_Dimmable, FB_RoomController can access the method M_RecallMinLevel() without knowing the exact type of the lighting block. The mere fact, that I_Dimmable is implemented, is sufficient. In this case, FB_RoomController and the lighting blocks communicate through the interface I_Dimmable.
So far, this problem could be solved with a communication structure. All lighting blocks and FB_RoomController would have as an input/output variable a structure, through which the blocks communicate with each other. But this variant has a disadvantage: what if the block FB_RoomController should be extended? Managing of scenes could serve as an example. Each lighting block has 10 scenes, which should be callable with the help of FB_RoomController. In the previous variant, FB_RoomController had to obtain other communication structures for the management of scenes, or the existing communication structure had to be extended. In both cases, FB_RoomController had to be adapted in such a way that it would be incompatible to the previous one. Development of a completely new blocks (e.g., FB_RoomControllerV2) could be one more option.
Defining a new interface (e.g., I_SceneManager) is more flexible. This interface determined the methods and the properties, which are necessary for the invocation of the respective scenes. If a lighting block has implemented this interface, FB_RoomController can invoke the requested scene. Extensions or adaptations of the existing inputs and outputs of FB_RoomController are not necessary; the block stays downward compatible.
Since inheritance is easy to apply, there is a great temptation to create base classes, which contain all possible functionalities. These functionalities are actually required from only one or the other subclass. After several extension cycles, dependencies arise, which are difficult to maintain. Interfaces give the opportunity to specify a particular task. These interfaces can be accessed during the development of a new function block without applying complex inheritance hierarchies.