Home > IEC 61131-3 (english) > IEC 61131-3: The ‘Observer’ Pattern

IEC 61131-3: The ‘Observer’ Pattern

The Observer Pattern is suitable for applications that require one or more function blocks to be notified when the state of a particular function block changes. The assignment of the communication participants can be changed at runtime of the program.

In almost every IEC 61131-3 program, function blocks exchange states with each other. In the simplest case, one input of one FB is assigned the output of another FB.

Pic01

This makes it very easy to exchange states between function blocks. But this simplicity has its price:

Inflexibility. The assignment between fbSensor and the three instances of FB_Actuator is hard-coded in the program. Dynamic assignment between the FBs during runtime is not possible.

Fixed dependencies. The data type of the output variable of FB_Sensor must be compatible to the input variable of FB_Actuator. If there is a new sensor component whose output variable is incompatible with the previous data type, this necessarily results in an adjustment of the data type of the actuators.

Problem Definition

The following example shows how, with the help of the observer pattern, the fixed assignment between the communication participants can be dispensed with. The sensor reads a measured value (e.g. a temperature) from a data source, while the actuator performs actions depending on a measured value (e.g. temperature control). The communication between the participants should be changeable. If these disadvantages are to be eliminated, two basic OO design patterns are helpful:

  • Identify those areas that remain constant and separate them from those that change.
  • Never program directly to implementations, but always to interfaces. The assignment between input and output variables must therefore no longer be permanently implemented.

    This can be realized elegantly with the help of interfaces that define the communication between the FBs. There is no longer a fixed assignment of input and output variables. This results in a loose coupling between the participants. Software design based on loose coupling makes it possible to build flexible software systems that cope better with changes, since dependencies between the participants are minimized.

    Definition of Observer Pattern

    The observer pattern provides an efficient communication mechanism between several participants, whereby one or more participants depend on the state of one participant. The participant providing a state is called Subject (FB_Sensor). The participants, which depend on the state, are called Observer (FB_Actuator).

    The Observer pattern is often compared to a newspaper subscription service. The publisher is the subject, while the subscribers are the observers. The subscriber must register with the publisher. When registering, you may also specify which information you would like to receive. The publisher maintains a list in which all subscribers are stored. As soon as a new publication is available, the publisher sends the desired information to all subscribers in the list.

    This becomes more formal in the book „Design pattern. Elements of reusable object-oriented software” expressed by Gamma, Helm, Johnson and Vlissides:

    The Observer pattern defines a 1-to-n dependency between objects, so that changing the state of an object causes all dependent objects to be notified and automatically updated.

    Implementation

    In which way the subject receives the data and how the observer processes the data is not discussed here in more detail.

    Observer

    The method Update() notifies the observer of the subject, if the value changes. Since this behaviour is the same for all observers, the interface I_Observer is defined, which is implemented by all observers.

    The function block FB_Observer also defines a property that returns the current actual value.

    Pic02 Pic03

    Since the data is exchanged by method, no further inputs or outputs are required.

    FUNCTION_BLOCK PUBLIC FB_Observer IMPLEMENTS I_Observer
    VAR
      fValue : LREAL;
    END_VAR
    

    Here is the implementation of the method Update():

    METHOD PUBLIC Update
    VAR_INPUT
      fValue : LREAL;
    END_VAR
    THIS^.fValue := fValue;
    

    und das Property fActualValue:

    PROPERTY PUBLIC fActualValue : LREAL
    fActualValue := THIS^.fValue;
    

    Subject

    The subject manages a list of observers. Using the methods Attach() and Detach(), the individual Observers can log on and off.

    Pic04 Pic05

    Since all Observers implement the interface I_Observer, the list is of type ARRAY[1..Param.cMaxObservers] OF I_Observer. The exact implementation of the observer does not have to be known at this point. Further variants of observers can be created, as long as they implement the interface I_Observer, the subject can communicate with them.

    The method Attach() contains the interface pointer to the observer as a parameter. Before it is stored in the list (line 23), the system checks whether it is valid and not already contained in the list.

    METHOD PUBLIC Attach : BOOL
    VAR_INPUT
      ipObserver            : I_Observer;
    END_VAR
    VAR
      nIndex                : INT := 0;
    END_VAR
    
    Attach := FALSE;
    IF (ipObserver = 0) THEN
      RETURN;
    END_IF
    // is the observer already registered?
    FOR nIndex := 1 TO Param.cMaxObservers DO
      IF (THIS^.aObservers[nIndex] = ipObserver) THEN
        RETURN;
      END_IF
    END_FOR
    
    // save the observer object into the array of observers and send the actual value
    FOR nIndex := 1 TO Param.cMaxObservers DO
      IF (THIS^.aObservers[nIndex] = 0) THEN
        THIS^.aObservers[nIndex] := ipObserver;
        THIS^.aObservers[nIndex].Update(THIS^.fValue);
        Attach := TRUE;
        EXIT;
      END_IF
    END_FOR
    

    The method Detach() also contains the interface pointer to the Observer as a parameter. If the interface pointer is valid, the Observer is searched in the list and the corresponding position is deleted (line 15).

    METHOD PUBLIC Detach : BOOL
    VAR_INPUT
      ipObserver             : I_Observer;
    END_VAR
    VAR
      nIndex                 : INT := 0;
    END_VAR
    
    Detach := FALSE;
    IF (ipObserver = 0) THEN
      RETURN;
    END_IF
    FOR nIndex := 1 TO Param.cMaxObservers DO
      IF (THIS^.aObservers[nIndex] = ipObserver) THEN
        THIS^.aObservers[nIndex] := 0;
        Detach := TRUE;
      END_IF
    END_FOR
    

    If there is a status change in the subject, the method Update() is called by all valid interface pointers in the list (line 8). This functionality is found in the private method Notify().

    METHOD PRIVATE Notify
    VAR
      nIndex : INT := 0;
    END_VAR
    
    FOR nIndex := 1 TO Param.cMaxObservers DO
      IF (THIS^.aObservers[nIndex] 0) THEN
        THIS^.aObservers[nIndex].Update(THIS^.fActualValue);
      END_IF
    END_FOR
    

    In this example, the subject generates a random value every second and then notifies the observer using the Notify() method.

    FUNCTION_BLOCK PUBLIC FB_Subject IMPLEMENTS I_Subject
    VAR
      fbDelay : TON;
      fbDrand : DRAND;
      fValue : LREAL;
      aObservers : ARRAY [1..Param.cMaxObservers] OF I_Observer;
    END_VAR
    
    // creates every sec a random value and invoke the update method
    fbDelay(IN := TRUE, PT := T#1S);
    IF (fbDelay.Q) THEN
      fbDelay(IN := FALSE);
      fbDrand(SEED := 0);
      fValue := fbDrand.Num * 1234.5;
      Notify();
    END_IF
    

    There is no statement in the subject to access FB_Observer directly. Access always takes place indirectly via the interface I_Observer. An application can be extended with any observer. As long as it implements the interface I_Observer, no adjustments to the subject are necessary.

    Pic06 

    Application

    The following module should help to test the example program. A subject and two observers are created in it. By setting appropriate auxiliary variables, the two observers can be both connected to the subject and disconnected again at runtime.

    PROGRAM MAIN
    VAR
      fbSubject         : FB_Subject;
      fbObserver1       : FB_Observer;
      fbObserver2       : FB_Observer;
      bAttachObserver1  : BOOL;
      bAttachObserver2  : BOOL;
      bDetachObserver1  : BOOL;
      bDetachObserver2  : BOOL;
    END_VAR
    
    fbSubject();
    
    IF (bAttachObserver1) THEN
      fbSubject.Attach(fbObserver1);
      bAttachObserver1 := FALSE;
    END_IF
    IF (bAttachObserver2) THEN
      fbSubject.Attach(fbObserver2);
      bAttachObserver2 := FALSE;
    END_IF
    IF (bDetachObserver1) THEN
      fbSubject.Detach(fbObserver1);
      bDetachObserver1 := FALSE;
    END_IF
    IF (bDetachObserver2) THEN
      fbSubject.Detach(fbObserver2);
      bDetachObserver2 := FALSE;
    END_IF
    

    Sample 1 (TwinCAT 3.1.4022) on GitHub

    Improvements

    Subject: Interface or base class?

    The necessity of the interface I_Observer is obvious in this implementation. Access to an observer is decoupled from implementation by the interface.

    However, the interface I_Subject does not appear necessary here. And in fact, the interface I_Subject could be omitted. However, I have planned it anyway, because it keeps the option open to create special variants of FB_Subject. For example, there might be a function block that does not organize the observer list in an array. The methods for logging on and off the different Observers could then be accessed generically using the interface I_Subject.

    The disadvantage of the interface, however, is that the code for logging in and out must be implemented each time, even if the application does not require it. Instead, a base class (FB_SubjectBase) seems to be more useful for the subject. The management code for the methods Attach() and Detach() could be moved to this base class. If it is necessary to create a special subject (FB_SubjectNew), it can be inherited from this base class (FB_SubjectBase).

    But what if this special function block (FB_SubjectNew) already inherits from another base class (FB_Base)? Multiple inheritance is not possible (however, several interfaces can be implemented).

    Here, it makes sense to embed the base class in the new function block, i.e. to create a local instance of FB_SubjectBase.

    FUNCTION_BLOCK PUBLIC FB_SubjectNew EXTENDS FB_Base IMPLEMENTS I_Subject
    VAR
      fValue               : LREAL;
      fbSubjectBase        : FB_SubjectBase;
    END_VAR
    

    The methods Attach() and Detach() can then access this local instance.

    Method Attach():

    METHOD PUBLIC Attach : BOOL
    VAR_INPUT
      ipObserver : I_Observer;
    END_VAR
    
    Attach := FALSE;
    IF (THIS^.fbSubjectBase.Attach(ipObserver)) THEN
      ipObserver.Update(THIS^.fValue);
      Attach := TRUE;
    END_IF
    

    Method Detach():

    METHOD PUBLIC Detach : BOOL
    VAR_INPUT
      ipObserver : I_Observer;
    END_VAR
    Detach := THIS^.fbSubjectBase.Detach(ipObserver);
    

    Method Notify():

    METHOD PRIVATE Notify
    VAR
      nIndex : INT := 0;
    END_VAR
    
    FOR nIndex := 1 TO Param.cMaxObservers DO
      IF (THIS^.fbSubjectBase.aObservers[nIndex] 0) THEN
        THIS^.fbSubjectBase.aObservers[nIndex].Update(THIS^.fActualValue);
      END_IF
    END_FOR
    

    Thus, the new subject implements the interface I_Subject, inherits from the function block FB_Base and can access the functionalities of FB_SubjectBase via the embedded instance.

    Pic07

    Sample 2 (TwinCAT 3.1.4022) on GitHub

    Update: Push or pull method?

    There are two ways in which the observer receives the desired information from the subject:

    With the push method, all information is passed to the observer via the update method. Only one method call is required for the entire information exchange. In the example, only one variable of the data type LREAL has ever passed the subject. But depending on the application, it can be considerably more data. However, not every observer always needs all the information that is passed to it. Furthermore, extensions are made more difficult: What if the method Update() is extended by further data? All observers must be customized. This can be remedied by using a special function block as a parameter. This function block encapsulates all necessary information in properties. If additional properties are added, it is not necessary to adjust the update method.

    If the pull method is implemented, the observer receives only a minimal notification. He then gets all the information he needs from the subject. However, two conditions must be met. First, the subject should make all data available as properties. On the other hand, the observer must be given a reference to the subject so that it can access the properties. One solution may be that the update method contains a reference to the subject (i.e. to itself) as a parameter.

    Both variants can certainly be combined with each other. The subject provides all relevant data as properties. At the same time, the update method can provide a reference to the subject and pass the most important information as a function block. This method is the classic approach of numerous GUI libraries.

    Tip: If the subject knows little about its observers, the pull method is preferable. If the subject knows its observers (since there are only a few different types of observers), the push method should be used.

  • Advertisements
    1. Anders
      August 12, 2018 at 10:02 PM

      Hi Stefan

      I really like your articles on how to take advantage of the new IEC61131-3

      Quick question. Is there a way to compare objects with classes in IEC61131-3 similar to the “is” statement in C#?

      Say I have a FB_Animal, and to calsses that inherit animal,
      FB_Dog EXTENDS FB_Animal
      FB_Cat EXTENDS FB_Animal

      I want to do:
      METHOD update
      VAR Input
      animal : FB_Animal;
      END_VAR

      IF animal IS FB_CAT THEN
      //DO cat stuff
      ELSIF animal IS FB_DOG THEN
      //Do dog stuff
      END_IF

      Br

      Anders

      • August 15, 2018 at 1:39 PM

        Hi Anders,
        Unfortunatley is no compareable operator in IEC 61131-3 available. But you can do a workaround with the function __ QUERYINTERFACE(). Define an Interface for each FB. Each FB implements this Interface.
        FB_Dog EXTENDS FB_Animal IMPLEMENTS I_Dog
        FB_Cat EXTENDS FB_Animal IMPLEMENTS I_Cat
        Your method can now query the interface of your input variable with the function __QUERYINTERFACE().
        You can find an example in my post ‚IEC 61131-3: Object composition with the help of interfaces‘.
        If you have any more questions I can do an separate example.
        Regards
        Stefan

    1. No trackbacks yet.

    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 )

    Google+ photo

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

    Twitter picture

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

    Facebook photo

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

    Connecting to %s

    %d bloggers like this: