IEC 61131-3: The Command Pattern

A command can be run on a function block by calling a method. Function block A calls a method of function block B. So far, so good, but how can such commands be exchanged flexibly between several function blocks. The command pattern provides an interesting approach.

A small example from the home automation should help us at this. Suppose we have several FBs which represent each a device or an actuator. Each device has an individual set of commands which provide different functions.

Pic01

A further function block should display 8-button keypad. This keypad contains 8 buttons which correspond to single functionalities (commands) of the devices. The necessary commands are called in the devices via positive edge at the corresponding input.

FUNCTION_BLOCK PUBLIC FB_SwitchPanel
VAR_INPUT
  arrSwitch          : ARRAY [1..8] OF BOOL;
END_VAR
VAR
  fbLamp             : FB_Lamp;
  fbSocket           : FB_Socket;
  fbAirConditioning  : FB_AirConditioning;
  fbCDPlayer         : FB_CDPlayer;
  arrRtrig           : ARRAY [1..8] OF R_TRIG;
END_VAR

arrRtrig[1](CLK := arrSwitch[1]);
IF arrRtrig[1].Q THEN
  fbSocket.On();
END_IF

arrRtrig[2](CLK := arrSwitch[2]);
IF arrRtrig[2].Q THEN
  fbSocket.Off();
END_IF

arrRtrig[3](CLK := arrSwitch[3]);
IF arrRtrig[3].Q THEN
  fbLamp.SetLevel(100);
END_IF

arrRtrig[4](CLK := arrSwitch[4]);
IF arrRtrig[4].Q THEN
  fbLamp.SetLevel(0);
END_IF

arrRtrig[5](CLK := arrSwitch[5]);
IF arrRtrig[5].Q THEN
  fbAirConditioning.Activate();
  fbAirConditioning.SetTemperature(20.0);
END_IF

arrRtrig[6](CLK := arrSwitch[6]);
IF arrRtrig[6].Q THEN
  fbAirConditioning.Activate();
  fbAirConditioning.SetTemperature(17.5);
END_IF

arrRtrig[7](CLK := arrSwitch[7]);
IF arrRtrig[7].Q THEN
  fbCDPlayer.SetVolume(40);
  fbCDPlayer.SetTrack(1);
  fbCDPlayer.Start();
END_IF

arrRtrig[8](CLK := arrSwitch[8]);
IF arrRtrig[8].Q THEN
  fbCDPlayer.Stop();
END_IF

Regarding flexibility, this outline is rather suboptimal. Why?

Unflexible. There is a fixed assignment between FB_SwitchPanel and the single devices (FB_Lamp, FB_Socket, FB_CDPlayer und FB_AirConditioning). If, for example, FB_Socket has to be replaced with a second FB_Lamp, it is necessary to adapt the implementation of FB_SwitchPanel.

Missing reusability. If the buttons 3 and 4 have to serve for controlling the CD player, the necessary sequence of methods calls has to be programmed anew.

Undynamic. The 8-button keypad was implemented as a function block. As long as all key fields have the same key assignments, this approach is executable. But what if the key assignments have to be different? An individual function block has to be programmed or, instead of function blocks, programs should be used.

Definition of the Command Pattern

The solution of the problem is to introduce a software layer which is inserted between the keypad and the devices. This layer encapsulates each single command (with a command FB) and contains all relevant method calls to perform an action on the device. Thus, the 8-button keypad sees only these commands and contains no further references to the corresponding devices.

You will find the exact definition of the command pattern in the book (Amazon Advertising Link *) Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph E. Johnson and John Vlissides.

The command pattern defines three layers:

InvokerFBs of this layer trigger the required command. The invoker, the 8-button keypad FB_SwitchPanel in our example, doesn’t know the command receiver. But it knows how a command is started.
ReceiverThese are the FBs which represent the corresponding receiver of the commands: FB_Socket, FB_Lamp, …
CommandsEach command is represented by a FB. This FB contains a reference to the receiver. Furthermore, these commands have a method to activate the command. If this method is called, the command FB knows, which methods have to be executed on the receiver to achieve the desired effect.

Let us take a close look at the command FB.

