Join us today!

Operator Overloadin...
 
Notifications
Clear all

Operator Overloading in TwinCAT using OOP

4 Posts
3 Users
6 Likes
283 Views
Posts: 6
Topic starter
(@fisothemes)
Active Member
Joined: 11 months ago

Introduction

Operator overloading can somewhat be achieved in TwinCAT/Codesys systems through Object-Oriented Programming (OOP). Before we dive into practical examples, let's grasp the fundamental concept of operator overloading and understand how it seamlessly integrates with interfaces.

What is Operator Overloading?

In the realm of programming, operator overloading allows us to redefine the behavior of operators for custom data types. Instead of adhering strictly to their predefined operations, operators can be customised to work with user-defined types, enhancing the expressiveness and flexibility of your code.

Leveraging Interfaces for Operator Overloading

In TwinCAT/Codesys, the synergy of operator overloading and Object-Oriented Programming (OOP) comes to life through the strategic use of interfaces. Interfaces serve as blueprints for classes, defining a set of methods that implementing classes must adhere to. This modularity not only enhances code organisation but also facilitates the powerful concept of functional interfaces.

Functional Interfaces: Enhancing Flexibility

Functional interfaces play a pivotal role in our exploration. These interfaces focus on a specific functionality, in our case, operator overloading. By designing interfaces such as I_Operator and I_Object, we create a structured foundation for overloading basic arithmetic operators. This ensures that our classes adhere to a consistent and standardised interface, promoting code clarity and reusability.

Overloading Arithmetic Operators: Designing Functional Interfaces

Building upon our foundation, let's craft functional interfaces for overloading arithmetic operators in TwinCAT/Codesys. Each interface encapsulates a specific operation, ensuring a clear and modular design.

Functional Interfaces to Overload

INTERFACE I_Object EXTENDS __SYSTEM.IQueryInterface
INTERFACE I_Operator EXTENDS I_Object 
INTERFACE I_Addition EXTENDS I_Operator

METHOD Plus : I_Addition
VAR_INPUT
	ipObject : I_Object;
END_VAR
VAR_OUTPUT
	e : E_Error;
END_VAR
INTERFACE I_Subtraction EXTENDS I_Operator

METHOD Minus : I_Subtraction
VAR_INPUT
	ipObject : I_Object;
END_VAR
VAR_OUTPUT
	e : E_Error;
END_VAR
INTERFACE I_Multiplication EXTENDS I_Operator

METHOD Times : I_Multiplication
VAR_INPUT
	ipObject : I_Object;
END_VAR
VAR_OUTPUT
	e : E_Error;
END_VAR
INTERFACE I_Division EXTENDS I_Operator

METHOD DivideBy : I_Division
VAR_INPUT
	ipObject : I_Object;
END_VAR
VAR_OUTPUT
	e : E_Error;
END_VAR

 

These interfaces provide a standardised structure for implementing classes to define their behavior during addition, subtraction, multiplication, and division operations. Let's now illustrate these principles with a concrete example involving numeric interfaces.

Example: Scalars and Vectors

To bring the theoretical concepts into practical application, let's consider the I_Scalar and I_Vector interfaces, representing scalar and vector entities, and their respective implementations in FB_Scalar and FB_Vector.

Scalar Interface Implementation (FB_Scalar):

The FB_Scalar function block implements the I_Scalar interface, defining methods for converting scalar values to different data types and handling potential errors.

INTERFACE I_Scalar EXTENDS I_Object

METHOD ToF64 : LREAL
VAR_OUTPUT
	e : E_Error;
END_VAR

METHOD ToI64 : LINT
VAR_OUTPUT
	e : E_Error;
END_VAR

METHOD ToU8 : BYTE
VAR_OUTPUT
	e : E_Error;
END_VAR
{attribute 'no_explicit_call' := 'do not call this function block directly'}
FUNCTION_BLOCK FB_Scalar IMPLEMENTS I_Scalar, I_Addition, I_Subtraction, I_Multiplication, I_Division
VAR
	_fValue : LREAL; 
END_VAR


-----------------------------------------------------------
METHOD ToF64 : LREAL
VAR_OUTPUT
	e	: E_Error;
END_VAR

ToF64 := THIS^._fValue;

IF TO_STRING(THIS^._fValue) = '#NaN' THEN 
	e := E_Error.NaN;
ELSIF TO_STRING(THIS^._fValue) = '#Inf' THEN
	e := E_Error.PositiveInfinity;
ELSIF TO_STRING(THIS^._fValue) = '-#Inf' THEN
	e := E_Error.NegativeInfinity;
	END_IF


-----------------------------------------------------------
METHOD Plus : I_Addition
VAR_INPUT
	ipObject : I_Object;
END_VAR
VAR_OUTPUT
	e : E_Error;
END_VAR
VAR
	e1, e2 : E_Error;
	ipNumber : I_Scalar;
END_VAR

Plus := THIS^;

IF NOT __QUERYINTERFACE(ipObject, ipNumber) THEN e := E_Error.TypeMismatch; RETURN; END_IF

THIS^._fValue := THIS^.ToF64(e => e1) + ipNumber.ToF64(e => e2);

e := MAX(e1, e2);

