You've met methods, EXTENDS and interfaces. Now we open the hood: the full anatomy of a method, properties that disguise getters and setters, real polymorphism across an array of devices, the ABSTRACT / FINAL controls that decide who may override what, and the question every engineer should ask: when NOT to use any of this.
A method is a tiny program bolted to an FB. Its header declares exactly what flows in, what flows out, what it keeps to itself, and who's allowed to call it. Click any coloured token below to see what it does.
Notice the return: you assign to the method's own name (Move := β¦), exactly like a FUNCTION. And Position isn't declared anywhere here. It is a member variable of the FB, reached through the implicit THIS^ every method carries.
A property looks like a plain variable to the caller (motor.Speed := 80;), but each access secretly runs code: a get accessor when you read it, a set accessor when you write it. That makes the accessor a good place to validate, clamp, scale or log without the caller ever knowing. Try writing an out-of-range speed:
PROPERTY Speed : REAL // looks like a variable to the outside (* get accessor β runs when someone READS motor.Speed *) Speed := fInternal; (* set accessor β runs when someone WRITES motor.Speed := x *) fInternal := LIMIT(0, Speed, 50); // clamp; 'Speed' here is the incoming value
This is where OOP pays for itself. An ARRAY OF I_Device holds a pump, a valve and a heater: totally unrelated FBs that each implement the same interface. The line code loops once and calls Stop() on each. No CASE, no type checks: every object runs its own override. Add a fourth device type next year and this loop never changes.
New to ARRAY[..] OF, STRUCT or ENUM? They're the data shapes underneath all this; see the Data Unit Types deep dive.
VAR devices : ARRAY[0..2] OF I_Device; // interface references END_VAR devices[0] := pump; devices[1] := valve; devices[2] := heater; FOR i := 0 TO 2 DO devices[i].Stop(); // SAME line β each runs its own Stop() END_FOR
Inheritance gives a child a lot of freedom, so IEC gives you brakes. These keywords let a base author say "you must fill this in" or "you may never change this", which turns a loose hierarchy into a contract the compiler enforces.
| keyword | on | what it means |
|---|---|---|
| ABSTRACT | method | No body here, just a signature. Any non-abstract child must override it. The compiler stops you forgetting. |
| ABSTRACT | FB | Can't be instantiated directly; it only exists to be extended. A blueprint, not a building. |
| FINAL | method | This override is the last word: no child may override it again. Use it to lock down critical logic. |
| FINAL | FB | Nothing may EXTEND it, so it is always a leaf of the tree. |
| PUBLIC / PROTECTED / PRIVATE / INTERNAL | method Β· property | Who may call it: anyone Β· this FB & its children Β· only this FB Β· only this library. |
FUNCTION_BLOCK ABSTRACT FB_Device // can't be instantiated METHOD ABSTRACT Stop : BOOL // no body β children MUST provide one // ββββββββββββββββββββββββββββββββββββ FUNCTION_BLOCK FB_Estop EXTENDS FB_Device METHOD FINAL Stop : BOOL // nobody may ever re-override this Energise := FALSE; // the one true safe state
Methods got all the new powers, so when is a plain action still the right call? There are two real cases, plus one refactor you'll reach for constantly.
πͺ Inside SFC: Sequential Function Chart steps fire actions on entry, while active and on exit. That is their native home, and methods don't slot into a step the same way.
βοΈ "Extract block": when one giant FB body is unreadable, split it into a few named actions that share the same variables. The body becomes easy to scan, and there is no new scope to reason about.
β οΈ Not for reuse across FBs: an action can't take parameters, return a value, or join an interface. As soon as you need any of those, it has to be a method.
π§ Rule of thumb: an action organises this FB's own code; a method exposes a reusable, testable, override-able capability to the outside world.
OOP is one tool among several. On a PLC it works best for device libraries, fleets of similar equipment, and code you'll ship to other people. Over-apply it and the code gets harder to work with.
β Reach for it
Many variants of one thing (10 drives, 30 valves). A reusable library. A device that must be swappable behind an interface. Logic worth unit-testing in isolation.
β Leave it alone
A one-off interlock. A simple latch. Code a maintenance tech must read at 3am. Deep inheritance chains built "because we can", where every SUPER^ hop is one more place to look.
Good automation code mixes paradigms without ceremony: ladder for the interlocks an electrician will debug, a clean OOP library for the motion devices and plain ST for the maths. Use the abstraction that makes the next person faster, and stop there.
You declare FUNCTION_BLOCK ABSTRACT FB_Device with an ABSTRACT method Stop. A colleague writes pump : FB_Device; and the compiler refuses. Why?
A loop calls devices[i].Stop() over an ARRAY OF I_Device. You add a brand-new FB_Damper that implements I_Device. What happens to the loop?