A command FB encapsulates an assignment by containing a set of actions for a certain receiver. For this purpose, the actions and the reference of the receiver are combined into one FB. Via any method (e.g., Execute()), the command FB ensures that the proper actions are executed on the receiver. The invoker doesn’t see from the exterior, which actions these actually are. It only knows, that when it calls the method Invoke(), all required steps are performed.

Commands

Below is the implementation for the command FB to run the ON command on the FB_Socket:

FUNCTION_BLOCK PUBLIC FB_SocketOnCommand
VAR
  refSocket : REFERENCE TO FB_Socket;
END_VAR

The variable refSocket contains the reference to the instance of the block FB_Socket, i.e. the receiver of the command. The reference is set via the method FB_init.

METHOD FB_init : BOOL
VAR_INPUT
  bInitRetains  : BOOL;
  bInCopyCode   : BOOL;
  refNewSocket  : REFERENCE TO FB_Socket;
END_VAR

IF (__ISVALIDREF(refNewSocket)) THEN
  THIS^.refSocket REF= refNewSocket;
ELSE
  THIS^.refSocket REF= 0;
END_IF

The method Execute() runs the required action on FB_Socket:

METHOD Execute
IF (__ISVALIDREF(THIS^.refSocket)) THEN
  THIS^.refSocket.On();
END_IF

Depending on the receiver, this method can be also composed of several actions. Thus, the following method calls are necessary to begin playing a CD:

METHOD Execute
IF (__ISVALIDREF(THIS^.refCDPlayer)) THEN
  THIS^.refCDPlayer.SetVolume(40);
  THIS^.refCDPlayer.SetTrack(1);
  THIS^.refCDPlayer.Start();
END_IF

Since all command FBs in our example provide the method Execute() to run the command, this method is standardised by the interface I_Command. Each command FB has to implement this interface.

Invoker

The 8-button keypad (FB_SwitchPanel) should only get the information, which command FBs have to be used. The details of the command FBs doesn’t have to be known. FB_SwitchPanel knows only 8 variables that are of type I_Command. If a positive edge on a button is detected, the command FB calls the method Invoke() via the interface I_Command.

FUNCTION_BLOCK PUBLIC FB_SwitchPanel
VAR_INPUT
  arrSwitch   : ARRAY [1..8] OF BOOL;
END_VAR
VAR
  aiCommand   : ARRAY [1..8] OF I_Command;
  arrRtrig    : ARRAY [1..8] OF R_TRIG;
  nIndex      : INT;
END_VAR

FOR nIndex := 1 TO 8 DO
  arrRtrig[nIndex](CLK := arrSwitch[nIndex]);
  IF arrRtrig[nIndex].Q THEN
    IF (aiCommand[nIndex] <> 0) THEN
      aiCommand[nIndex].Execute();
    END_IF
  END_IF
END_FOR

Prior to this, the required command FB is assigned to the single buttons using the method SetCommand(). Thus, FB_SwitchPanel is universally applicable.

METHOD PUBLIC SetCommand : BOOL
VAR_INPUT
  nPosition  : INT;
  iCommand   : I_Command;
END_VAR

IF ((nPosition >= 1) AND (nPosition <= 8) AND (iCommand <> 0)) THEN
  THIS^.aiCommand[nPosition] := iCommand;
END_IF

The invoker FB_SwitchPanel doesn’t know the receiver. It sees only the interface I_Command with its method Execute() 8 times.

Receiver

The FBs which map a receiver doesn’t have to be adapted. A command FB can execute the required actions with the help of the corresponding methods or inputs.

Application

Below is a small example of a program, which combines an application from the three software layers shown above:

