Part 6b - Function blocks and interfaces (IEC 61131-3)

Video

#PLC #TwinCAT3 #Beckhoff #OOP #IEC_61131-3

🔙 Previous Part | Next Part 🔜

↩️ Go Back

Table of Contents:


A) Objectives

The last episode ended a little abruptly as we were in the midst of having fun with function blocks.
Function blocks are our first steps into the world of object oriented programming, and thanks to them we can combine state with behavior I promised to talk about something called interfaces which is what I'll do now.

Interfaces are a really neat thing.

They can at a first glance look a little weird, they seem to be just an abstract annoyance. But once you understand the actual advantage of programming by interfaces, it will all make sense. Let's get started!

|400


B) Introduction to "Interfaces"

B.1) < Concept > - From "Abstarction" to "Inheritance" to "Polymorphism"

In the previous part of this tutorial we introduced the topic of inheritance where we learned that a function block could inherit some of the properties and functionality of another function block by EXTENDING another function block.
|200

Just as with inheritance, interfaces in IEC 61131-3 is a mechanism to achieve abstraction.

Interfaces are one way of achieving one of the pillars of object oriented programming called "polymorphism"

Polymorphism is the abstract concept of dealing with multiple types in a uniform manner, and interfaces are one way to implement that concept.

|300

Polymorphism: In summary, polymorphism in OOP allows objects of different classes to be treated as if they were of the same class, enabling code to be more flexible, reusable, and easy to maintain Polymorphism enables objects to take on multiple forms or behaviors. It includes compile-time polymorphism (method overloading) and runtime polymorphism (method overriding). Objects can be treated uniformly, regardless of their specific subclass type, providing flexibility and extensibility in the code.
|300
Read more here


B.2) Interface example - building a chess game with OOP

Interfaces:

  1. specify what a function block must do but not how.
  2. They are the blueprint of the function block.
  3. they are "programming by contract"

An Interface is about capabilities, for example:

Imagine a chessboard.
|200

Every player on the chessboard may be an "interface"

And any function block (which could be any of the players like king, peasant etc) implementing the interface "player" must be able to move, in other words it needs to implement the interface "move()"
|500

The interface specifies a set of methods that the function block has to implement.

You can see it as a binding contract.
Anyone implementing an interface must follow the contract, that is, implement the methods in the contract.
|250


B.3) How to define an Interface and how to Implement it on a FB - Light tower example

An interface is defined using the INTERFACE keyword. In this example we have an interface we call I_LightTower.
|220

|300

With the interface we describe the behavior for anyone that will actually implement the interface.

One behavior might for example be....

that it's possible to power On/Off the light tower, that is to enable the LEDs of it.
|400

|150

Another one might be to set the color of the light tower, such as red, blue and green.
|400

|150

One additional one might be to enable or disable a buzzer or beeper of some kind
|400

|400

These all three methods describe some of the behavior that all function blocks that implement this must follow.


► Implement the Light Tower Interface with different manufacturers:
Now let's imagine we have an actual implementation of the interface I_LightTower that is specific for the company's Balluff light towers.
|250

The FB_Balluff_LightTower is one type of light tower that has an actual implementation of the interface. Balluff is just one manufacturer of many, and the Balluff one might have an implementation that differs from the others.
|450

Another manufacturer that might do light towers are SICK,
|250

|450

and then this goes on...
|450

There might just be many manufacturers doing light towers, although all of them share the behavior that:

They all implement the same interface I_LightTower, in other words they all share the same behavior.

The actual implementation of these however might differ....

One might use simple 24V digital inputs,
|250

while another might use some more elaborate protocol such as IO-Link or EtherCAT for communication.
|420


► How to Implement the Interface on different FB:
Remember, to create an interface we need to use the keyword INTERFACE, and then we just add methods to the interface as we would add methods to a function block:
|330

Then we need to create a function block that IMPLEMENTS that interface, which we do by using the keyword IMPLEMENTS
|420

By saying that we will implement the interface I_LightTower in our function block Balluff_LightTower we are signing a contract that we have to implement all methods that are in the interface I_LightTower
|250

So we need to add the implementing code, that is the code that does the actual work of power on the light tower, which of course could vary between manufacturers. For some it might be a simple digital output to enable it, whilst it for others might involve complex serial protocols.
|420

When using a function block that implements an interface, you just declare it as you normally do.
For example:
|400

