Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

State Machine Design in C++


StateMachine Usage

StateMachine is used as an inherited base class. The Motor class is an example of how to use it (see Listings Four and Five). Motor implements our hypothetical motor-control state machine, where clients can start the motor, at a specific speed, and stop the motor. The SetSpeed and Halt public functions are the external events into the Motor state machine. Note that to the caller an external event is just a function call. The state machine implementation details are hidden from the client's view of this class. SetSpeed takes event data, as evidenced by the pointer to the MotorData structure, which contains the motor speed. This data structure will be deleted upon completion of the state processing, so it is imperative that the structure inherit from EventData and be created using operator new before the function call is made.

Listing Four: Defines Motor state machine class

#ifndef MOTOR_H
#define MOTOR_H
#include "StateMachine.h"

// structure to hold event data passed into state machine
struct MotorData : public EventData
{
    int speed;
};

// the Motor state machine class
class Motor : public StateMachine
{
public:
    Motor() : StateMachine(ST_MAX_STATES) {}

    // external events taken by this state machine
    void Halt();
    void SetSpeed(MotorData*);
private:
    // state machine state functions
    void ST_Idle();
    void ST_Stop();
    void ST_Start(MotorData*);
    void ST_ChangeSpeed(MotorData*);

    // state map to define state function order
    BEGIN_STATE_MAP
        STATE_MAP_ENTRY(ST_Idle)
        STATE_MAP_ENTRY(ST_Stop)
        STATE_MAP_ENTRY(ST_Start)
        STATE_MAP_ENTRY(ST_ChangeSpeed)
    END_STATE_MAP

    // state enumeration order must match the order of state
    // method entries in the state map
    enum E_States { 
        ST_IDLE = 0,
        ST_STOP,
        ST_START,
        ST_CHANGE_SPEED,
        ST_MAX_STATES
    };
};
#endif //MOTOR_H

Listing Five: Implements Motor class

#include <assert.h>
#include "Motor.h"

// halt motor external event
void Motor::Halt(void)
{
    // given the Halt event, transition to a new state based upon 
    // the current state of the state machine
    BEGIN_TRANSITION_MAP                      // - Current State -
        TRANSITION_MAP_ENTRY (EVENT_IGNORED)  // ST_Idle
        TRANSITION_MAP_ENTRY (CANNOT_HAPPEN)  // ST_Stop
        TRANSITION_MAP_ENTRY (ST_STOP)        // ST_Start
        TRANSITION_MAP_ENTRY (ST_STOP)        // ST_ChangeSpeed
    END_TRANSITION_MAP(NULL)
}

// set motor speed external event
void Motor::SetSpeed(MotorData* pData)
{
    BEGIN_TRANSITION_MAP                      // - Current State -
        TRANSITION_MAP_ENTRY (ST_START)       // ST_Idle       
        TRANSITION_MAP_ENTRY (CANNOT_HAPPEN)  // ST_Stop       
        TRANSITION_MAP_ENTRY (ST_CHANGE_SPEED)// ST_Start      
        TRANSITION_MAP_ENTRY (ST_CHANGE_SPEED)// ST_ChangeSpeed
    END_TRANSITION_MAP(pData)
}

// state machine sits here when motor is not running
void Motor::ST_Idle() 
{
}

// stop the motor 
void Motor::ST_Stop()
{
    // perform the stop motor processing here
    // transition to ST_Idle via an internal event
    InternalEvent(ST_IDLE);
}

// start the motor going
void Motor::ST_Start(MotorData* pData)
{
    // set initial motor speed processing here
}

// changes the motor speed once the motor is moving
void Motor::ST_ChangeSpeed(MotorData* pData)
{
    // perform the change motor speed to pData->speed here
}

When the Motor class is created, its initial state is ST_Idle. The first call to SetSpeed transitions the state machine to the ST_Start state, where the motor is initially set into motion. Subsequent SetSpeed events transition to the ST_ChangeSpeed state, where the speed of an already moving motor is adjusted. The Halt event transitions to ST_Stop, where, during state execution, an internal event is generated to transition back to the ST_Idle state.

The state-machine engine knows which state function to call by using the state map. The state map maps the currentState variable to a specific state function. For instance, if currentState is 2, then the third state-map function pointer entry will be called (counting from zero). The state map is created using these three macros:

BEGIN_STATE_MAP
STATE_MAP_ENTRY
END_STATE_MAP

BEGIN_STATE_MAP starts the state map sequence. Each STATE_MAP_ENTRY that follows has as an argument a state function, which is added to the state map. END_STATE_MAP terminates the map. The completed state map just implements the pure virtual function GetStateMap defined within the StateMachine base class. Now the StateMachine base class can ask for all the state function pointers via this call.