PROGRAM MAIN
VAR
  // Invoker
  fbSwitchPanel                 : FB_SwitchPanel;

  // Receiver
  fbSocket                      : FB_Socket;
  refSocket                     : REFERENCE TO FB_Socket := fbSocket;
  fbLamp                        : FB_Lamp;
  refLamp                       : REFERENCE TO FB_Lamp := fbLamp;
  fbAirConditioning             : FB_AirConditioning;
  refAirConditioning            : REFERENCE TO FB_AirConditioning := fbAirConditioning;
  fbCDPlayer                    : FB_CDPlayer;
  refCDPlayer                   : REFERENCE TO FB_CDPlayer := fbCDPlayer;

  // Commands
  fbSocketOnCommand             : FB_SocketOnCommand(refSocket);
  fbSocketOffCommand            : FB_SocketOffCommand(refSocket);
  fbLampSetLevel100Command      : FB_LampSetLevelCommand(refLamp, 100);
  fbLampSetLevel0Command        : FB_LampSetLevelCommand(refLamp, 0);
  fbAirConComfortCommand        : FB_AirConComfortCommand(refAirConditioning);
  fbAirConStandbyCommand        : FB_AirConStandbyCommand(refAirConditioning);
  fbMusicPlayCommand            : FB_MusicPlayCommand(refCDPlayer);
  fbMusicStopCommand            : FB_MusicStopCommand(refCDPlayer);

  bInit                         : BOOL;
END_VAR

IF (NOT bInit) THEN
  fbSwitchPanel.SetCommand(1, fbSocketOnCommand);
  fbSwitchPanel.SetCommand(2, fbSocketOffCommand);
  fbSwitchPanel.SetCommand(3, fbLampSetLevel100Command);
  fbSwitchPanel.SetCommand(4, fbLampSetLevel0Command);
  fbSwitchPanel.SetCommand(5, fbAirConComfortCommand);
  fbSwitchPanel.SetCommand(6, fbAirConStandbyCommand);
  fbSwitchPanel.SetCommand(7, fbMusicPlayCommand);
  fbSwitchPanel.SetCommand(8, fbMusicStopCommand);
  bInit := TRUE;
ELSE
  fbSwitchPanel();
END_IF

An instance of FB_SwitchPanel is created for 8 keys (invoker).

Likewise, an instance is declared per each device (receiver). Moreover, a reference of each instance is required.

When declaring the command FBs, the created references are passed to FB_init. If necessary, further parameters can be also transferred here. Thus, the command to set lighting has a parameter for the control value variable.

In this example, the single command FBs can be assigned to the 8 buttons with the method SetCommand(). The method expects the key numbers (1…8) as a first parameter, and an FB, which implements the interface I_Command, as a second parameter.

The resulting benefits are quite convincing:

Decoupling. Invoker and receiver are decoupled from each other. As a consequence, FB_SwitchPanel can be designed generically. The method SetCommand() provides possibility to adapt the assignment of the keys during runtime.

Expandability. Any command FB can be added. Even if FB_SwitchPanel is provided by a library, a programmer can define any command FB and use it with FB_SwitchPanel, without the need for adapting the library. Because the additional command FBs implement the interface I_Command, they can be used by FB_SwitchPanel.

Sample 1 (TwinCAT 3.1.4020) on GitHub

UML class diagram

UML

All the commandos implement the interface I_Command.

The invoker has references to the commands via the interface I_Command and executes these commands if required.

One command calls all the required methods of the receiver.

MAIN sets out a link between the single commands and the receiver.

Extensions

The command pattern can be very easily expanded with further functions.

Macro command

Especially interesting is the possibility to combine any commands with one another and encapsulate in a command object. One speaks here of so-called macro commands.

A macro command has an array of commands. Up to four command FBs can be defined in this example. Since each command FB implements the interface I_Command, the commands can be stored in an array of type ARRAY [1…4] OF I_Command.

FUNCTION_BLOCK PUBLIC FB_RoomOffCommand IMPLEMENTS I_Command
VAR
  aiCommands    : ARRAY [1..4] OF I_Command;
END_VAR

The single command FBs are piped to the macro command via the method FB_init().

METHOD FB_init : BOOL
VAR_INPUT
  bInitRetains  : BOOL;
  bInCopyCode   : BOOL;
  iCommand01    : I_Command;
  iCommand02    : I_Command;
  iCommand03    : I_Command;
  iCommand04    : I_Command;
END_VAR

THIS^.aiCommand[1] := 0;
THIS^.aiCommand[2] := 0;
THIS^.aiCommand[3] := 0;
THIS^.aiCommand[4] := 0;
IF (iCommand01 <> 0) THEN
  THIS^.aiCommand[1] := iCommand01;
END_IF
IF (iCommand02 <> 0) THEN
  THIS^.aiCommand[2] := iCommand02;
END_IF
IF (iCommand03 <> 0) THEN
  THIS^.aiCommand[3] := iCommand03;