So here we have two different light towers, one from Balluff and another from SICK.

We can then declare an interface, and assign it a function block, in this case we just assign it the balluff light tower.
This means that if we call the interface, the actual implementation that will be called is the Balluff one.
|400

The nice thing with interfaces is that we can change the actual implemented function block of the interface during runtime.

For example, here we just re-assign the interface to the SICK light tower:
|400

This is possible because both function blocks implement the interface I_LightTower and thus both function blocks have agreed on the contract to implement whatever behavior is defined in the interface.

To call a method, you call it as usual,
|400


B.4) How to create an Interface in TwinCAT

To create an interface in TwinCAT, right-click on "POUs", select "Add", and select "Interface"
|400

This will create this popup window,
|250

enter the name of the interface, in this case I_LightTower, click "Open"

This will create the interface. Now you can add your methods to the interface
|400


B.5) Why do we need Interfaces? (Abstraction, Team works and Testing with Mock Interfaces)

Now you might be wondering...

If you think about it, with inheritance, by extending another function block we at least got a lot for free. But with interfaces, don't we just make life harder for us? 🤔


► 1. Abstraction
Interfaces happen when you want to describe in abstract terms what an object is supposed to do but don't want to tie it down to a particular implementation or function block.

So in the example of the light tower, at the time of writing your code that uses the light tower, you might not actually care exactly "how the light tower works" and "how it executes it different functions" (e.g setting a certain color).

|450

In your code that is using the light tower, you just want to set the color.

This also means that we can develop parts of the software without knowing exactly how other parts work in detail. So this also makes it easier to work in teams.


► 2. Team Collaboration
Now imagine a team.... (Team A and Team B)
|200

The only thing team A needs from team B is an interface to write events.
|200

Team A doesn't need to know exactly how the persistent logging is being made. They don't even need to know whether its written to a CSV file, an XML-file or a database. They just need to know that there is the interface that has a method LogEvent() which describes the intent, not the actual implementation.

Thus team A and team B only need to agree on the interface, the "contract" between each other.
|250

Then both teams can develop their software separately and independently, and then just have an integration phase much later.
|150


► 3. Testing
The third point is tests. When writing tests, you will find that concrete classes will not work well in your Test Environment.
|200

If you for example have a function block that depends on a database function block, your Unit Tests will not run in your test environment.

The solution is to use an interface and "mock" that interface so you can test your function block.

|500

Read more here

The key point of interfaces is that they provide a layer of abstraction so that you can write code that is ignorant of unnecessary details.

Here is a Playlist about Unit Tests in TC3

- - - #### B.5.1) Interfaces in other programming languages (polymorphism)

In IEC 61131-3 and Structured Text (ST) we have the luxury to have an explicit keyword INTERFACE to achieve this.

This is however not necessary to achieve polymorphism. There are other ways to do this, and in more dynamically typed languages such as Python this is usually what is referred to as Duck-Typing.
What this basically says is that "if something walks like a duck and quacks like a duck, then it is a duck"
|400

|400

Read more about Interfaces in Python here: https://realpython.com/python-interface/


B.5.2) Comparing Interfaces and FB Inheritance

A difference worth mentioning between interfaces and inheritance is that a function block can only inherit one function block but it can implement as many interfaces as it wants.

So here we have a function block, and this function block can at most only extend one additional function block.

|300

However, with interfaces, a function block can implement as many interfaces as it wants.

|400

For example, if we had a Function Block (FB_ABC) that implements 2 interfaces:

|500

it can implement, for example, the three methods in I_Interface_X and one method in I_Interface_Y.

The syntax for implementing more than one interface is to simply add a comma between the interfaces that you want your function block to implement.

|700


C) TUTORAL 14 - Interface to describe how to store Event Data persistently //FILE -->TimeProject (part3)

Alright, it's time for us to do a little bit of coding...

We will continue with the example that we did in the last part of this tutorial (so we will continue with the exact same project that we did and we will add some functionality).

Reminder: Just to remember what we did on our last project called "TimeProject". In the Last part we added an Event logger FB. Read more about the last part here

See Full Diagram:

C.1) STEP 1 - Create the Interface to Store Events in Persistent Storage

STEP 1 - We will start by creating a shell for storing our events persistently.

What I mean by "persistently" is to store them to some form of Persistent storage, for example:

Did you know?

Persistent storage retains data even after power loss or system restart, while Ephemeral storage does not. Object storage is persistent but is primarily designed for long-term data storage, not for in-memory, short-term use.

And we are going to make this very flexible by using an interface, so we're going to say that we want to store our events, but we don't specify exactly in which format (JSON, XML, SQL database, etc)

We just want to provide an interface to say...
|250

So we are going to create an interface for this.

Comment: There are varying ways to do this, but they way we will do right now will help us learn a little bit more about programming.

Note: In this iteration, we will not implement the "actual storage" (that we will do in the next part), first we have to go through "state machines" (and some other things).

So, if you remember here from the last part we have our EventLogger that takes care of the actual events

Right now we are only using this array of 100 events and everything is stored in that array.

We could of course extend this functionality and make a better EventLogger by making sure to use a Ring Buffer (so the oldest one is always removed once you reach more than 100 events)

|400

But we already have a good core functionality, the responsibility of this particular function block "EventLogger" is to allocate the events once we get a trigger.

What we can see is that we don't really want to put the functionality of "storing" the events persistently in this function block. Because again, we want to separate the concerns as much as possible. We want to make this code as modularized as possible.

Reminder: as a PLC developer, you have to understand that for all the year of software development and software engineering, we have learnt over and over again that the software is just much more maintainable (and you are going to save tons of time) if you try to separate the concerns as early as you can.

That's what we're going to do, separate the functionalities as much as we can.
But to do that first we need to provide an interface...

The idea is that, since we will still have this method AddEvent().

Once one event is added to the EventLogger THEN we WRITE the event to the persistent storage through an interface.

So, it's actually not the EventLogger that will WRITE the persistent data. For that we will have individual Function Blocks for whatever format that we want to store in.

For example, if you want to store in a file (in XML format) we might have one function block that we call XmlFileStorageLogger, and if you want to store in JSON format we might have one that's called JsonFileLogger. And if we want to have one database we can have an DatabaseLogger, etc...

The important thing is that we will at some point need the concrete Function blocks that implement a certain behavior. But the EventLogger doesn't need to know how all these particular implementations are going to look like.
It just needs to know...
|200
"Hey, I have an event here and I want you to store it"

So we need an interface to describe this and the easiest way is to just create an interface...

And we can call this interface "I_PersistentEventStorage".

Note: Any function block that wants to have persistent storage needs to implement this interface.

And when you create the interface it's just an empty shell and this doesn't do anything.

So we need to write our first method and the thing we want to do here is basically just to write any event to the persistent storage.

That's the interface, right??

So we say:

but we still need to provide it to the interface so that the concrete function block that actually implements the interface will have the data.

So for this we create a method.

Think of the method as behavior. So what should we be able to do? We should be able to store the event so we can call it StoreEvent().
No return type needed

And the input data is the event, remember we made a structure for this, so we can use it.

|500

Note: The reason we didn't have a structure in AddEvent() was because we didn't have the timestamp at that point in time so we needed to provide everything except the timestamp.

But we don't need to split the event like that anymore now because we have the timestamp, so the EventLogger will have the whole structure ready for us.

In this StoreEvent() method we can just provide the whole structure and have the whole event.

So we have an interface and that interface provides the "service or the behavior" of storing events --> StoreEvent().

And if you call StoreEvent(), whatever that is implementing this interface, if it's the XML-logger, JSON-logger, database-logger, etc...

Whoever implements this interface will have to implement this particular method StoreEvent() that describes what it is supposed to do.

|250

So we have our interface ready.

Diagram:
|500


C.2) STEP 2 - Making the reference to our interface in the FB_init method (part 1 )

Now, we are going to learn something new...

Note that right now we haven't written any actual implementation code, but that doesn't mean that we can't already start to code how our software is supposed to behave.

What we want to do, is that once an event is added here, in the array,

we will have the whole ST_Event structure available here,

So... after that, here we want to store the event persistently

To do this, what we need to do is to have a reference to our interface (I_PersistentEventStorage)

And whoever implements that interface is not our concern, we just want to describe the behavior of our software, that is we just want to have access to the interface.

Tip: One way to work with this is to think "How would I like to do this if I was in team A and someone else was in team B"

And to do this we need a reference to this interface and one way to do this is by something called "Dependency Injection"

C.2.1) < Concept > - Dependency injection (Light Tower Example)

Dependency Injection is a technique in which an object receives other objects that it depends on.

These other objects are called dependencies

The "injection" refers to the passing of a dependency (a service) into the object (a client) that would use it.

