State - Krystian-L-Lis/Stage GitHub Wiki
#Guide #State
States and state machines are basic building block of any PLC program. They happen to be vital for keeping complex systems organized and predictable with clear state transition and responsibility. Ideally they should take an exclusive control over a system during their execution to avoid any undefined behaviour.
In many implementations, these state machines are driven by large, ever growing CASE OF
statements. While this approach may work for smaller applications, it can become unwieldy as complexity increases. Adding new behaviour often prompts questions: How should the new state interact with others? What unintended side effects could arise? Where should state transition logic reside? And what happens if a sub-state is needed between two existing states?
Moving toward a more modular, interface-driven architecture alleviates these issues by encapsulating behaviour within discrete components and eliminating the need for giant enumeration blocks.
This framework implements state pattern, compelling developers to explicitly define when a state CanActivate
, when it CanDeactivate
, and what actions should occur CanActivate
, OnExecution
, and OnExit
. States themselves can be thought of as interchangeable DVD that encoded specific behaviours, while the StateManager
acts as the DVD-Player that runs them(I hope that this analogy is still clear ;) ).
State
as a state
The State
function block represents an abstract base for defining system states. It provides the foundation for implementing state-specific behaviour in a modular, maintainable way.
Unlike the broader Stage approach, the State
function block deviates slightly from the "composition over inheritance" principle by using inheritance instead of an composition. This decision is deliberate, as it streamlines development by providing default implementations for common behaviours, eliminating the need for repetitive code. For example, using inheritance allows developers to override only the necessary methods, while default behaviours for unimplemented methods are automatically provided. Using a raw interface, in contrast, would require all methods to be implemented explicitly, even if their logic is trivial or redundant.
Below is a standard implementation of the State
function block. Let’s first create the context in which the state machine will operate. The I_StateManager
interface, which will be discussed later, is part of this context together of cached interface of each state.
Example:
TYPE Machine_Ctx:
STRUCT
smgr: StateManager; // State manager interface
iOn: I_State; // Reference to the "On" state
iOff: I_State; // Reference to the "Off" state
bStartStopButton: BOOL; // Button to start/stop the motor
bOverload: BOOL; // Overload status
bMotorRun: BOOL; // Motor running status
END_STRUCT
END_TYPE
Next, this context is injected into a base state that serves as a template for other specific states.
Example:
FUNCTION_BLOCK ABSTRACT Machine_State EXTENDS State
VAR
_ctx: REFERENCE TO Machine_Ctx; // Reference to the shared context
END_VAR
METHOD FB_Init : BOOL
VAR_INPUT
// Pass context to the base state
ctx: REFERENCE TO Machine_Ctx;
END_VAR
_ctx REF= ctx; // Initialize the context reference
END_METHOD
Now, states can be defined to implement specific behaviours.
Example:
FUNCTION_BLOCK Machine_State_On EXTENDS Machine_State
METHOD FB_Init : BOOL
// Cache the "On" state interface for easy access
_ctx.iOn := THIS^;
END_METHOD
METHOD PROTECTED OnEntry
// Actions to perform when entering the "On" state
_ctx.bMotorRun := TRUE;
END_METHOD
METHOD PROTECTED OnExecute
// Actions to perform during the "On" state
IF _ctx.bOverload THEN
_ctx.iMgr.Request(_ctx.iOff); // Request transition to "Off" state
ELSIF _ctx.bStartStopButton THEN
_ctx.bStartStopButton := FALSE; // Clear the button press
_ctx.iMgr.Request(_ctx.iOff); // Request transition to "Off" state
END_IF
END_METHOD
METHOD PROTECTED CanActivate
// Conditions to activate the "On" state
CanActivate := NOT _ctx.bOverload;
END_METHOD
END_FUNCTION_BLOCK
Example:
FUNCTION_BLOCK Machine_State_Off EXTENDS Machine_State
METHOD FB_Init : BOOL
// Cache the "Off" state interface for easy access
_ctx.iOff := THIS^;
END_METHOD
METHOD PROTECTED OnEntry
// Actions to perform when entering the "Off" state
_ctx.bMotorRun := FALSE;
END_METHOD
METHOD PROTECTED OnExecute
// Actions to perform during the "Off" state
IF _ctx.bStartStopButton THEN
_ctx.bStartStopButton := FALSE; // Clear the button press
_ctx.iMgr.Request(_ctx.iOn); // Request transition to "On" state
END_IF
END_METHOD
END_FUNCTION_BLOCK
Notice that not all methods were implemented. CanActivate
and CanDeactivate
methods return TRUE
by default.
StateManager
as a state manager
Now that the states are defined, a manager is needed to handle their lifecycle. The StateManager
function block fulfils this role. It tracks and coordinates state transitions. The manager requires an Entity
to register states within the system. It uses an internal buffer for evaluating state change requests.
Example:
FUNCTION_BLOCK Machine
VAR
_entityStart: EntityStart; // The entity managing this machine
_ctx: Machine_Ctx; // Shared context
_smgr: StateManager(_self) := (Proxy := _ctx.iSmgr) // State Manager. Proxy allows state manager isntance to be assigned to the interface variable within the declaration line.
_on: Machine_State_On(_ctx); // "On" state instance
_off: Machine_State_Off(_ctx); // "Off" state instance
_entityEnd: EntityEnd; // Finalize entity declaration
END_VAR
END_FUNCTION_BLOCK
Upon calling Request
, the StateManager
performs the following checks:
-
State Validity: Verifies that the requested
I_State
is valid. - Entity Ownership: Ensures that the requested state belongs to the associated entity.
- Queue Duplication: Confirms the state is not already present in the evaluation queue.
If all checks pass, the I_State
is added to the buffer for evaluation.
The StateManager
uses the runtime for execution, so there is no need to call its Execute
method explicitly. On each runtime iteration:
- The
StateManager
pops one request from the queue. - It evaluates whether the requested state change can proceed by checking:
- The current state’s
CanDeactivate
method. - The requested state’s
CanActivate
method.
- The current state’s
- If the transition is valid, the state change is executed, which involves:
- Calling the current state’s
OnExit
method. - Activating the new state by calling its
OnEntry
method.
- Calling the current state’s
When a state change is successfully conducted, the StateManager
emits a signal containing the state’s I_Tag
. This signal is automatically added to the Entity
instance during StateManager
initialization and can be accessed using the ID _stateChangeEvent
. To further evaluate the context of the state change, the following properties of the StateManager
can be utilized:
-
Current : I_State
- Represents the currently active state before the transition. -
Previous : I_State
- Stores a reference to the state that was active before current state. -
Pretender : I_State
- Refers to the state that is to be activated.
< Previous | Home | Next >