TwinCAT and CODESYS support object-oriented extensions for IEC 61131-3 Structured Text. That does not mean every PLC program should be written like a desktop application.
The useful question is smaller:
When should a PLC programmer use inheritance, and when should a PLC programmer use interfaces?
The short answer is this:
Use inheritance for small, stable hierarchies. Use interfaces for behavior that must work across different devices.
Function Blocks as Classes
In TwinCAT, a function block can act like a class. It has internal state. It can expose
methods. It can expose properties. The cyclic body or an explicit update() method can
process its behavior.
A simple light could expose one property:
FUNCTION_BLOCK Light
VAR
_on : BOOL;
END_VARThe property On controls the internal state or a mapped output.
PROPERTY On : BOOLGetter:
On := _on;Setter:
_on := On;Usage stays simple:
VAR
light : Light;
END_VAR
light.On := TRUE;So far, this is not revolutionary. It is only a function block with a cleaner public API. The interesting part starts when the system contains different kinds of lights.
Some lights are only on or off. Some are dimmable. Some have RGB channels. Some may be virtual devices used for simulation or testing.
Putting every variant into one large Light function block is possible. It is also how
small examples become large maintenance problems. The block grows flags, unused variables,
special cases, and customer-specific branches. Very elegant, if the goal is job security
through confusion.
Inheritance
Inheritance lets one function block extend another function block.
A dimmable light can extend a basic light:
FUNCTION_BLOCK LightDimmable EXTENDS Light
VAR
dimmingValue : LREAL;
END_VARThe derived block inherits the public members of Light and adds its own property.
PROPERTY DimmingValue : LREALUsage:
VAR
light : LightDimmable;
dimmingValue : LREAL;
END_VAR
light.On := TRUE;
light.DimmingValue := 0.3;
dimmingValue := light.DimmingValue;This works well when the relationship is stable:
A dimmable light is a light.
It also allows a reference to the base type to refer to an instance of the derived type.
VAR
lightSimple : Light;
lightDimmable : LightDimmable;
lightReference : REFERENCE TO Light;
END_VAR
lightReference REF= lightDimmable;
IF __ISVALIDREF(lightReference) THEN
lightReference.On := TRUE;
END_IFThe reverse does not work. A reference to LightDimmable cannot point to a plain Light,
because a plain Light has no DimmingValue property.
That is correct. It is also the first important limitation.
The base class starts to define the shape of the hierarchy. If more variants appear, the hierarchy can become awkward:
- dimmable light
- RGB light
- dimmable RGB light
- simulated light
- diagnostic light
- vendor-specific light
At that point, inheritance is no longer solving the problem. It is organizing the problem into a tree and hoping the real world behaves like a tree. It often does not.
Where Inheritance Fits
Inheritance is useful when the hierarchy is shallow and stable.
Good candidates are small families with clear relationships:
- basic indicator and specialized indicator
- base simulation block and one test implementation
- common diagnostic base with a few narrow extensions
The practical rule is:
If the hierarchy needs a diagram to understand, it is probably already too deep.
For PLC frameworks, inheritance should usually stay one level deep. It should not become the main tool for sharing behavior across unrelated devices.
Interfaces
An interface defines what a function block must provide. It does not define how the function block works internally.
For a switchable device, the interface could be:
INTERFACE ISwitchableWith one property:
PROPERTY On : BOOLA light can implement it:
FUNCTION_BLOCK Light IMPLEMENTS ISwitchable
VAR
on : BOOL;
END_VARA ventilator can implement the same interface:
FUNCTION_BLOCK Ventilator IMPLEMENTS ISwitchable
VAR
on : BOOL;
END_VARA ventilator is not a light. But both can be switched on and off. That is the point.
The interface describes behavior the caller may use. It avoids forcing unrelated devices into the same inheritance tree.
Interface References
An interface variable can refer to any instance that implements the interface.
VAR
device : ISwitchable;
light : Light;
ventilator : Ventilator;
END_VAR
device := light;
IF device <> 0 THEN
device.On := TRUE;
END_IF
device := ventilator;
IF device <> 0 THEN
device.On := FALSE;
END_IFThe caller does not need to know whether the device is a light, fan, pump, or simulated object. It only needs the contract:
This object has an
Onproperty with the agreed meaning.
That last part matters. The compiler checks that the property exists. It does not check
the semantic convention. A troll implementation could switch the output off when On is
set to TRUE. The compiler will not care. Your machine might.
Interfaces define syntax. The engineering team must still define the protocol.
Keep Interfaces Small
Interfaces should be focused. One interface should describe one capability.
Examples:
INTERFACE ISwitchableINTERFACE IDimmable EXTENDS ISwitchableINTERFACE IRequestOperationINTERFACE IStartStopOperationDo not create one large interface for every possible device feature. That only recreates the oversized function block problem with different syntax.
A useful interface answers one question:
What does the caller need from this object?
If a caller only needs to switch devices on and off, use ISwitchable. Do not force it to
know about dimming, diagnostics, simulation mode, motor currents, RGB colors, and whatever
the next project meeting invents.
Optional Capabilities
Interfaces also help when some devices support additional behavior.
Assume a room contains several switchable devices. Some are dimmable. Others are not.
The basic operation can use ISwitchable:
VAR CONSTANT
LIGHT_COUNT : DINT := 8;
END_VAR
VAR
lights : ARRAY[0..LIGHT_COUNT - 1] OF ISwitchable;
N : DINT;
value : BOOL;
END_VAR
FOR N := 0 TO LIGHT_COUNT - 1 DO
IF lights[N] <> 0 THEN
lights[N].On := value;
END_IF
END_FORTo apply dimming only where supported, query the additional interface:
VAR
dimmable : IDimmable;
dimmingValue : LREAL;
END_VAR
FOR N := 0 TO LIGHT_COUNT - 1 DO
IF lights[N] <> 0 THEN
IF __QUERYINTERFACE(lights[N], dimmable) THEN
dimmable.DimmingValue := dimmingValue;
END_IF
END_IF
END_FORThe caller does not need a separate array for every light type. It can operate on common behavior and query optional behavior only when needed.
This is useful in automation frameworks because real systems rarely contain perfectly uniform devices. A project may contain standard hardware, optional hardware, simulated hardware, and customer-specific devices in the same control concept.
Interfaces as Communication Protocols
An interface is more than a list of methods and properties. In a PLC framework, it becomes part of the communication protocol between function blocks.
For example, there are at least two different ways to put a device into operation.
One interface may describe commands:
INTERFACE IStartStopOperation
METHOD start
METHOD stopAnother may describe cyclic intent:
INTERFACE IRequestOperation
METHOD requestOperationBoth can be valid. They do not mean the same thing.
start() is an event. It says something should happen now. requestOperation() can mean
that operation is requested as long as the method is called cyclically. That difference is
important for watchdogs, communication loss, and state-machine behavior.
The interface alone does not explain all of this. The convention must be documented and used consistently.
Practical Guidelines
Use inheritance when:
- the relationship is a clear
is-arelationship - the hierarchy is shallow
- the base behavior is stable
- derived blocks should really inherit the base API
Use interfaces when:
- different devices share a behavior but not an implementation
- a caller should depend on capability, not concrete type
- optional features must be queried at runtime
- a framework needs replaceable components
- simulation and real hardware should use the same protocol
Avoid both when a plain function block or composition is simpler. OOP is a tool, not a certificate of adulthood.
Limitations
There are several practical limits.
First, interface references must be checked before use.
IF device <> 0 THEN
device.On := TRUE;
END_IFSecond, a REFERENCE TO must be checked with __ISVALIDREF() before access.
IF __ISVALIDREF(lightReference) THEN
lightReference.On := TRUE;
END_IFThird, OOP does not remove PLC constraints. Code still runs in scan cycles. It must stay deterministic. It must be easy to debug online. It must not hide timing behavior behind nice-looking abstractions.
Fourth, interfaces do not replace documentation. They define the callable surface. They do not fully define the expected behavior, timing, error handling, or reset semantics.
Conclusion
Inheritance and interfaces are both useful in TwinCAT Structured Text, but they solve different problems.
Inheritance shares structure and behavior inside a small hierarchy. It is useful when the relationship is stable and obvious.
Interfaces define contracts. They are better when different objects must provide the same capability without sharing the same base class.
For PLC frameworks, interfaces usually scale better. They let the application depend on behavior instead of concrete implementation. They also make simulation, testing, and customer-specific variation easier to manage.
The best design usually starts before coding:
Define the protocol first. Then implement the devices behind it.
