Menu

Tutorial

Peter Szabados
Attachments
CdPlayer1.jpg (28501 bytes)
CdPlayer2.jpg (35577 bytes)
CdPlayerComposite.jpg (31611 bytes)

Simple state machine

This is a sample state machine of a CD player. It is based on an example in the Boost Meta State Machine documentation.

Here is the code implementing this state machine:

:::java
public class Main {

    private static enum States {
        Empty, Stopped, Playing, Paused, Open
    }

    private static enum Events {
        CdDetected, Play, Stop, Pause, OpenClose
    }

    private static class ActionHandler implements
            ITransitionAction<States, Events> {

        private String action;

        public ActionHandler(String action) {
            this.action = action;
        }

        @Override
        public void onTransition(IState<States, Events> fromState,
                IState<States, Events> toState, Events event) {
            System.out.println(fromState.getId()+": "+
                event+"/"+action+" -> "+toState.getId());
        }

    }

    private static class EntryExitHandler implements IEntryExitAction<States, Events> {

        @Override
        public void onEnter(IState<States, Events> state, Events event) {
            System.out.println("Entering "+state.getId()+
                    " ("+event+")");
        }

        @Override
        public void onExit(IState<States, Events> state, Events event) {
            System.out.println("Exiting "+state.getId()+
                    " ("+event+")");
        }

    }

    public static void main(String[] args) {
        EntryExitHandler entryExitHandler = new EntryExitHandler();
        StateMachineBuilder<States, Events> stateMachineBuilder =
                new StateMachineBuilder<States, Events>();
        SubStateMachineBuilder<States, Events> mainStateMachine =
                stateMachineBuilder.get();

                        mainStateMachine.addState(States.Empty).setEntryExitAction(entryExitHandler);
        mainStateMachine.addState(States.Stopped).setEntryExitAction(entryExitHandler);
        mainStateMachine.addState(States.Playing).setEntryExitAction(entryExitHandler);
        mainStateMachine.addState(States.Open).setEntryExitAction(entryExitHandler);
        mainStateMachine.addState(States.Paused).setEntryExitAction(entryExitHandler);

        mainStateMachine.setInitialState(States.Empty)

            .addTransition(States.Empty,    Events.CdDetected,
                new ActionHandler(Actions.StoreCdInfo),    States.Stopped)
            .addTransition(States.Empty,    Events.OpenClose,
                new ActionHandler(Actions.OpenDrawer),     States.Open)
            .addTransition(States.Stopped,  Events.Play,
                new ActionHandler(Actions.StartPlayback),  States.Playing)
            .addTransition(States.Stopped,  Events.OpenClose,
                new ActionHandler(Actions.OpenDrawer),     States.Open)
            .addTransition(States.Playing,  Events.Pause,
                new ActionHandler(Actions.PausePlayback),  States.Paused)
            .addTransition(States.Playing,  Events.Stop,
                new ActionHandler(Actions.StopPlayback),   States.Stopped)
            .addTransition(States.Playing,  Events.OpenClose,
                new ActionHandler(Actions.StopAndOpen),    States.Open)
            .addTransition(States.Paused,   Events.Pause,
                new ActionHandler(Actions.ResumePlayback), States.Playing)
            .addTransition(States.Paused,   Events.Stop,
                new ActionHandler(Actions.StopPlayback),   States.Stopped)
            .addTransition(States.Paused,   Events.OpenClose,
                new ActionHandler(Actions.StopAndOpen),    States.Open)
            .addTransition(States.Open,     Events.OpenClose,
                new ActionHandler(Actions.CloseDrawer),    States.Empty);

        stateMachine = stateMachineBuilder.create();

        // process events
        stateMachine.processEvent(Events.OpenClose);
        stateMachine.processEvent(Events.OpenClose);
        stateMachine.processEvent(Events.CdDetected);
        stateMachine.processEvent(Events.Play);
        stateMachine.processEvent(Events.Pause);
        stateMachine.processEvent(Events.Pause);
        stateMachine.processEvent(Events.Pause);
        stateMachine.processEvent(Events.Stop);
        stateMachine.processEvent(Events.Play);
        stateMachine.processEvent(Events.OpenClose);
    }

}

First, create an instance of the master builder class StateMachineBuilder. Next, create the states and transactions using the SubStateMachineBuilder class that can be acquired by calling StateMachineBuilder.get(). When the state machine is ready, call StateMachineBuilder.create() to create the state machine itself and call processEvent() to give it events.

The output of this program shows what states the state machine goes through and what actions are taken:

Entering Empty (null)
Exiting Empty (OpenClose)
Empty: OpenClose/OpenDrawer -> Open
Entering Open (OpenClose)
Exiting Open (OpenClose)
Open: OpenClose/CloseDrawer -> Empty
Entering Empty (OpenClose)
Exiting Empty (CdDetected)
Empty: CdDetected/StoreCdInfo -> Stopped
Entering Stopped (CdDetected)
Exiting Stopped (Play)
Stopped: Play/StartPlayback -> Playing
Entering Playing (Play)
Exiting Playing (Pause)
Playing: Pause/PausePlayback -> Paused
Entering Paused (Pause)
Exiting Paused (Pause)
Paused: Pause/ResumePlayback -> Playing
Entering Playing (Pause)
Exiting Playing (Pause)
Playing: Pause/PausePlayback -> Paused
Entering Paused (Pause)
Exiting Paused (Stop)
Paused: Stop/StopPlayback -> Stopped
Entering Stopped (Stop)
Exiting Stopped (Play)
Stopped: Play/StartPlayback -> Playing
Entering Playing (Play)
Exiting Playing (OpenClose)
Playing: OpenClose/StopAndOpen -> Open
Entering Open (OpenClose)

Guards, internal and completion transitions

These concepts are shown on a modified version of the previous example.

To implement guards, implement the IGuard interface. Also, the GuardNot, GuardAnd and GuardOr classes can be used for logical combination of guards.

:::java
isCdDetected = new GuardCdDetected<States, Events>();
isLastTrack = new GuardLastTrack<States, Events>();
...
mainStateMachine.addTransition(States.Playing, Events.FastForward,
        new ActionHandler("StopPlayback"), States.Stopped,
        isLastTrack);

Internal transitions are similar to normal transitions except that they don't exit the state. The difference between a self-transition (a transition with both endpoints on the same state) and an internal transition is that for a self-transition the exit and entry actions of a state is called, while for an internal transition it is not.

In this example, there are two transitions from state Playing to the event FastForward. It can only happen if these transitions have mutually exclusive guards. We use GuardNot with the same guard as before to ensure it.

:::java
mainStateMachine.addInternalTransition(States.Playing, Events.FastForward,
        new ActionHandler("ForwardTrack"),
        new GuardNot<States, Events>(isLastTrack));

Completion transitions are triggered automatically after each transition as well as right after creating the state machine with StateMachineBuilder.create(). They are represented by a null event in callbacks. They are usually guarded, although it is not required.

:::java
mainStateMachine.addTransition(States.Empty, null,
        new ActionHandler("StoreCdInfo"), States.Stopped,
        isCdDetected);

Composite states

The above example is modified so that the Playing and Paused states are now in one state. This is a logical choice for a composite state, as most transitions are the same from these two states.

A composite state is created with the call of the addCompositeState() method of the state machine. It returns a ICompositeStateBuilder object, which has an internal SubStateMachineBuilder of its own, which can be used to build the sub state machine the same way as the master state machine.

:::java
SubStateMachineBuilder<States, Events> statePlaying =
        mainStateMachine.addCompositeState(States.Playing).
    setEntryExitAction(entryExitHandler).
    getStateMachineBuilder();

statePlaying.addState(States.Playback).setEntryExitAction(entryExitHandler);
statePlaying.addState(States.Paused).setEntryExitAction(entryExitHandler);
statePlaying.setInitialState(States.Playback)

    .addTransition(States.Playback,          Events.Pause,
        new ActionHandler(Actions.PausePlayback),   States.Paused)
    .addTransition(States.Paused,          Events.Pause,
        new ActionHandler(Actions.ResumePlayback),   States.Playback);

The transitions initiating from the Playing state remain the same as in the previous example. However, since Playback and Paused is a substate of Playing, these transitions apply for any of these states, so the transitions initiating from Paused in the earlier example are no longer needed.


Related

Wiki: Home

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.