Welcome to the first blog in this series on Beckhoff Object-Oriented Programming (OOP).
OOP enables code to be divided into self-contained, reusable objects, resulting in code that is easier to understand, maintain, and update. Throughout this series, the reasons behind this advantage will hopefully become evident and clear.
Beckhoff has a set of examples that will occasionally be referenced throughout this series.
Object oriented programming exists of 4 pillars:
- Encapsulation: Encapsulation refers to the grouping of data (variables, properties) and functions (methods, function blocks) into a single unit called an object. The single object allows for the internal code to be hidden from application code.
- Inheritance: Inheritance allows objects to inherit properties and functions from other objects. This allows for code to be reused even if the logic is not exactly the same.
- Polymorphism: Polymorphism means the ability of objects to take on different forms or exhibit different behaviors based on the context. This is useful for devices that have very similar functions, but the processes for how they are completed are very different.
- Abstraction: Abstraction involves simplifying complex systems by breaking them down into more manageable and understandable parts. It focuses on only the essential properties and behaviors of an object while hiding the unnecessary details.
Beckhoff provides several different tools that can be used to achieve these four pillars. Throughout this series, it will be pointed out how each tool correlates to the four pillars.
This first blog will touch on two basic principles of OOP, methods and properties.
Methods are actions that an object can perform, while encapsulating the details of how an object performs its tasks. Methods specify the behavior or actions that objects can perform, abstracting the implementation details and providing a clean interface for interacting with objects.
Methods can perform operations, manipulate data, and they may or may not return a value depending on their implementation. As for what a method should return, this depends largely on whether the method will be completed in one scan or not. For example, commanding an a conveyor to move 10 feet will take more than one scan. However, determining the value of a sensor only takes one scan.
In addition, there are different access modifiers for methods with the main two being
PRIVATE. Public versus private determines whether the method can be called outside of the program or function block. There are two additional modifiers:
PROTECTED, which allows any function block that inherits the function block to access the method (more on that in future blogs) and
INTERNAL, which restricts access to objects in the library.
In the example of commanding a conveyor to move 10 feet, the method may return a standard response that can be used in application. This should be public because function blocks outside of the conveyor will need to call this.
bComplete : BOOL;
bError : BOOL;
METHOD PUBLIC MoveDistance : ST_Response
Distance : LREAL; // feet
When the method has completed, the state machine can then be moved to the next step. If it errors during the process, the application can enter its error handling sequence.
respone := fbConveyor.MoveDistance(10); // move 10 feet
IF respone.bComplete THEN
eState := E_State.RUN_MACHINE;
ELSIF respone.bError THEN
eState := E_State.ERROR;
An example of a method that takes one scan is calculating the torque of the conveyor. For calculating torque, it is often useful to implement a moving average or filter to smooth out any noise in the data. For example, a method can do all of the math inside and return the value in terms of foot-pounds. This would be an example of a private method because only the conveyor function block should be calling this. The torque could be accessed outside of the function block via a property, for example:
METHOD PRIVATE CalculateTorque : REAL
rActualTorque : REAL; //0.1% of max torque
fbMovingAverage : FB_MovingAverage;
Another very common return type is simply a BOOL. Of course, a BOOL can mean many things (sensor state, method complete successfully, etc.), so be sure to comment what the return value means if it is not obvious!
// true = enable successful, false = interlocks active
METHOD PUBLIC Enable : BOOL
Methods can also act as a way of organizing code and maintaining readability in application code. This an example of when a return is not needed. The followiing excerpt is taking from the OOP Extended Sample.
// Call of actions
// Mode and state requests
// Enable for power supply, compressed air control and buttons
// Input variables of separating and sorting modules
// Output variables
METHOD PRIVATE General_Init
A really powerful feature of Beckhoff is the ability to call methods via ADS. This in turn makes methods able to be called via OPCUA. Head on over to our blog about setting an OPCUA server if have not already done so!
METHOD PUBLIC MoveDistance : ST_Response
Here, I am using UaExpert to call the method.
This can make testing extremely efficient. No need to add debug bits to test your code, just call the method that is being tested!
Another Beckhoff specific implementation of a method is FB_init. This allows for initialization of the function block before it is called. A simple use case for this is when project variants are used (another plug for one of our blogs!). Different initialized values can be used depending on the variant selected. For example, in the Simulation variant, the default conveyor speed is set to the maximum value to speed up any testing that is needed. Another common use case is initializing pointers and references to be used during runtime.
METHOD FB_init : BOOL
bInitRetains : BOOL;
bInCopyCode : BOOL;
lrSpeedSetpoint := MAX_SPEED;
lrSpeedSetpoint := 100;
Obviously, this is just scratching the surface of what methods can do. There are varying schools of thought on how much logic a method should contain. Some implementations of methods simply move state machines from one state to another, other implementations may contain an entire sequencer inside of a state. This depends on the architecture of the project.
DMC is happy to discuss the implementation of your project and help decide which implementation of methods will work best for you!
Moving onto properties, a property is an object attribute that encapsulates data and provides a controlled way to access or modify the data associated with it. Properties allow you to expose the internal state of an object while enforcing rules and logic for data access.
Properties are implemented using getter and setter methods. A getter is a method used to retrieve the value of a private attribute of an object. A setter is a method used to modify the value of a private attribute of an object.
Properties have the same access modifiers as the methods described above.
A simple example of this would be the speed setpoint of the previously mentioned conveyor.
PROPERTY PUBLIC lrSpeedSetpoint : LREAL
The setter can have conditions to make sure the speed is set within the proper bounds. In this example, a negative number is rejected and a speed greater than maximum is set to the maximum speed. The getter simply returns the speed here; however, a getter could convert a raw signal into a useful value, for example:
IF lrSpeedSetpoint >= 0 THEN
_lrSpeedSetpoint := SEL(lrSpeedSetpoint > MAX_SPEED, lrSpeedSetpoint, MAX_SPEED);
lrSpeedSetpoint := _lrSpeedSetpoint;
To access proporterties, the following should be used:
// Access "Set"
fbConveyor.lrSpeedSetpoint := 100; // 100 ft/min
// Access "Get"
If a property does not need either the getter or setter, it can simply be deleted.
From my personal experience, I have not seen properties used a ton. There are some specific situations where they are useful, but often times there are several different ways to achieve the same functionality. Methods, on the other hand, are essential to building an OOP based project.
Look out for part 2, interfaces, coming soon!
Learn more about DMC's Beckhoff and TwinCAT 3 programming expertise and contact us for your next project.