State - Krystian-L-Lis/Stage GitHub Wiki

#Guide #State

Overview

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 ;) ).

Core Elements

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

Request State Change

Upon calling Request, the StateManager performs the following checks:

  1. State Validity: Verifies that the requested I_State is valid.
  2. Entity Ownership: Ensures that the requested state belongs to the associated entity.
  3. 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.

Execution

The StateManager uses the runtime for execution, so there is no need to call its Execute method explicitly. On each runtime iteration:

  1. The StateManager pops one request from the queue.
  2. It evaluates whether the requested state change can proceed by checking:
    • The current state’s CanDeactivate method.
    • The requested state’s CanActivate method.
  3. 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.

Event Emission

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 >

⚠️ **GitHub.com Fallback** ⚠️