|400


Light Tower Example:
Let's imagine we have a function block that takes care of the complete state of one machine.

So it knows whether the machine has stopped, is running and whether it has any active alarms.

Part of this information could be forwarded to a light tower, so for example if the machine has stopped the light tower could be red and if it has alarms the light tower might be yellow and we might want to give of a sound using the buzzer to indicate that attention is needed.

|150

However, the state machine function block should not have to take care of actually enabling the LEDs and the buzzer of the light tower.

For that we should have a separate function block that does the communication with the light tower. We can have the FB_LightTower_Manufacturer_X deal with that problem, since it already implements the "rules" described by the Interface I_LightTower (to turn on the LEDs, to sound the buzzer, etc)

|400

What we should do next, is to "inject" a dependency to the light tower.

This means that when we instantiate the function block FB_StateMachine, we inject the dependency of the light tower. So we know that FB_StateMachine uses the Light Tower Interface and its "services" (to turn on certian colors, to sound the buzzer, etc)

By using an interface as injection, the function block FB_StateMachine doesn't need to know exactly how the light tower works, it only knows that the Light Tower provides a certain service (in our case, that it can turn on/off, set a certain color of light, use the buzzer, etc)


C.2) STEP 2 - Making the reference to our interface in the FB_init method (part 2 - continue)

Alright, now that we got the theory for Dependency Injection we're going to do it. That's achieved by creating something called the FB_init method.

So we add a method,

and here we get some default methods,
|350

Right now, we are only going to use the FB_init method which will be called when the object is instantiated, so whenever there is an instance of the EventLogger FB instantiated the FB_init method will be called implicitly (you don't have to call it explicitly, it will be called automatically)

And here we can define some default behavior,

What we want to do here is to provide a reference to this interface of persistent storage. So this is basically doing the Dependency Injection.

So we want to have our reference to our interface coming in here:
We provide a reference and call it "iPersistentEventStorage"

So, in this case we have a reference to the persistent storage interface, and the nice thing about this is that, in the background, the reference can be executing the actual implementation function block (which can be any of the function blocks that do the actual storage).

Now we need to store it locally so we always have the reference locally in the function block.

First create a local reference to it in the function block variable definition,

Tip: One good practice is to add an underscore " _ " to everything that's coming "in" as a dependency.

We just call it _PersistentEventStorage
Now the whole FB has a reference to this interface,

Copy PLC Code:

FUNCTION_BLOCK EventLogger
VAR
	// local to the function block and to its methods
	// internal (instance) variables
	
	aEventBuffer : ARRAY[1..MAXIMUM_SIZE_OF_EVENT_BUFFER] OF ST_Event;
	nCurrentEventBufferIndex : INT := 0;
	
	//Create local reference of our Interface
	// FB_EventLogger USES I_PersistentEventStorage
	_PersistentEventStorage : I_PersistentEventStorage;
	
END_VAR

VAR CONSTANT
	// local to the function block and to its methods
	// these variables cannot be changed during program execution.
	
	MAXIMUM_SIZE_OF_EVENT_BUFFER : INT := 100;
	
END_VAR
----------------------------------------------------------------

And this one (in the FB_init method) is the one we want to store.

Copy PLC Code:

METHOD FB_init : BOOL
VAR_INPUT
	bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
	bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
	
	// Making a reference to the Interface I_PersistentEventStorage
	// This Interface is for Storing Events Persistently	
	iPersistentEventStorage : I_PersistentEventStorage; 
END_VAR

----------------------------------------------------------------------
// store reference locally in the FB when FB_init method executes
// this allows all methods in the FB to have access to it
_PersistentEventStorage := iPersistentEventStorage;

Summary
What we are going to do here is...

  1. when we create the event logger, we will have to provide it a reference to an interface.

  2. then we will store this reference locally in the FB.

So all the methods in the function block have access to it. That's quite neat!

I want to point it out that when the FB EventLogger is created, inside the FB_init method we just have an interface, that will only describe the behavior.

|500

So if you look at this interface...
|600

it only describes that there is a method StoreEvent()

but it doesn't actually describe ANYTHING about how this is done ...

So whether it's done by JSON, XML or whatever, it's not described here and that's important thing, we don't care!!

Because in the AddEvent() we just want to store it

and how that's stored and how the code is working it's not anything of this function blocks concern. We want to modularize them as much as we possibly can, because of all the advantages. It's going to be easier to test and everything.

So now we have a PersistentEventStorage reference in the function block.

So when we do the AddEvent... we can actually call this ...

And that's it! That's all the code needs to know.

The EventLogger FB doesn't need to know anything about how this is actually stored. That's done by whoever is implementing this interface.

Now, if we would try to compile this we will get an error because when this function block (EventLogger) is created, the FB_init method requires us to provide a reference to the interface.

Copy PLC Code:

(* Returns TRUE if add was succesful and FALSE if buffer is full *)
METHOD PUBLIC AddEvent : BOOL
VAR_INPUT
	// user input
	eEventType : E_EventType; //user defined ENUM
	eEventSeverity : TcEventSeverity; // library ENUM
	nEventIdentity : UDINT;
	sEvenText : STRING(255);
	
END_VAR
------------------------------------------------------------------------
//Check if the event buffer is full using a private method
IF NOT IsEventBufferFull () THEN
	
	nCurrentEventBufferIndex := nCurrentEventBufferIndex + 1;
	
	// from user input, fill buffer
	aEventBuffer[nCurrentEventBufferIndex].eEventType := eEventType;
	aEventBuffer[nCurrentEventBufferIndex].eEventSeverity := eEventSeverity;
	aEventBuffer[nCurrentEventBufferIndex].nEventIdentity := nEventIdentity;
	aEventBuffer[nCurrentEventBufferIndex].sEvenText := sEvenText;
	
	// using our function, fill the date and time stamp
	// input current event to the function
	// updates only date and type stamp of the input event
	UpdateEventTimestampWithSystemTime(stEvent := aEventBuffer[nCurrentEventBufferIndex]);
	
	AddEvent := TRUE; // add was succesful
ELSE
	AddEvent := FALSE; // buffer is full
END_IF

// Here we want to store the event persistently
_PersistentEventStorage.StoreEvent(stEvent := aEventBuffer[nCurrentEventBufferIndex]);

Diagram:

If we try to compile this now, it's going to say "No matching FB_init method found for instantiation of EventLogger"

And that's exactly what it means. There is nothing provided here.

Now we actually need to provide a "concrete implementation".
We need to provide it an interface, but this interface needs to point to something that does the work. We need to provide a reference to a function block that actually does the implementation.

What I'm going to do now is to create a shell for a function block that implements the interface without actually doing the implementation, that's going have to wait for the next part, so I'm going to add a POU...

and then a FB, call it XmlPersistentEventStorage

And here you can actually select IMPLEMENTS when you create the FB.

We want the FB XmlPersistentEventStorage to implement the interface I_PersistentEventStorage

We want it to be in Structured text, and we Add the POU

Now we've got an empty shell for our function block that implements this interface.

And what it also did for us automatically when we did this, was that it added the StoreEvent() method in this new FB for us.

However if we click on the StoreEvent() we actually get a warning here that says "Add method implementation"

Which is what the use of interfaces is all about because the interface just describes the behavior, nothing about the implementation. It just says that "you NEED to have a method StoreEvent()" that has this stEvent as input.

So in the next part we will continue from here, and work on the implementation of the XML method

Diagram:
|500

Remember, it IMPLEMENTS an Interface:
|500

The important thing here is that now we have a FB implements this actual interface.

What we can do now is to create an instance of FB_XmlPersistantEventStorage in the MAIN program,

Now we can provide a reference to this one so... we can do like this.
Note: This is how you provide parameters to the [[Constructor]].

Observation: For anyone that's used to Java or C++ or any other object oriented programming language, the FB_init method is basically equivalent to constructors... it's not exactly the same there are some differences, but from a principal perspective it's the same thing. The obvious difference here is that you can only have one FB_init method, you can't have several like you can have several constructors with different parameters in Java or C++, you can't have that here.

So, now we're going to get a reference to something that implements the I_PersistentEventStorage interface which in this case happens to be the Xml storage.

But it could be anything, right? It doesn't have to be XML, it can be JSON, it can be a reference to some SQL database storage Whatever, your fantasy sets the limit! And the really nice thing here is that you can in runtime change this storage so it's extremely flexible.

The EventLogger can use the XmlStorage but it can use any other storage as well and ... the event logger doesn't care! The EventLogger doesn't have to care. The only thing it cares about is that it stores an event, but it doesn't care whether it's stored in a database or whatever, and it shouldn't care, that's the important thing. It shouldn't care, because if it cared about exactly how the data would be stored then we would have this tight coupling that's really not something you want to strive for in software development.

Believe me, you are going to save a lot of time and a lot of frustration, and especially if someone else is going to take over your software and do software maintenance ten years from now then a good software architecture is going to help that next person a lot.

Copy PLC Code:

PROGRAM MAIN
VAR
	(* 
	  --------------- PART 1 ---------------------
	stThisIsOurFirstInstanceOfAStructure : ST_Event;
	bBoolean : BOOL := FALSE; 
	  --------------------------------------------
	*)
	
	bBoolean : BOOL := FALSE; 
	
	// Define sample values to the variables needed to add an Event
	eEventType1 : E_EventType := E_EventType.Alarm; //user defined ENUM
	eEventSeverity1 : TcEventSeverity := TcEventSeverity.Error; // library ENUM
	nEventIdentity1 : UDINT := 42;
	sEvenText1 : STRING(255) := 'This is a sample event';
	
	// Create an instance of the FB that defines the format for Persistent Event Storage
	XmlPersistentEventStorage : FB_XmlPersistentEventStorage;
	
	// Create one instance of our FB EventLogger, and provide interface parameter for FB_init
	EventLogger1 : EventLogger(XmlPersistentEventStorage); 
	
	
END_VAR
-----------------------------------------------------------------------
(* 
  ----------------- PART 1 ---------------------
IF NOT bBoolean THEN
	UpdateEventTimestampWithSystemTime (stEvent := stThisIsOurFirstInstanceOfAStructure);
	bBoolean := TRUE;
END_IF
  -----------------------------------------------
*)

IF bBoolean THEN
	// Add an event (in one cycle of the PLC)
	EventLogger1.AddEvent(eEventType1, eEventSeverity1, nEventIdentity1, sEvenText1); 
	bBoolean := FALSE;
END_IF


Diagram:

Full Diagram:

With this we should be able to compile...

Now we actually get this warning...

the compiler actually warns us that "Hey, you haven't actually done any implementation of this function block which you should" because this is the implementing function block that implements the interface and this one needs to have all the magical code here. So we get a warning.

|500

But anyway... it compiled and we can run it but nothing is going to happen of course because we have no implementation yet.


Final Thoughts:

Now we have reached the end of this part.

Just as with the previous part about function blocks I want to point out that we only scratched the surface of the possibilities of interfaces with this part of the tutorial.

Interfaces are so, so, SOOOO much more than just writing IMPLEMENTS and implementing an interface.

By using interfaces we can design our software in a much more modularized way and by utilizing certain design patterns we can make our software more robust.

When doing our software design using test driven development, which is a coming part of this tutorial we don't have to worry about the implementation specifics in the beginning of a project, but more architectural concerns.

By thinking in this way, we will end up with software modules that have clearly separated concerns high cohesion, and loose coupling which are all desirable properties of software.

Decades of surveys in software projects have shown this over and over again. I will get back to the topic of interfaces more, especially when we start to talk about test driven development in the more advanced parts of this tutorial.

Thanks for sticking around, and see you in the next part!

D) Summary

