OOP in TwinCAT3 – Asynchronous Notifications

Notes:

This blog post is a follow-on to a previous post on implementing domain events (found here). I have replace all references to “events” with “notifications” to avoid confusion with the TwinCAT Eventlogger3 events.

The classes described can be found in the open source library “tcl_BaseClasses” at
https://github.com/RedRockControls/tcl_BaseClasses

This repository also contains an example project.

Introduction

The previous post described a synchronous mechanism for sending notifications from one POU to another. The synchronous behavior means that when a notification is raised during the execution of the POU, the code in the notification handlers of the client POUs executes there and then, in the context of the current POU (i.e in the same task).

Where all POUs share the same context (i.e are called by the same task), there is no risk of inconsistency in the state of the client POUs, as there is no concurrent access. However, if we need to use notifications to pass messages between POUs executed by different task, there is a risk of data inconsistency caused by this concurrent access. The mechanism described below provides a way of removing this risk by enabling a notification raised in one context to be handled in another.

As a side benefit, this solution is much simpler to use. It only requires a single class for notifications and a single class for notification handlers: no sub-classing is required to define different types of notification, just separate instances of these classes.

Recap – Domain Events

The notification mechanism we are discussing is an implementation of Domain Events. It allows the consistent update of POUs of a program based on things that have happened in other unrelated POUs.

The mechanism is based on the publisher/subscriber model:

  • Subscribers register with a publisher.
  • The publisher raises a notification when something interesting happens.
  • Each subscriber receives the notifications, and updates accordingly.

The main benefits are:
1. It allows decoupling of classes as the publisher class needs to know nothing about the subscriber classes.
2. It allows one-to-many communication as multiple client objects can subscribe to a single publishing object.
3. It allow many-to-one communication as a single client object can subscribe to the same notification raised by multiple publisher objects.
4. It allows many-to-many communication – for example many objects can receive a Reset message that is raised by multiple sources (different controls stations or HMIs, etc)

Overview – A Simple Example

The notification represents something interesting happening. Here, a notification is used to indicate that a radioactive nucleus has decayed:

This is handled by a Geiger counter to count the decays:

The notification is an instance of T_Notification. Here it is declared in a global list, but it could be a local variable of the publishing class:

Instances of the publisher and subscriber classes are declared in a global object list:

And the notification handler is linked to the notification during initialisation:

The result being that when the atom decays, the Geiger counter increments.

Notifications

A notification is an instance of the T_Notification class.

Each notification holds a list of notification handlers. Notification handlers are added to this list during program initialization.

Notifications can be declared either as local variables of a publishing object, or in a global list of notifications. A global list of notifications produces a more readable program, at the expense of making POUs harder to test (as it introduces a dependency on global variables).

Notifications have an arguments property that can be used to attach values to the notification by calling methods such as Add_Byte, Add_UDINT, etc. before raising the notification.

The notification’s Raise method iterates through the list of handlers, copying the arguments and calling the raise method on each handler.

Notification Handlers

A notification handler is an instance of the T_NotificationHandler class, declared as a local variable of a subscribing object.

Notification handlers also have an arguments property that is used by the subscriber to read any values that were attached to the notification by calling methods such as read_Byte, Read_UDINT, etc.

Notification handlers implement an IsRaised method. The subscribing object checks the state of the notification by calling this method and checking its return value – if true, it can then query the notification arguments and update its state.

Synchronisation between tasks

The notification object is only accessed by the publishing object, so there is no concurrent access to the notification.

The notification handler object is accessed by the notification object and by the subscribing object, so access to local variables must be synchronized, here using an instance of the FB_IecCriticalSection function block as follows:

When the notification is raised, the RaiseNotification method calls the Enter method of the critical section object. It then writes the notification arguments (if any) to a private copy and sets the Raised state of the notification handler. It then calls the Leave method of the critical section object

While the notification is handled, the IsRaised method calls the Enter method of the critical section object. It then checks the Raised state of the notification handler. If it is raised, it writes the notification arguments from the private copy to a public copy that can be accessed by the subscriber object. In either case, it then clears the raised state of the notification handler.

Notification Arguments

Notification arguments are stored using instances of a memory stream class T_Stream. This class has methods to write data to the stream sequentially (Add_BYTE, Add_UDINT, etc), and to read data from the stream sequentially (Read_BYTE, Read_UDINT, etc). The T_Stream class is initialized with a default length (8 bytes), but will automatically resize as data is added.

Here we modify the previous example by adding a string value and a LREAL value as notification arguments:

Additionally, a pair of methods (SetNextArgTypeId and GetNextArgTypeId) are implements so the type of each argument can be written to the stream before writing it, and read from the stream before reading it to ensure the type read by the subscriber matches the type written by the publisher:

Summary

Notifications and handlers are easy to create, and the benefits of decoupling events from their effects can be significant for large projects.

Additionally, they open up the possibility of writing library classes that raise custom notifications that can be handled in client code without introducing dependencies between the library code and the client code.

One further benefit of this design is that it provides a convenient way of exchanging data between tasks, as it takes care of the data synchronization as part of its implementation.

Last updated 17/08/2021

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.

OOP in TwinCAT3 – Events Framework

Last updated 28/09/2021

This blog describes an event framework that implements domain events.

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 base classes and interfaces are defined in the library tcl_BaseClasses, and the open-source project for this library can be found at https://github.com/RedRockControls/tcl_BaseClasses

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[]

OOP in TwinCAT3 – Command Pattern

Last updated 27/09/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 (T_Execution) that implements an interface (I_Execution). The source code for an example project can be found here. The Execution classes and interfaces are defined in the library tcl_BaseClasses. The source code for the library can be found on git hub here

The T_Execution 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 T_Execution is declared as an instance variable of the service class, and an interface pointer of type I_Execution is made available to the client code via a property

The execution state is held in the T_Execution instance by an enumerated value (T_ExecutionState), 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 I_Execution interface are shown in red and methods called by the service implementation are shown in blue:

I_Execution Methods

The I_Execution 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 interrupt 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 I_Execution

The following methods are provided to allow the service class to update the current execution state. They are excluded from the I_Execution 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 successfully
  • SetError(ErrorMsg) – Sets the Error and ErrorMsg properties and selects the Ready state to indicate that the operation failed to complete successfully. An event is raised to provide a notification that an error occurred.
  • 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 operation completed successfully
  • Error (BOOL) – last operation terminated with an error
  • ErrorMsg (STRING) – error message describing the last error
  • Aborted (BOOL) – last operation was aborted
  • Notification_OnAbort – Interface pointer to the OnAbort notification. By default, this points to a global notification object in the GVL GlobalEvents, but can be set to zero to inhibit the instance from raising the OnAbort notification
  • Notification_OnError – Interface pointer to the OnError notification. By default, this points to a global notification object in the GVL GlobalEvents, but can be set to zero to inhibit the instance from raising the OnError notification

The I_Execution 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 I_Execution interface, and check the progress of the operation by querying the state of the Busy, Done and Error properties.

Notifications

Every instance of T_Execution has the ability to raise a notification 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 notification framework.

The library that implement T_Execution has a global variable list called GlobalEvents. This holds a pair of notifications- Notification_OnError and Notification _OnAbort:

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

These notifications can be handled by instances of T_NotificationHandler in a Logger class which can then be used to send error messages, etc:

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.

Summary

The T_Execution class can offer a consistent and safe way for classes to provide an API to the operations they implement. It can track the state of an operation, provide a mechanism for aborting the operation and returning the class to a known state, and can provide diagnostics across the entire program to track errors in the execution of operations.