Join us today!

Dependency Injectio...
 
Notifications
Clear all

[Sticky] Dependency Injection in TwinCAT

2 Posts
2 Users
3 Likes
2,027 Views
twinControls
Posts: 114
Admin
Topic starter
(@twincontrols)
Member
Joined: 2 years ago

Before we discuss the implementation specifics, assume for the moment that you wish to purchase a brand-new car. The car's manufacturer designed the vehicle in such a way that you would have to take it to one of their repair facilities and could only use parts made by that business if something went wrong. Because of this, you become very reliant on the automaker and lack the freedom to choose the auto mechanic you want to use or to buy aftermarket parts for your car. You and the automaker are tightly coupled in this situation.

A different automaker, on the other hand, gives you the freedom to take your vehicle anywhere you wish to get it serviced. They even offer to educate you how to do it yourself using aftermarket auto components. In this choice, you are loosely coupled with with the automaker. For instance, if your water pump has a crack that is causing a coolant leak, all you need to do is get a new water pump that will fit your car model and make and replace it yourself with the help of your car manufacturer. If you had bought the first car, however, you'd have to go to a specific location and buy the water pump which is sold solely by that automaker.

In our PLC applications, we would want the function blocks, functions, and methods to be as loosely coupled as possible. We don't want to depend on one function block or method and wait for that function block to change its implementation so that we can resume operating our machine.

Dependency injection is based on the concept that if your POU needs something, don't try to create it within the POU, ask for what you need and get it from outside. Every time the POU tries to "do it itself", it creates hard-coded dependency. Using dependency injection makes our code more flexible and clean. 

Let's assume we need an alarm function block for our PLC application and this function block needs to send the alarms to the operator via email, SMS etc... At the time of our PLC application development, we have decided to use the email method. If we implement this communication logic within the alarm function block, this would cause dependency on the alarm function block since we might want to use SMS method in the future. Instead of creating the communication logic inside the function block, we can inject a communication method function block into the alarm function block. To achieve this, we can use the constructor (FB_Init) of the alarm function which would allow us to change the function block that's being injected at the runtime. 

To demonstrate the implementation, we can create an interface called I_MessageSender and add a method called 'SendMessage'. Then we can create function blocks called FB_SendMessageViaEmail and FB_SendMessageViaSMS as our communication methods which would implement this interface. Inside the FB_init method of FB_Alarm function block, we can inject the I_MessageSender interface. This would enable us to decide which method we would want to use when initializing the FB_Alarm function block. 

Option 1: 

fbAlarm : FB_Alarm(iMessageSender := fbSendMessageViaEmail);

Option 2:

fbAlarm : FB_Alarm(iMessageSender := fbSendMessageViaSMS);

 

This way, FB_Alarm doesn't need to know the logic in these function blocks and it gives us huge flexibility to change the communication method in the future if we need to. All it needs is to know which datatype to pass to the SendMessage method of these function blocks. 

structure

 

Let's create the I_MessageSender interface and the SendMessage Method: 

INTERFACE I_MessageSender
METHOD SendMessage : BOOL
VAR_INPUT
	sMessage : T_MaxString;
END_VAR

 

Create the FB_SendMessageViaEmail and FB_SendMessageViaSMS function blocks. Add your Send Email and Send SMS logics inside the SendMessage methods of these function blocks. 

FUNCTION_BLOCK FB_SendMessageViaEmail IMPLEMENTS I_MessageSender
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR

 

FUNCTION_BLOCK FB_SendMessageViaSMS IMPLEMENTS I_MessageSender
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR

 

FB_Alarm function block: 

FUNCTION_BLOCK FB_Alarm
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
	_iMessageSender : I_MessageSender;
	sMessage : T_MaxString;
END_VAR
_iMessageSender.SendMessage(sMessage);

 

FB_Init of FB_Alarm: 

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)
	
	iMessageSender : I_MessageSender;
END_VAR
_iMessageSender := iMessageSender;

 

We have declared an I_MessageSender here and we are assigning this input to the internal variable declared as _iMessageSender

Using '_' at the beginning of the internal variable name is just a preference that I also use for the internal variables whose values are being assigned by the properties. 

 

In the MAIN program, declare the function blocks. 

PROGRAM MAIN
VAR
	fbSendMessageViaEmail : FB_SendMessageViaEmail;
	fbSendMessageViaSMS   : FB_SendMessageViaSMS;
	
	fbAlarm : FB_Alarm(iMessageSender := fbSendMessageViaEmail);
END_VAR
fbAlarm();

 

If you forget to initialize the FB_Alarm function block, you'd get the error below: 

fb init error

 

If you decide to use another communication method besides SMS and Email, all you need to do is to implement the logic for the new function block and initialize the FB_Alarm function block with it. We don't have to change anything inside the FB_Alarm method. 

fbSendMessageViaMyNewMethod : FB_SendMessageViaMyNewMethod;

fbAlarm : FB_Alarm(iMessageSender := fbSendMessageViaMyNewMethod);

 

The class diagram: 

dependencyinjection

 

Reply
1 Reply
rruiter
Posts: 63
(@rruiter)
Estimable Member
Joined: 2 years ago

Nice short example! Another one can be found on AllTwinCAT.

Reply
Share: