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.
Sample (TwinCAT 3.1.4020) on GitHub
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.
9 thoughts on “IEC 61131-3: Object composition with the help of interfaces”
Understandable for beginners and strengthened the assets of OOP Professionals.
Intensive and well documented Jobs. I will forward it to my colleagues.
All the best for you Stefan and your Blog.
Jerome / Switzerland
Thanks, I had looked at the interface mechanism before and only got the idea that it was just another kind of user defined data type. It seemed like there should have been a little more to it and you have explained what that little bit more actually is:
iLight : I_Light;
iDimmableLight : I_Dimmable;
IF (__ISVALIDREF(refLight01)) THEN
iLight := refLight01;
IF (__QUERYINTERFACE(iLight, iDimmableLight)) THEN
Thanks for your nice article.
It seems you are trying to say that using interfaces is a better approach rather than inheritance, but it’s not the case.
In fact inheritance and interfaces are two different ways to implement polymorphism. Each of them is useful for particular problems and solutions.
For example you have noticed about the temptation of creating base classes with all possible functionalities and its future problems. You’re at right and it’s not a good approach, but the solution of this problem is only keeping all classes, not only base classes, as simple as possible. This is the first law of the SOLID principles, which called “Single responsibility principle” and notices that each class (FB) should have only one responsibility. For example writing an FB that both scales analog input values and doing the weighing tasks, is a wrong design.
So this is a problem of our design not OOP techniques. It’s important to note that using complicated interfaces can also lead us to the same problem as complicated base classes.
I think, not only in OOP, but in all programming techniques, we should try to keep the building blocks, FBs, FCs, methods, subroutines, etc., as simple as possible.
Can you explain the html tags? I want to understand why these tags are needed to redirect the method calls. Great work by the way. My Beckhoff rep gave me a link to this site and your OOP articles are great!
Never mind I believe it was an error in the browser loading the site as the tags are no longer present in the code snipet. I knew I had never seen HTML inline In TwinCAT before. 😂
Great work making more and more sense of this. Think I found a typo though.
“Furthermore, the method FB_RecallMinLevel() is called from FB_MyLight, because it implements the interface I_Dimmable.”
Where you mention FB_RecallMinLevel it should be M_RecallMinLevel.
Other than that thanks!
Hi, excellent website, I have been struggling to come to a nice OOP methodology of programming with TwinCAT. I use Java mostly along with a host of other regular programming languages and trying to apply the patterns you learn in OOP has been a struggle.
I had a question though, how would you handle integration with existing libraries in an OOP fashion, where they use the common bDone/bBusy/bError/nErrorId pattern? I have resorted at the moment to essentially creating a function block within a method using VAR_INST parameters, but this feels very unsatisfactory.