Notice that we have to use the dastardly reinterpret_cast<> operator within the STATE_MAP_ENTRY macro to cast the derived class member function pointer to a StateMachine member function pointer. It is necessary to perform this upcast since the StateMachine base class has no idea what the derived class is. So, it is imperative that the entries provided to STATE_MAP_ENTRY are really member functions of an inheriting class and that they conform to the state function signature discussed earlier. Otherwise bad things will happen.

Each state function must have an enumeration associated with it. These enumerations are used to store the current state of the state machine. In Motor, E_States provides these enumerations, which are used later in the transition map. It is important that the enumeration order matches the order provided within the state map. This way, we can tie a state enumeration to a particular state function call. EVENT_IGNORED and CANNOT_HAPPEN are two other constants used in conjunction with these state enumerations. EVENT_IGNORED tells the state engine not to execute any state, just return and do nothing. CANNOT_HAPPEN tells the state engine to fault. This is an abnormal catastrophic failure condition that is never suppose to occur.

The last detail to attend to are the state transition rules. How does the state machine know what transitions should occur? The answer is the transition map, which is created using these macros:

BEGIN_TRANSITION_MAP
TRANSITION_MAP_ENTRY
END_TRANSITION_MAP

Each external event function has a transition map. BEGIN_TRANSITION_MAP starts the map. Each TRANSITION_MAP_ENTRY that follows indicates what the state machine should do based upon the current state. The number of entries in each transition map must match the number of state functions exactly. In our example we have four state functions, so we need four entries. The location of each entry matches the order of state functions defined within the state map. Thus, the first entry within the Halt function indicates an EVENT_IGNORED. This is interpreted to mean, "If a Halt event occurs while the current state is state Idle, just ignore the event." Similarly, the third entry means, "If a Halt event occurs while current is state Start, then transition to state Stop." END_TRANSITION_MAP terminates the map. The argument to this end macro is the event data, if any. Halt has no event data so the argument is a null pointer, but ChangeSpeed has data so it is passed in here.

The transition map is basically a lookup table indexed by the currentState variable to determine the course of action. This information is passed to the ExternalEvent function as an argument along with the event data. When the StateEngine function executes, it looks up the correct state function pointer by calling GetStateMap:

const StateStruct* 
pStateMap = GetStateMap();

Then, based upon the currentState variable, it calls one of the state functions in the map:

(this->*pStateMap[currentState].pStateFunc)(pDataTemp);

After the state function has a chance to execute, it deletes the event data, if any, before checking to see if any internal events were generated.

Generating Events

At this point we have a working state machine class. Let's see how to generate events to it. An external event is generated by creating the event data structure on the heap using new, assigning the structure member variables, and calling the external event function. Obviously, if the external event doesn't take event data then a data structure is not created. The following code fragment shows how a synchronous call is made. You could, of course, send the pointer to the data structure, along with some means of identifying the event, through an operating system message queue to be handled asynchronously by the destination task:

MotorData* pData = new MotorData;
pData->speed = 50;
motor.setSpeed(pData);

To generate an internal event from within a state function, call InternalEvent. If the destination doesn't accept event data, then the function is called with only the state you want to transition to:

InternalEvent(ST_IDLE);

In the example above, once the state function completes execution the state machine will transition to the ST_Idle state. If, on the other hand, event data needs to be sent to the destination state, then the data structure needs to be created on the heap and passed in as an argument:

MotorData* pData = new MotorData;
pData->speed = 100;
InternalEvent(ST_CHANGE_SPEED, pData);

Multithread Safety

To prevent preemption by another thread when the state machine is in the process of execution, the StateMachine class uses semaphores within the StateEngine function. Before the external event is allowed to execute, the semaphore is locked. When the external event and all internal events have been processed, the semaphore is unlocked, allowing another external event to enter the state machine instance.

Comments indicate where the semaphore lock and unlock should be placed if the application is multithreaded. Note that each StateMachine object should have its own instance of a semaphore. This prevents a single instance from locking a semaphore and preventing all other StateMachine objects from executing.

Benefits

Implementing a state machine using this method as opposed to the old switch statement style may seem like extra effort. However, the payoff is in a more robust design that is capable of being employed uniformly over an entire multithreaded system. Having each state in its own function provides easier reading than a single huge switch statement, and allows unique event data to be sent to each state. In addition, validating state transitions prevents client misuse by eliminating the side effects caused by unwanted state transitions.

This implementation offers easy use for the inheriting classes. With the macros it lets you just "turn the crank" without much thought given to the underlying mechanics of how the state engine operates. This allows you more time to concentrate on more important things, like the design of the state transitions and state function implementation.

Reference

[1] Sally Shlaer. Object Lifecycles (Prentice-Hall, Englewood Cliffs, NJ), 1992.


David Lafreniere has designed hardware and software over the past ten years. He currently works at PropHead Development, Inc., a software-consulting firm, designing software for a variety of embedded and PC-based applications. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.