Join us today!
[Sticky] Dependency Injection in TwinCAT
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.
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:
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:
In case you want to say thank you !)
We'd be very grateful if you could share this community with your colleagues and friends. You can also buy us a coffee to keep us fueled 😊 This is the best way to say thank you to this project and support your community.
twinControls - https://twincontrols.com/
Nice short example! Another one can be found on AllTwinCAT.
My blog: https://cookncode.com/twincat
My code: https://github.com/roald87
-
Operator Overloading in TwinCAT using OOP
10 months ago
-
A few difficulties while learning OOP
1 year ago
-
Fluent Interface and Method Chaining in TwinCAT 3
2 years ago
-
Decorator Design Pattern
2 years ago
-
FB_init with derived function blocks
2 years ago
- 17 Forums
- 267 Topics
- 942 Posts
- 1 Online
- 722 Members