Declarative vs. Imperative command handling in axis state machines — should mapp Framework be rewritten?
Hi everyone,
I’d like to open a discussion about two fundamentally different approaches to structuring axis state machines when using MpAxisBasic (or similar function blocks). I’ve seen both patterns in real projects and I think the difference is worth talking about — and I have some uncomfortable questions at the end.
Approach 1: Imperative (commands set inside states)
This is the pattern used in the mapp Framework. Commands like .Power, .Home, .MoveAbsolute, .Stop, etc. are set to TRUE or FALSE inside the individual states of the CASE block:
CASE State OF
STATE_POWER_ON:
MpAxisBasic.Power := AxisControl.Command.Power;
STATE_REFERENCE:
MpAxisBasic.Home := TRUE;
STATE_MOVE_TO_HOME_POSITION:
MpAxisBasic.MoveAbsolute := TRUE;
STATE_STOPPING:
MpAxisBasic.Stop := MpAxisBasic.PowerOn;
...
END_CASE
Because commands are scattered across states and set imperatively, a dedicated reset action (ResetAllAxisCommands) is needed to clear all commands on every state transition. Forgetting to call it can leave a command “stuck” on TRUE.
Approach 2: Declarative (commands derived from state)
In this approach, the CASE block only handles state transitions — it decides when to move to the next state. All MpAxisBasic commands are then assigned in one centralized place after the CASE block, as boolean expressions derived from the current state:
CASE State OF
(* only transition logic here — no direct FB command manipulation *)
END_CASE
(* All commands in one place, derived from state *)
MpAxisBasic.Power := State >= AXIS_STATE_POWER_ON;
MpAxisBasic.Home := State = AXIS_STATE_HOME;
MpAxisBasic.MoveAbsolute := State = AXIS_STATE_MOVEABSOLUTE;
MpAxisBasic.MoveAdditive := State = AXIS_STATE_MOVEADDITIVE;
MpAxisBasic.MoveVelocity := State = AXIS_STATE_MOVEVELOCITY;
MpAxisBasic.Stop := State = AXIS_STATE_STOP_MOVEMENT;
MpAxisBasic.JogPositive := State = AXIS_STATE_JOGPOSITIVE;
MpAxisBasic.JogNegative := State = AXIS_STATE_JOGNEGATIVE;
MpAxisBasic.ErrorReset := State = AXIS_STATE_ERROR_RESET;
MpAxisBasic.AutoTune := State = AXIS_STATE_AUTOTUNE;
MpAxisBasic();
No ResetAllAxisCommands is needed — if the state changes, the expressions automatically evaluate to FALSE. There is zero risk of a “dangling” command.
Comparison
| Aspect | Imperative (inside states) | Declarative (derived from state) |
|---|---|---|
| Where commands are set | Spread across individual states | One centralized block |
| Command reset | Explicit reset action required | Automatic — expression evaluates to FALSE |
| Bug risk | Higher (forgotten reset = stuck command) | Lower (state unambiguously determines outputs) |
| Readability | Must scan entire CASE to see what’s active | Complete command mapping visible at a glance |
| Adding a new state | Must remember to update reset logic | Just add one line with a condition |
| Flexibility | Easier to do conditional/partial commands within a state | Slightly more verbose for edge cases |
The declarative approach essentially follows the Moore machine pattern — outputs are a pure function of the current state. The imperative approach is closer to a Mealy machine, where outputs depend on transitions and actions within states.
Questions for discussion
- Which pattern do you use in your projects, and why?
- Have you ever been bitten by a “stuck command” bug in the imperative approach?
- Are there situations where the imperative approach is genuinely better (e.g., complex sequences where a command needs to be set conditionally within a state)?
- For those using mapp Framework — have you considered refactoring to the declarative pattern, or does the framework’s structure make that impractical?
Now, let me raise some more provocative points:
-
The mapp Framework is marketed as a “best practice” template that many developers blindly copy into their projects. If the imperative pattern is inherently more error-prone, are we propagating a flawed design pattern across the entire B&R ecosystem?
-
Should the mapp Framework be rewritten to use the declarative approach? The
ResetAllAxisCommandsaction is essentially a band-aid for a problem that the declarative pattern eliminates entirely. Why maintain a workaround when you can remove the root cause? -
How many undiscovered bugs are lurking in production machines right now because someone extended the mapp Framework state machine with a new state and forgot to call
ResetAllAxisCommandsat the right place? The imperative pattern essentially requires the developer to be perfect — every time, in every state, for every command. The declarative pattern requires no such perfection. -
If safety is our top priority in machine control, shouldn’t we choose the architecture that is inherently safer by design — where it’s structurally impossible to leave a motion command active by accident? Or are we comfortable relying on code reviews and testing to catch what the architecture could prevent?
-
Is the B&R community too attached to “this is how we’ve always done it”? The declarative pattern isn’t new, isn’t exotic, and isn’t harder to understand — it’s arguably easier. So why isn’t it the standard?
I’m not trying to bash the mapp Framework — it does many things well. But I think this particular design choice deserves scrutiny, especially when it directly impacts motion safety. A stuck MoveVelocity := TRUE is not a theoretical concern — it’s a motor running when it shouldn’t be.
What do you think — is it time for a paradigm shift?