State Machine Design

State machines are a useful and widely used concept in automation. It is easy to build a state machine that overtime, becomes an untameable beast. Before you start a state machine, you should ask yourself if you need one

The best functioning state machines are ones in which any state can be entered and exited at any time, without breaking the state machine. It is possible that entering or exiting breaks the actual machine, but the state machine should self recover. This is a very important concept when developing within the context of Piper. Piper is an example of a pure state machine, where the Piper State is a state machine and the Piper Substate is a Sequence Machine. Like it or not, Piper will force the entire system into whatever state it is commanded, without notice. It will ALWAYS start at Substate (read sequence step) 0 and increase in substates that have been requested, until it reaches completion, or is canceled abruptly by the user or other parts of the system.

Calling function blocks, or performing operations WITHIN a Piper state or substate is HIGHLY discouraged. The same applies to state and sequence machines. Doing so will cause the function blocks to pickup partway through their operation and will cause bugs.

STATE_READY:
        
    liftSequence.step =    LIFT_READY;

    if( moveTarget ){
        state = STATE_MOVE_ABSOLUTE;
        moveAbs.position = TargetPos;
    }
    else if( moveZero ){
        state = STATE_MOVE_ABSOLUTE;
        moveAbs.position = 0;
    }
    else if( executeLift ){
        state = STATE_LIFT;
    }    

STATE_MOVE_ABSOLUTE:

    // State with a fully contained function block
    // The moveAbs block is configured before entering the state
    //  by populating the position based on a command

    IF moveAbs.error THEN

        Alarm( "Motion Error!" );

        state = STATE_READY;

    ELSIF moveAbs.done THEN

        state = STATE_READY;

    ELSIF NOT moveAbs.busy THEN
        // State execution
        moveAbs.execute := TRUE;
        
    END_IF

STATE_LIFT:

    //Fully contained state, with partially contained sequence.
    //  The state is responsible for initiating a sequence.
    //  The sequence is implemented elsewhere, and ends in the DONE or ERROR step
    //  Any step other than READY, DONE or ERROR is part of the process, 
    //  and should be ignored by this state machine

    CASE liftSequence.step  OF
        LIFT_READY:

            status = "Lift Sequence Started"    
            state = STATE_READY;
            liftSequence.step = LIFT_START_SEQUENCE;

        LIFT_DONE:

            status = "Lift Sequence Finished"    
            state =                 STATE_READY;
            liftSequence.step =    LIFT_READY;

        LIFT_ERROR:

            Alarm( "Lift Sequence Errored!" );
            status = "Lift Sequence Errored"    
            state =                 STATE_READY;
            liftSequence.step =    LIFT_READY;

        DEFAULT:

            status = "Executing Lift Sequence"    

    END_IF 

...

moveTarget =    0;
moveZero =      0;
executeLift =   0;

moveAbs();
moveAbs.execute := FALSE;