END_IF
IF (iCommand04 <> 0) THEN
  THIS^.aiCommand[4] := iCommand04;
END_IF

When executing the method Execute(), iteration over the array is performed and Execute() is called for each command. Thus, several commands can be executed all at once with the single Execute() of the macro command.

METHOD Execute
VAR
  nIndex    : INT;
END_VAR

FOR nIndex := 1 TO 4 DO
  IF (THIS^.aiCommands[nIndex] <> 0) THEN
    THIS^.aiCommands[nIndex].Execute();
  END_IF
END_FOR

When declaring the macro command, the four command FBs are passed in MAIN. Since the macro command is a command FB itself (it implements the interface I_Command), it can be assigned to a button in the 8-button keypad.

PROGRAM MAIN
VAR
  ...
  fbRoomOffCommand     : FB_RoomOffCommand(fbSocketOffCommand,
                                           fbLampSetLevel0Command,
                                           fbAirConStandbyCommand,
                                           fbMusicStopCommand);
  ...
END_VAR

IF (NOT bInit) THEN
  ...
  fbSwitchPanel.SetCommand(8, fbRoomOffCommand);
  ...
ELSE
  fbSwitchPanel();
END_IF

Another option would be a method which passes single command FBs to the macro command. The implementation would be comparable with the method SetCommand() of the 8-button keypad. Thus, a scene controller could be implemented, with which the user can himself allocate the commands to a scene over an interactive user interface.

Sample 2 (TwinCAT 3.1.4020) on GitHub

Undo functionality

A further possible feature is a cancellation function. The 8-button keypad gets a further input which undoes the last executed command. For this purpose, the interface I_Command is extended by the method Undo().

UML2

This method contains the inversion of the execute method. If a socket is switched on with the execute method, it is switched off again in the same command FB with the undo method.

FUNCTION_BLOCK PUBLIC FB_SocketOffCommand IMPLEMENTS I_Command
VAR
  refSocket : REFERENCE TO FB_Socket;
END_VAR

METHOD Execute
IF (__ISVALIDREF(THIS^.refSocket)) THEN
  THIS^.refSocket.Off();
END_IF

METHOD Undo
IF (__ISVALIDREF(THIS^.refSocket)) THEN
  THIS^.refSocket.On();
END_IF

The implementation of the undo method for setting the lamp brightness is somewhat more complex. In this case, the command FB has to be expanded with a memory. Before setting a new regulating variable via the method Execute(), the previous regulating variable is stored. When calling, the undo method uses this value to restore the previous regulating variable.

FUNCTION_BLOCK PUBLIC FB_LampSetLevelCommand IMPLEMENTS I_Command
VAR
  refLamp        : REFERENCE TO FB_Lamp;
  byNewLevel     : BYTE;
  byLastLevel    : BYTE := 255;
END_VAR

METHOD Execute
IF (__ISVALIDREF(THIS^.refLamp)) THEN
  THIS^.byLastLevel := THIS^.refLamp.Level;
  THIS^.refLamp.SetLevel(THIS^.byNewLevel);
END_IF

METHOD Undo
IF (__ISVALIDREF(THIS^.refLamp)) THEN
  IF (THIS^.byLastLevel <> 255) THEN
    THIS^.refLamp.SetLevel(THIS^.byLastLevel);
  END_IF
END_IF

After the command FBs were expanded with a undo method, the 8-button keypad must also be adjusted.

FUNCTION_BLOCK PUBLIC FB_SwitchPanel
VAR_INPUT
  bUndo         : BOOL;
  arrSwitch     : ARRAY [1..8] OF BOOL;
END_VAR
VAR
  aiCommand     : ARRAY [1..8] OF I_Command;
  arrRtrig      : ARRAY [1..8] OF R_TRIG;
  iLastCommand  : I_Command;
  fbRtrigUndo   : R_TRIG;
  nIndex        : INT;
END_VAR

FOR nIndex := 1 TO 8 DO
  arrRtrig[nIndex](CLK := arrSwitch[nIndex]);
  IF arrRtrig[nIndex].Q THEN
    IF (aiCommand[nIndex] <> 0) THEN
      aiCommand[nIndex].Execute();
      iLastCommand := aiCommand[nIndex];
    END_IF
  END_IF
END_FOR

fbRtrigUndo(CLK := bUndo);
IF fbRtrigUndo.Q THEN
  IF (iLastCommand <> 0) THEN
    iLastCommand.Undo();
  END_IF
END_IF

In line 19, the last executed command is stored temporarily. When activating the undo function, this command can be used and the undo method is executed (line 27). The details of the inversion of a command are implemented in the command FB. The 8-button keypad refers to the single commands only via the interface I_Command.

Sample 3 (TwinCAT 3.1.4020) on GitHub

Logging of commands

Since each command FB implements the interface I_Command, each command can be stored in a variable of type I_Command. For example, the undo functionality would make use of it. If this variable is replaced with a buffer, we get an opportunity to log the commands. The analysis and the diagnosis of an equipment can be facilitated in this way.

Summary

The central idea of the command pattern is decoupling of the invoker and the receiver by means of command objects.

•    A developer can add new command FBs without adapting the code of the invoker (8-button keypad).
•    The assignment of the commands to the invoker can be performed dynamically during runtime.
•    Command FBs can be reused at various points. Thus, code redundancy is prevented.
•    Commands can be made “intelligent”. In this way, macro commands and undo commands can be implemented.

Author: Stefan Henneken

I’m Stefan Henneken, a software developer based in Germany. This blog is just a collection of various articles I want to share, mostly related to Software Development.

10 thoughts on “IEC 61131-3: The Command Pattern”

  1. Hey,
    thanks a lot for this blog, or rather tutorial – I found it really challenging 🙂 – OOP is not my daily job, so it would take me much, much more time to develop such a constructions than write it in old fashion way. Anyway, your solution is in the end of the day much more readable and with great possibility od simple and error free changes.

    I just wonder how efficient will be the final machine code – if you are doing whole PLC project in this way… can you somehow compare it? Especially in Codesys world? (There is a lot of C and C++ benchmarks in the net but codesys is something quite different – in codesys is almost no optimization at all as far as I know…)

    Thanks

    Jan

    1. Hello Jan,

      I have just finished my Master Thesis in this topic (Design patterns for PLC based systems using CODESYS).

      As far as I can tell, it just deppends on what efficient means to you or what the machine code is going to be used for. For example, if you are a machine builder, you want the code to be as simple as possible because your machine can handle a finite number of variables or products; but if you are an automation developer, you migth need to think about moving forward to OOP and othe complex concepts.

      IoT and/or Industrie 4.0 will be the driving force in the next 10-15 years and a “Top-Down” program is going to be harder to implement under these concepts. You will have to handle way more states on your state machine diagram and the developing cost will increase and constat updates will be required, that would also translate to cost.

      Even if you are a machine builder you will have to implement IoT/Industrie 4.0 in the near future because EVERYTHING will have to be compatible.

      If you want more examples like this (Not as detailed and accurate as these ones in Stefan Henneken’s Blog) I could send you a copy of my thesis. I cover 5 different patterns; Builder, Decorator, Observer, Proxy and Singleton design patterns.

      Have a nice day!

      Armando.

      1. Hi Armando, I’m working on a new plc plattform where I will use the advanced of OOP. Is it possible to get the mentioned pattern-examples?

        Thx in advance.

        Raphael

  2. Sometimes its possible to take OOP a bit too far and this is one example. Creating a whole Fb just for the purposes of sending a command seems a bit overkill, not to mention that you need to initialize 2 FBs and a reference.

    Instead of having the FB_SwitchPanel triggering the commands internally, it is possible to output the button press as a request through a property, with which you can create a CASE statement and have the Device FBs be used as initially designed (via method calls). Might not be very OOP, but simplicity is key.

    1. Hi Mike,
      Basically, I agree with you.
      It is not always necessary and useful to map a command by a FB. However, there are use cases where this is a very intersting solution. E.g. if there is a requirement that the assignment between sensors and actuators should change at runtime, it is not always possible to call methods directly. But I agree with you, the command pattern is not always necessary. This also applies to all other design patterns. Design patterns should always be understood as a proposal for a solution, not as a rule. Also can adapt each design pattern to your own needs.
      Regards
      Stefan

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: