Working with Function Blocks
The Adapnex SDK adopts the Function Block pattern from the IEC 61131-3 industrial standard. Unlike standard C++ functions which are stateless, Function Blocks maintain internal memory (state) across execution cycles.
To implement this pattern in C++, we utilize Functors (Function Objects). These are C++ classes that overload the function call operator operator(), allowing instances to be invoked as if they were functions.
The Cyclic Update Pattern
In industrial control, logic is executed cyclically. Your application’s Update() loop runs repeatedly (e.g., every 10ms). Function blocks like Timers and Triggers must be called every cycle to update their internal clocks and state machines.
Comparison: IEC 61131-3 vs. C++
The Adapnex C++ syntax is designed to be similar to Structured Text (ST) syntax. Below is a comparison showing how a simple "Motor Delay" program is structured in both languages.
| IEC 61131-3 (Structured Text) | Adapnex C++ |
|---|---|
|
Passing Parameters
Function Blocks in Adapnex are flexible. You can interact with them using concise function arguments or by explicitly setting member variables.
Style 1: The Functor Argument (Compact)
Pass inputs and outputs directly into the function call. This is the most common pattern for logic where values change frequently.
-
Inputs are passed by value.
-
Outputs are passed by reference.
// Update logic AND read the result into 'motor_on' in one line
timer(start_signal, 5s, motor_on);
// Update logic AND read result + elapsed time
timer(start_signal, 5s, motor_on, elapsed_time);
Style 2: Member Access (Explicit)
Alternatively, you can set input members manually before calling the block, and read output members afterwards. This is useful for fixed configuration parameters (like Hysteresis thresholds) that do not change every cycle, keeping your Update() calls cleaner.
class TempControl : public Task {
Hysteresis<float> temp_switch;
public:
TempControl() {
// 1. Set static configuration in Constructor
temp_switch.SET = 30.0f;
temp_switch.RESET = 25.0f;
}
void Update() override {
// 2. Set dynamic inputs
temp_switch.IN = current_temp;
// 3. Update internal state (using the pre-set members)
temp_switch();
// 4. Read Output
if (temp_switch.OUT) {
enable_fan();
}
}
};
Critical: Scope & Lifetime
A Function Block must remember its past. Therefore, you cannot declare a Function Block as a local variable inside your Update() loop.
If you declare a block locally, it is created and destroyed within a single execution cycle. Its internal state is lost, and the timer will never increment beyond 0.