D.1) Concepts - Interfaces & Polymorphism: How They Fit Together

What is Polymorphism?

Polymorphism comes from two Greek words: "poly" (many) and "morph" (form). It allows objects of different classes to be treated as if they were objects of the same class.

What are Interfaces?

  1. Definition: Interfaces are like contracts. They define a set of methods (actions) without specifying how these methods are implemented.
  2. Purpose: Ensure consistency. Any class that "signs" (implements) this contract agrees to provide specific behaviors.

How does Polymorphism connect with Interfaces?

  1. Shared Interface: Multiple classes might implement the same interface. While the method signatures (names, return types, parameters) are the same due to the interface's contract, the actual behavior (implementation) of those methods can vary among classes.
  2. Uniform Interaction: Thanks to interfaces, we can refer to objects of different classes under the umbrella of that interface. This capability is polymorphism in action: different objects, unified by an interface, can be interacted with in a standard manner.

Example: Let's Simplify with a Device Button

Interface: ButtonInterface with a method press().

Using Polymorphism:

You can have a list of ButtonInterface objects. This list might contain both TV remotes and light switches. Even though they are different objects, you can call the press() method on any of them without needing to know their specific class. This is because both classes implement the same interface.

When you loop through the list and call press(), the TV remote will turn on the TV, and the light switch will turn on the light. The action taken (turning on a TV or light) is determined at runtime, showcasing polymorphism.

Key Insight:

To summarize:

With these concepts combined, you gain the power of structured, predictable interactions with the flexibility of diverse, real-time implementations.


Concepts - Dependency Injection

[[Dependency Injection]] (DI) is a design technique used in programming where an object receives its dependencies rather than creating them itself. This method promotes better code structure, making components more modular, maintainable, and testable.

Simple Analogy: Imagine you're baking a cake. Instead of gathering all the ingredients yourself, someone hands them to you as you need them.

In this scenario:

|300

Key Points:

  1. An object obtains its dependencies (the services or components it relies on).
  2. The act of providing these dependencies is termed "injection".

Example (Light Tower):

Benefit: By injecting dependencies like the light tower, code remains modular. The main object doesn’t need to know all the details, just the services the dependency provides. This makes both testing and modifications easier in the long run.


D.2) TimeProject (part3) Event logger and Interface for Persistent Storage

D.2.1) DUTs

  1. Enum
TYPE E_EventType :
(
	Alarm := 0,
	Message := 1
);
END_TYPE
  1. Structure
TYPE ST_Event :
STRUCT
	eEventType : E_EventType;
	eEventSeverity : TcEventSeverity;
	nEventIdentity : UDINT;
	sEventText : STRING(255);
	dtTimestamp : DATE_AND_TIME;
END_STRUCT
END_TYPE

Remember:

Diagram:


D.2.2) POUs

D.2.2.1) FUN
  1. Function
FUNCTION UpdateEventTimestampWithSystemTime
VAR_INPUT
	stEvent : REFERENCE TO ST_Event;
END_VAR
VAR
	fbGETSYSTEMTIME : GETSYSTEMTIME;
	stFileTime : T_FILETIME; // requires Tc2_Utilities
END_VAR

------------------------------------------------------------------
// The function block writes on stFileTime (VAR_OUTPUT) in T_FILETIME format 
fbGETSYSTEMTIME(timeLoDW => stFileTime.dwLowDateTime,
				timeHiDW => stFileTime.dwHighDateTime);

// Overwrites the Timestamp of stEvent using the FILETIME_TO_DT function
stEvent.dtTimestamp := FILETIME_TO_DT(fileTime := stFileTime);

Remember:
A) GETSYSTEMTIME FB

B) T_FILETIME Structure as output of the FB

C) Function to convert FILETIME to DATE_AND_TIME (DT)

Diagram:

D.2.2.2) ITF
  1. Interface (FB_XmlPersistentEventStorage)
INTERFACE I_PersistentEventStorage

4.1 Public Method

METHOD StoreEvent
VAR_INPUT
	stEvent : ST_Event;
END_VAR

------------------------------------------------------------------
// There is no implementation here, it is just an establish requirement for any FB that implements the Interface

Diagram:
|500


D.2.2.3) FB
  1. Function block (EventLogger)
FUNCTION_BLOCK EventLogger
VAR_INPUT
	// inputs to the function block. 
	// These are read-only, meaning you cannot modify their values within the block.
END_VAR


VAR_OUTPUT
	// outputs from the function block. 
	// The function block can modify the values of these 
END_VAR


VAR
	// local to the function block and to its methods
	// internal (instance) variables
	
	aEventBuffer : ARRAY[1..MAXIMUM_SIZE_OF_EVENT_BUFFER] OF ST_Event;
	nCurrentEventBufferIndex : INT := 0;
	
	//Create local reference of our Interface
	// FB_EventLogger USES I_PersistentEventStorage
	_PersistentEventStorage : I_PersistentEventStorage;
	
END_VAR

VAR CONSTANT
	// local to the function block and to its methods
	// these variables cannot be changed during program execution.
	
	MAXIMUM_SIZE_OF_EVENT_BUFFER : INT := 100;
	
END_VAR
----------------------------------------------------------------
// code

5.1 FB_init Methods

METHOD FB_init : BOOL
VAR_INPUT
	bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
	bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
	
	// Making a reference to the Interface I_PersistentEventStorage
	// This Interface is for Storing Events Persistently	
	iPersistentEventStorage : I_PersistentEventStorage; 
END_VAR

------------------------------------------------------------------
// store reference locally in the FB when FB_init method executes
// this allows all methods in the FB to have access to it
_PersistentEventStorage := iPersistentEventStorage;

5.2 Private Method

METHOD PRIVATE IsEventBufferFull : BOOL


------------------------------------------------------------------
//Check if the event buffer is full
IF (nCurrentEventBufferIndex + 1) > MAXIMUM_SIZE_OF_EVENT_BUFFER THEN
	IsEventBufferFull := TRUE; //method returns TRUE, it is full
ELSE 
	IsEventBufferFull := FALSE; //method returns FALSE, it is not full
END_IF

5.3 Public Method

(* Returns TRUE if add was succesful and FALSE if buffer is full *)
METHOD PUBLIC AddEvent : BOOL
VAR_INPUT
	// user input
	eEventType : E_EventType; //user defined ENUM
	eEventSeverity : TcEventSeverity; // library ENUM
	nEventIdentity : UDINT;
	sEvenText : STRING(255);
	
END_VAR

------------------------------------------------------------------
//Check if the event buffer is full using a private method
IF NOT IsEventBufferFull () THEN
	
	nCurrentEventBufferIndex := nCurrentEventBufferIndex + 1;
	
	// from user input, fill buffer
	aEventBuffer[nCurrentEventBufferIndex].eEventType := eEventType;
	aEventBuffer[nCurrentEventBufferIndex].eEventSeverity := eEventSeverity;
	aEventBuffer[nCurrentEventBufferIndex].nEventIdentity := nEventIdentity;
	aEventBuffer[nCurrentEventBufferIndex].sEvenText := sEvenText;
	
	// using our function, fill the date and time stamp
	// Step1: input current event to the function (aEventBuffer[nCurrentEventBufferIndex])
	// Step2: updates ONLY date and type stamp of the input event
	UpdateEventTimestampWithSystemTime(stEvent := aEventBuffer[nCurrentEventBufferIndex]);
	
	AddEvent := TRUE; // add was succesful
ELSE
	AddEvent := FALSE; // buffer is full
END_IF

// Here we want to store the event persistently
_PersistentEventStorage.StoreEvent(stEvent := aEventBuffer[nCurrentEventBufferIndex]);

Diagram:


  1. Function Block (FB_XmlPersistentEventStorage)
FUNCTION_BLOCK FB_XmlPersistentEventStorage IMPLEMENTS I_PersistentEventStorage
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR


------------------------------------------------------------------
// code

6.1 Public Method

{warning 'add method implementation '}
METHOD StoreEvent
VAR_INPUT
	stEvent	: ST_Event;
END_VAR


------------------------------------------------------------------
// Here is where the XML-writing will be done

Diagram:
|500

Remember, it IMPLEMENTS an Interface:
|500


D.2.2.3) MAIN
  1. Main
PROGRAM MAIN
VAR
	(* 
	  --------------- PART 1 ---------------------
	stThisIsOurFirstInstanceOfAStructure : ST_Event;
	bBoolean : BOOL := FALSE; 
	  --------------------------------------------
	*)
	
	bBoolean : BOOL := FALSE; 
	
	// Initialize the variables needed to add an Event with sample values
	eEventType1 : E_EventType := E_EventType.Alarm; //user defined ENUM
	eEventSeverity1 : TcEventSeverity := TcEventSeverity.Error; // library ENUM
	nEventIdentity1 : UDINT := 42;
	sEvenText1 : STRING(255) := 'This is a sample event';
	
	// Create an instance of the FB that defines the format for Persistent Event Storage
	XmlPersistentEventStorage : FB_XmlPersistentEventStorage;
	
	// Create one instance of our FB EventLogger, and provide interface parameter for FB_init
	EventLogger1 : EventLogger(XmlPersistentEventStorage); 
	
	
END_VAR

------------------------------------------------------------------
(* 
  ----------------- PART 1 ---------------------
IF NOT bBoolean THEN
	UpdateEventTimestampWithSystemTime (stEvent := stThisIsOurFirstInstanceOfAStructure);
	bBoolean := TRUE;
END_IF
  ----------------------------------------------
*)

IF bBoolean THEN
	// Add an event (in one cycle of the PLC) using the AddEvent Method
	EventLogger1.AddEvent(eEventType1, eEventSeverity1, nEventIdentity1, sEvenText1); 
	bBoolean := FALSE;
END_IF

Diagram:

Full Diagram:


🔙 Previous Part | Next Part 🔜

↩️ Go Back


Z) Glossary

File Definition