-----------------------------------------------------------
METHOD Times : I_Multiplication
VAR_INPUT
	ipObject : I_Object;
END_VAR
VAR_OUTPUT
	e : E_Error;
END_VAR
VAR
	e1, e2 : E_Error;
	ipNumber : I_Scalar;
END_VAR

Times := THIS^;

IF NOT __QUERYINTERFACE(ipObject, ipNumber) THEN e := E_Error.TypeMismatch; RETURN; END_IF

THIS^._fValue := THIS^.ToF64(e => e1) * ipNumber.ToF64( e => e2);

e := MAX(e1, e2);

Vector Interface Implementation (FB_Vector):

Similarly, the FB_Vector function block implements the I_Vector interface, providing methods for vector operations like addition and multiplication.

INTERFACE I_Vector EXTENDS I_Object

METHOD ToArray : ARRAY[0..2] OF LREAL
VAR_INPUT
END_VAR

PROPERTY X : LREAL

PROPERTY Y : LREAL

PROPERTY Z : LREAL
{attribute 'no_explicit_call' := 'do not call this function block directly'}
FUNCTION_BLOCK FB_Vector IMPLEMENTS I_Vector, I_Addition, I_Subtraction, I_Multiplication
VAR
	_fX, _fY, _fZ : LREAL;
END_VAR


-----------------------------------------------------------
METHOD Plus : I_Addition
VAR_INPUT
	ipObject : I_Object;
END_VAR
VAR_OUTPUT
	e : E_Error;
END_VAR
VAR
	ipVector : I_Vector;
END_VAR

Plus := THIS^;

IF NOT __QUERYINTERFACE(ipObject, ipVector) THEN e := E_Error.TypeMismatch; RETURN; END_IF

THIS^._fX := THIS^._fX + ipVector.X;
THIS^._fY := THIS^._fY + ipVector.Y;
THIS^._fZ := THIS^._fZ + ipVector.Z;


-----------------------------------------------------------
METHOD Times : I_Multiplication
VAR_INPUT
	ipObject : I_Object;
END_VAR
VAR_OUTPUT
	e : E_Error;
END_VAR
VAR
	ipNumber : I_Scalar;
	ipVector : I_Vector;
	TmpX, TmpY, TmpZ : LREAL;
END_VAR

Times := THIS^;

// Scale vector.
IF __QUERYINTERFACE(ipObject, ipNumber) THEN 
	THIS^.SetValue( THIS^._fX * ipNumber.ToF64(),
					THIS^._fY * ipNumber.ToF64(),
					THIS^._fZ * ipNumber.ToF64());
	RETURN;
	END_IF

// Do the cross-product.
IF __QUERYINTERFACE(ipObject, ipVector) THEN 
	TmpX := THIS^._fX; TmpY := THIS^._fY; TmpZ := THIS^._fZ;
	THIS^.SetValue(	(TmpY*ipVector.Z) - (TmpZ*ipVector.Y),
					(TmpZ*ipVector.X) - (TmpX*ipVector.Y),
					(TmpX*ipVector.Y) - (TmpY*ipVector.X));
	
	RETURN; 
	END_IF

e := E_Error.TypeMismatch; 

 

Main Program Implementation:

Now, let's integrate these function blocks into a main program to showcase the practical usage of operator overloading with Scalars and Vectors.

 

PROGRAM MAIN
VAR
	bDoubleSquareScal, bAddVec, bCrossProd, bScaleVec: BOOL;
	eError : E_Error;
	fbScalar : FB_Scalar(2.2);
	fbVec1 : FB_Vector(1,2,3);
	fbVec2 : FB_Vector(3,2,1);
END_VAR
============================================================
IF bDoubleSquareScal THEN 
	bDoubleSquareScal := FALSE;
	fbScalar.Times(fbScalar.Plus(fbScalar));
	END_IF

IF bAddVec THEN
	bAddVec := FALSE;
	fbVec1.Plus(fbVec2);
	END_IF
	
IF bCrossProd THEN
	bCrossProd := FALSE;
	fbVec1.Times(fbVec2);
	END_IF
	
IF bScaleVec THEN
	bScaleVec := FALSE;
	fbVec1.Times(fbScalar);
	END_IF

Conclusion

Developers can utilize these concepts and implementations to enhance code expressiveness and flexibility in their projects.

Example Code can be found here

Reply
Topic Tags
3 Replies
3 Replies
twinControls
Admin
(@twincontrols)
Joined: 2 years ago

Member
Posts: 114

@fisothemes Excellent and very well documented! Thank you for sharing!

Reply
(@fisothemes)
Joined: 11 months ago

Active Member
Posts: 6

@twincontrols 

No worries, there are lots of helpful topics in this community that helped me so I wanted to share somethings.

Might delve deeper into functional interfaces and how we can use them to mimic lambda functions in TwinCAT/Codesys, since lambda functions in most OOP langs are just classes with 1 method and we have those in our platforms.

Here's a little preview of an alarm management system design that is inspired by Java Streams

AlarmManagementSystem

 

Reply
 Alex
(@alex)
Joined: 2 years ago

Eminent Member
Posts: 25

@fisothemes : you arouse my curiosity 😍

Reply
Share: