OOP in TwinCAT3 – Command Pattern

Last updated 23/05/2020

A previous post discussed the benefits of using the command pattern to provide a service class with a safe and convenient way to allow other objects to invoke and monitor a sequence of operations (a service). This post describes an implementation of the command pattern that uses a class (TExecution) that implements an interface (IExecution). The source code for an example project can be found here. The Execution classes and interfaces are defined in the library tcl_BaseClasses that are part of the project.

The TExecution class keeps track of the execution state of the service, and has methods to update the execution state and properties to show the current execution state in a convenient form. An instance of TExecution is declared as an instance variable of the service class, and an interface pointer of type IExecution is made available to the client code via a property

The execution state is held in the TExecution instance by an enumerated value (TExecutionState), defined as follows:

  • Ready indicates the service is not currently executing.
  • Starting indicates that there has been an external request to start the service, but the service has not yet been initiated.
  • Busy indicates that the service is currently executing.
  • Aborting indicates that there has been an external request to cancel the current service, which should be handled internally to cleanup and return to a ready state.

The state machine is shown below. States are shown in green, methods called by the client via the IExecution interface are shown in red and methods called by the service implementation are shown in blue:

IExecution Methods

The IExecution interface is the interface provided to the client code to allow it to start and abort the service, and to clear the results.

The following methods are implemented:

Start()

If the current state is Ready, the result flags are cleared and the current state is set to Starting to request execution of the service. True is returned to signal that the start request was accepted.

If the current state is not Ready, an event is raised to provide a notification that the state was not the expected state, and False is returned (see later for a description of the error and aborted events)

Abort()

If the current state is not Ready, then the current state is set to Aborting. Otherwise, the abort is silently ignored.

This method provides the calling object with a way to interupt execution of the current operation and return it to a ready state. For this to work, the implementing class must detect the Aborting state and cancel its current operation. Once cancelled, it signals the completion of the abort using the SetAborted method which returns the current state to Ready

If the current state is Busy, an event is raised to provide a notification that the service was aborted (see later for a description of the error and aborted events)

Clear()

The Clear method can be used to clear result flags, provided the current state is Ready. If the current state is not Ready, an event is raised to provide a notification that the state was not the expected state (see later for a description of the error and aborted events)

Methods not belonging to IExecution

The following methods are provided to allow the service class to update the current execution state. They are excluded from the IExecution interface as the state transitions they trigger need to be restricted to the service class implementation:

  • SetBusy() – If the execution state is Starting, the Busy state is selected to indicate that the operation has started
  • SetDone() – Sets the Done property and selects the Ready state to indicate that the operation has completed succesfully
  • SetError(ErrorId) – Sets the Error and ErrorId properties and selects the Ready state to indicate that the operation failed to complete sucessfully. An event is raised to provide a notification that an error ocurred.
  • SetAborted – Sets the Aborted property to indicate that the operation was aborted and selects the Ready state. An event is raised to provide a notification that the current service was aborted.

The following read-only properties are defined:

  • Ready (BOOL)- current state is Ready
  • Busy (BOOL) – current state is Busy
  • Done (BOOL) – last opertion completed successfully
  • Error (BOOL) – last operation terminated with an error
  • ErrorId (UDINT) – error Id of last error
  • Aborted (BOOL) – last operation was aborted
  • Event_OnAbort – Interface pointer to the OnAbort event. This can be set to zero to inhibit the instance from raising the OnAbort event
  • Event_OnError – Interface pointer to the OnError event. This can be set to zero to inhibit the instance from raising the OnError event

The IExecution interface that is available to the client class is exposed via a read-only property:

Because interfaces are reference types, we can access methods and properties directly on the interface property. i.e. External code can initiate the operation by calling Start() via the IExecution interface, and check the progress of the operation by querying the state of the Busy, Done and Error properties.

Events

Every instance of TExecution has the ability to raise an event when its SetError or SetAborted method is executed, or when the Start method is executed while the execution state is not ready. This is very useful when testing the software on a machine. See this post for an explanation of the event framework.

The library that implement TExecution has a global variable list called ExecutionEvents. This holds a pair of events – Event_OnError and Event_OnAbort:

Each instance of TExecution holds references to these event instances, allowing them to raise the events as necessary. These reference is intialised in the FB_Init method of TExecution (and can be zeroed via the associated property to inhibit the event from being raised).

These events can be subscribed to by any external class that implements the following interfaces:

This only requires that the class has methods called OnExecutionFailed and OnExecutionAborted respectively. These methods would typically write a message to an event log or send a message to the error list in Visual Studio. For example, a TLogging class that implements the event handlers might look like this:

The Logging object is linked to the execution events as follows:

So with a few lines of code, all execution errors and aborts can be directed to a single destination for analysis.

OOP in TwinCAT3 – Events Framework

Last updated 23/05/2020

This blog describes an event framework that implements domain events, first discussed in this post.

The example code shows the creation of events TEvent_OnEStopTripped and TEvent_OnEStopReset, handled by class implementing the interfaces IEventHandler_OnEStopTripped and IEventHandler_OnEStopReset. The source code for an example project can be found here. The base classes and interfaces are defined in the library tcl_BaseClasses that are part of the project.

Domain Events

The event mechanism we are discussing is a simple implementation of Domain Events. It allows the consistent synchronous update of the state of a program based on things that have happened, and is based on the publisher/subscriber model.

Events and Handlers

Each event class defines an event of a particular type, and has an associated event handler interface definition. This interface defines the name of the method to be executed when the event is raised.

If a class is to handle a specific event, it must implement the event’s associated event handler interface (i.e it must implement a method with the correct name and signiture).

During an initialisation phase, any objects which must receive a notification of an event are added to the event’s list of subscribers.

When the event is raised in the course of the program’s execution, the event object iterates through its list of subscribers and calls the event handler method of each in turn. This means that the event handling code in the subscribing objects is executed there and then (Note1)

The publishing class knows nothing of the subscribing class – the only thing that links them is the event handler interface. They could each be defined in separate projects, with the event and event handler defined in a third project..

Defining an Event handler

An event handler interface is defined by creating an interface that extends a base interface IEventHandlerBase, and adding a method with the required name and signature. For example, to implement an EStopTripped and an EStopReset event we need to define two event handler interfaces that extend IEventHandlerBase, each with one method to be executed when the event is raised:

In the example project, the events are to be handled here by a class TMachine, so this class must implement the event handler interfaces:

In the example project, the actual event handler methods implemented by TMachine just set or reset a boolean variable:

Defining an Event class

Event classes are defined by creating classes that extend a base class, with an instance variable that can hold a reference to the appropriate event handler. For example:

Each event class needs to override two abstract base class methods.

The IsHanderSupported method is used by the base class’s AddHandler method to check that the object passed to the method implements the required event handler interface for the event. This method is always as shown below – there is no variation between event classes:

The CallHandler method is used by the base class’s Raise method to call the event handler method implemented by the event handler. It casts the interface pointer passed in to the actual event handler interface and calls the event handler method. The EventArgs parameter is used if event data is to be passed to the handler method – this is explained in a later section. This method will be slightly different for each event class – the event handler method name will be that defined in the event handler interface.

Linking Event Handlers to Events

All that remains is to link the objects that must handle the events with the event instances. The simplest arrangement is to have a global list of events and a global list of objects:

Each event holds a list of registered event handlers that are called when the event is raised. The event base class has a method call ClearEventHandlers() that clears the list and a method call AddEventHandler() that adds an event handler to the list.

Note: These methods have a return type IEvent (which defines the ClearEventHandlers and AddEventHandler methods) so that we can chain method calls to improve the readability of the application code. Here adding event handlers to the defined events looks like this:

Raising Events

If the event instances are global, the events can be raised from any module:

The above code will result in the local EnablePower variable in Objects.Machine being set and reset as the state of the EStopHealthy input changes.

The events could also be declared as instance variables in the TSafety module:

The code that links the event handlers can reference the local instances directly (Note2):

Event Data

The event base class has an EventArgs object which can be used to pass event data from the object that is raising the event to the objects that are handling the event. Internally, the EventArgs is implemented as a resizable memory stream.

Event data is written to the EventArgs object using the following methods of the IStreamWriter interface implemented by the EventArg object:

  • Add_BOOL()
  • Add_BYTE()
  • Add_INT()
  • Add_UINT()
  • Add_DINT()
  • Add_UDINT()
  • Add_LINT()
  • Add_ULINT()
  • Add_REAL()
  • Add_LREAL()
  • Add_STRING()
  • Add_PVOID()

Each of these methods returns an IStreamWriter interface pointer, so if multiple arguments are to be added, the method calls can be chained. Arguments can be added in any order provided they are retrieved in the same order in the event handler.

So raising an event with event data might look like this:

The CallHandler method of the event passes an interface pointer that points to the EventArgs object to the event handler method:

The event handler method uses this interface pointer to retreive the event data. Note that if multiple data items are passed in the EventArgs object, they must be retreived in the same order as they were added when the event was raised:

Summary

Events and handlers are easy to create, and the benefits of decoupling events from their effects can be enormous for large projects. Additionally, it opens the possibility of writing library classes that raise custom events that can be handled in client code.

Notes
  1. There is no synchronisation between tasks here. If an object running in Task 1 raises an event on an object running in Task 2, there are no built in checks to prevent inconsistent data resulting from concurrent access. This must be done in the event handler using FB_IecCriticalSection or similar.[]
  2. The local instances of events should not be accessible to the code that adds the event handlers (as they should be private). However, TwinCAT does allow this and it works (but the events do not appear in the intellisense menu unless it has been configured in TwinCAT Options to show all variables). If this offends you, you can add a public property of type IEvent that points to the local event instance, and use this in the code that adds the handlers. This is shown in the example project[]

OO Architecture in TwinCAT3

Last updated 09/06/2020

This article is intended as guide to the adoption of object-oriented (OO) techniques in PLC programs written using structured text using  TwinCAT3.

An example project showing some of these ideas in practice can be downloaded here

Background

TwinCAT is a programming tool provided by Beckhoff for programming their range of PC based controllers. TwinCAT3 is the latest version and supports 3 programming styles:

  1. Procedural
  2. A mix of procedural and object-oriented programming
  3. Purely object oriented programming

Many PLC programs are difficult to understand and difficult to modify due to a high degree of coupling between modules and to informal communication paths between modules. This can be a result of the conflicting requirements of:

  • The high degree of interaction between different parts of a machine
  • The need to minimise coupling and dependancies between different parts of the program.

An object oriented approach offers a way of reconciling these requirements by allowing POUs to encapsulate behaviour and to exchange messages and in a consistent and well defined way.

However, when I first tried writing a program in a fully object-oriented style, I found it was not straight-forward. Creating the classes was easy – getting them to collaborate was hard. I think this is a natural consequence of the properties of objects that make them so useful – encapsulation.

This article will attempt to explain some of the techniques I have found minimise these difficulties.

Procedural Code

A procedural program will generally comprise the following

  • Programs – top level POUs that access global variables and call function blocks
  • Function blocks – POUs that accept inputs and drive outputs that are assigned when the function block is called. Function blocks may in turn contain function blocks and functions

The arrangement of function block calls and the messaging between function blocks is fixed by the program which has the advantage of being simple but the disadvantage of being inflexible. It works but we can do better.

Procedural/OO Mix

A half-way house to moving to a fully OO architecture is to use a mix of procedural and OO styles. The major building blocks of the program are still implemented as function blocks called by programs, but the smaller function blocks that provide services are implemented as classes. This can improve the readability of code by providing a fluent interface (Here is an interesting post on fluent interfaces in TwinCAT3 written by Gerhard Barteling).

For example, the standard rising edge trigger function block can be used as follows:

We can make this slightly easier to read by wrapping the R_TRIG in a class:

The TRisingEdge POU is a function block with a single method (Test):

This can be a useful way to introduce classes of gradually increasing complexity to a procedural program to get a feel for how objects can be used to present an intuitive interface to the client code, and hide the implementation details.

Fully OO Code

A fully OO program is one where all function blocks are implemented as classes – i.e they use properties and methods to provide interfaces to other objects. Classes are instantiated as objects that interact with each other to provide the required functionality.

So what are the guidelines that we can use to identify the classes we need? The best way that I have found is to think of a class as something with a single responsibility (or a small collection of closely related responsibilities). This quite often turns out to be the noun-based classes that are the traditional candidates for classes but it can also accommodate more abstract responsibilities such as parts tracking or alarm handling.

Provided these classes are kept small, implementing them can be fairly straightforward, as all the required information is held by the class in a well defined context. The class defines the operations required to handle it’s responsibility.

The class may carry out this operation during its cyclic execution, or it may allow other objects to invoke the operation as a service via a public interface.

The problem to solve is how to arrange the instances of these classes so that they can safely invoke each other’s services.

The Problems Of Ownership

The simplest case is where the client object (object requesting an operation be performed) owns a service object (object implementing the operation to be performed). In the rising edge trigger example shown earlier, the client object (RisingEdgeExample) asks the service object (StartPbRisingEdge : TRisingEdge) to use its Test methods to detect a rising edge and return true when one is detected. This works a treat, so why not use this all the time?

  • It only works if the client object is the sole user of the service object. This is generally the case for small classes such as timers and edge triggers, but not for larger classes that other clients may want to access.
  • If the service object is complex, it will need to hold its own service objects. This can result in a deep heirachy of objects, which should be avoided as it is difficult to change and difficult to navigate.

A better approach is to avoid nesting complex classes. Instead, assume all complex objects may need to be accessed by other objects, and instantiate them in a global object list. Provided the interface is carefully designed to ensure it is safe to use and cannot lead to an invalid internal state, there should be no problem making such an object globally accessible.

Referencing Shared Objects

If we have a global object list, any object can reference an other object by using its fully qualified name. Whilst this is reasonable for small programs, it should be avoided because:

  • A dependancy is created between the classes which makes the program harder to understand and change.
  • An instance of an object may not know which service object it is interested in where multiple instances of the service exist (such as where there are repeated machine elements, for example)

The alternative is to pass in a refence to the service object when the client object is called, either as a reference (using VAR_IN_OUT), or as an interface pointer (using VAR_INPUT) – also known as dependancy inversion.

Dependancy Inversion

Here, the VAR_INPUT parameter type is an interface. The service object must implement this interface so it can be implicitly converted when it is passed to the client during the call, and the interface must define the properties and methods to be used by the client so the client can use this interface pointer as if it were the service object itself

Thus, only the parts of the server object that are interesting to the client need be passed in. A benefit of this is that to test the client, a dummy class that implements the interface (a test double) can be passed in to remove the dependancy during testing.

Invoking Asynchronous Operations

Where a client wants to invoke an asynchronous operation on a service object (i.e one taking multiple scans with the possibility of the operation failing), a single method is not sufficient – one method is required to invoke the operation and another to query the current state of the operation. A third may be required to abort the operation under certain conditions (E-Stop, for example). In order to tie these methods together, an abstraction can be used that follows the command pattern.

Command Pattern

This pattern uses a command object to represent the the invocation of an asynchronous command. The command object is an instance variable of the service object. It implements an interface that is made public via a property of the service object that allows a client to start and abort the operation, and query the state of the operation.

The command interface has the following methods and properties:

  • Start() – A method to start the execution of the command
  • Abort() – A methods to abort the execution of the command
  • Clear() – A method to clear the results of the command
  • State – indicates the current state of the command.
  • Done – indicates that the last execution completed succesfully
  • Error – indicates that the last execution failed to complete successfully
  • ErrorId – error code relating to the reason the last execution failed to complete successfully

The possible states are:

  • Ready – not currently executing
  • Starting – a start command has been accepted, but execution has not begun
  • Busy – currently executing
  • Aborting – an abort command has been recieved so the command is terminating to leave the service object in a ready state

The command object implements the following methods that are used by the service object to update the State property

  • SetBusy()
  • SetDone()
  • SetError(ErrorId)
  • SetAborted()

Command Usage

The service object exposes the command interface as a property (Command). The client object holds a reference to this command interface (CommandInterface)

  1. The client object invokes the operation by calling CommandInterface.Start(), which returns true if the start was accepted (i.e the state was ready)
  2. The service object detects that the command state is starting and initiates the operation, calling Command.SetBusy() to set the state to busy
  3. If the operation completes sucessfully, the service object calls Command.SetDone(), which sets Done property and sets the state back to ready
  4. If the operation fails, the service object calls Command.SetError(ErrorId), which sets the Error and ErrorId properties, and sets the state back to ready
  5. The client object monitors the command interface properties to determine the result of the operation, and act accordingly.

Command Benefits

The command encapsulates the interaction between the client object and the service object, reducing duplicated code by defining the ineraction once.

It creates a standard pattern across the program for how clients invoke operations on services, so a sequence of operations can be implemented by a state machine that sequentially calls Start() and checks Done or Error on a series of command interfaces

It supports both an ‘execute with feedback’ and a ‘fire and forget’ approach to triggering an operation. The former is useful in sequences where progress to the next step requires confirmation that the operation has completed. The latter is useful in response to manual commands where it is reasonable to ignore the command cannot be executed.

The client and the service become decoupled – the client no longer cares about the type of object that the service is, only that it has an interface that controls the invocation of the operation.

A description of an implementation of this pattern can be found here.

Further Decoupling – Domain Events

Domain events are an implementation of the observer pattern that allows an object to publish the fact that something has occurred to other interested subscriber objects (whose types are unknown to the publisher).

A domain event holds a list of objects that are to be notified when the event is raised. It has the following methods for updating this list:

  • ClearEventHandlers()
  • AddEventHandler()

The domain event has methods that allows the client to add event data and to raise the event:

  • EventArgs.AddInt()
  • EventArjs.AddString()
  • EventArgs.AddLreal() etc
  • Raise()

The raise method causes a method in each registered subscriber to be executed, with the EventArgs passed in for evaluation by this event handler method.

The main benefits are:

  • The removal of dependancies between the publisher and the subscriber.
  • The synchronous nature of the update – all subscribes are updated at the same time.
  • The ability to have many objects react to a single event in a one to many relationship
  • The ability to have many objects raise a single event in a many to one relationship
  • The cognitive decoupling that it allows between thinking about the event being raised and the event being handled.

A description of an implementation of this pattern can be found here.

Summary

The resulting architecture is based on a global list of small objects that interact using dependancy inversion, commands and events. Where the objects’ relationship is one-to-one, dependancy inversion or the command pattern are the best ways for them to communicate as this is easier to follow and understand than an event mechanism. Events are appropriate for one to many relationships (such as manual control events that must be handled by multiple objects) and many to one relationships (such as where many instances of the same class can raise the an event that must be handled by a single object.