How to write a Finite State Machine

A simple example should clarify how to implement a finite state machine based on the wns::fsm::FSM template. A finite state machine for a light control, which switches the light at night on if a movement was detected and switches it off again after a certain timer has elapsed should be designed. The following sections will show the necessary steps to implement the ligt control based on wns::fsm::FSM. The following sub-sections represent the necessary steps:

Modelling the FSM Light Control

Recipe: Find a model which describes the problem based on a finite state machine

First of all we need to find a model for the finite state machine. We know that the Lamp Control should turn on the lamp if a movement was detected and if it’s currently night. Thinking about this we can derive that we need at least three states for the FSM to represent the different behaviours: Day, Night, LampOn, since the FSM must react different to input if it’s day or night and different again, if the light is currently turned on (e.g., at day the light must not be turned on if a movement was detected).

At run time the finite state machine will be in one of these three states. Being in these states it needs to react to certain signals (input) with some action. The action may also include a state change (e.g., the finite state machine is in state “Night” and the signal “light” arrives, the state would change to “Day”). The signals needed here are: dark (to switch from Day to Night), light (to switch from Nigth to Day), movementDetcted (to switch the Lamp on) and timerElapsed (to switch the lamp off after some time). With these states and signals, the model of our finite state machine “Lamp Control” looks like this:

_images/graphviz/4896ee1db3ab6827786d2dd0c7cce5f18e8de399.png

Summary of the model:

  • It has three states: #. Day #. Night #. LampOn
  • It has four signals: #. light #. dark #. movementDetected #. timerElapsed

After this model has been identified it needs to be transfered to C++. The framework provided by wns::fsm::FSM will help us to get the model implemented fast and efficiently. Most of the following steps contain a sentence describing in recipe style what needs to be done.

An interface (abstract class) to define the signals

Recipe: Write a class having all signals of the finite state machine as pure virtual functions.

The signals have to be modeled as pure virtual methods of a class. Make sure the class is really an interface (does not contain any variables, is stateless). The States classes will be derived from this interface later. The interface for our signals looks like this:

		class LightControlSignals
		{

		public:
			// the four signals as pure virtual functions
			virtual LightControlSignals*
			light() = 0;

			virtual LightControlSignals*
			dark() = 0;

			virtual LightControlSignals*
			movementDetected() = 0;

			virtual LightControlSignals*
			timeElapsed() = 0;

 			// abstract interface with virtual functions needs
 			// virtual destructor (otherwise g++ complains)
 			virtual
 			~LightControlSignals()
 			{}

 		protected:
 			// only called from derived classes
 			LightControlSignals()
 			{}

		private:
			LightControlSignals(const LightControlSignals&);
		};

Note

The copy constructor has been disallowed in order to prevent copying of states. The default constructor is protected: since an interface can’t be instantiated the constructor can only be called by a derived class.

States share Variables

Recipe: Write a class containing all the variables of the FSM shared by the different states

The finite state machine is also characterized by a set of variables which it holds. All these variables have to be implemented in a struct or class which will be used by the wns::fsm::FSM template later. For simplicity our example assumes the class definig the variables is called “Lamp” and contains only one variable called “on”:

struct Lamp
{
	Lamp() :
		on(false)
	{}

	bool on;
};

An interface for the FSM “Light Control”

Recipe: Make a handy typedef to the interface of your FSM

The template wns::fsm::FSM instantiated with Variables and Signal will not implement the finite state machine itself. It will provide an interface for the final finite state machine. To have a handy alias create a typedef to this interface with the signal interface from above (“LightControlSignals”) and the variables that characterize the finite state machine (“Lamp”):

typedef FSM<LightControlSignals, Lamp> LightControlInterface;

Implementation of the finite state machine

Recipe: Derive the final implementation from the interface (aliased by the typedef)

We’re going to use the previously introduced typedef defining our FSM to derive a real implementation of this FSM. All methods of the signal interface (“LightControlSignals”) need to be defined here:

class LightControl :
	public LightControlInterface
{
public:
	LightControl(const LightControlInterface::VariablesType& v);

	void
	movementDetected();

	void
	timeElapsed();

	void
	light();

	void
	dark();

	Lamp&
	lamp()
	{
		return getVariables();
	}
};

The implementation of the methods forward the method call simply to the current state object:

FSMTest::LightControl::LightControl(const FSMTest::LightControlInterface::VariablesType& v) :
	FSMTest::LightControlInterface(v)
{
	changeState(createState<Night>());
}

void
FSMTest::LightControl::movementDetected()
{
	changeState(getState()->movementDetected());
}

void
FSMTest::LightControl::timeElapsed()
{
	changeState(getState()->timeElapsed());
}

void
FSMTest::LightControl::light()
{
	changeState(getState()->light());
}

void
FSMTest::LightControl::dark()
{
	changeState(getState()->dark());
}

Note

The constructor must always take a const reference to the type exported as “VariablesType” by the FSM interface (“LightControlInterface”, the typedef)

Note

The constructor must set the FSM to a valid initial state (here Night).

Implementing the behaviour in the different states

Recipe: Implement a class for each state

Each state is represented by a different class. These classes will be instantiated and deleted by the FSM. Here the definition of the states (Day, Night and LampOn) are presented:

class Day :
	public LightControlInterface::StateInterface
{
public:
	Day(LightControlInterface* lci) :
		LightControlInterface::StateInterface(lci, "wns_fsm_tests_Day")
	{}

	virtual StateInterface*
	movementDetected();

	virtual StateInterface*
	timeElapsed();

	virtual StateInterface*
	light();

	virtual StateInterface*
	dark();
};
class Night :
	public LightControlInterface::StateInterface
{
public:
	Night(LightControlInterface* lci) :
		LightControlInterface::StateInterface(lci, "wns_fsm_tests_Night")
	{}

	virtual StateInterface*
	movementDetected();

	virtual StateInterface*
	timeElapsed();

	virtual StateInterface*
	light();

	virtual StateInterface*
	dark();
};
class LampOn :
	public LightControlInterface::StateInterface
{
public:
	LampOn(LightControlInterface* lci) :
		LightControlInterface::StateInterface(lci, "wns_fsm_tests_LampOn")
	{}

	virtual StateInterface*
	movementDetected();

	virtual StateInterface*
	timeElapsed();

	virtual StateInterface*
	light();

	virtual StateInterface*
	dark();
};

Note

All methods from the signal interface must be redefined.

Note

The state must be derived from an type exported by the interface of the FSM (“Light Control Interface”, the typedef) named “StateInterface”

Note

The constructor must take a pointer to the interface of the FSM

The implementation of the methods of these classes looks like this:

STATIC_FACTORY_REGISTER_WITH_CREATOR(FSMTest::Day,
				     FSMTest::LightControlInterface::StateInterface,
				     "wns_fsm_tests_Day",
				     FSMConfigCreator);

	FSMTest::Day::StateInterface*
	FSMTest::Day::movementDetected()
	{
		// ignore
		return this;
	}

	FSMTest::Day::StateInterface*
	FSMTest::Day::timeElapsed()
	{
		throw(Exception("Signal only valid in state LampOn"));
		return this;
	}

	FSMTest::Day::StateInterface*
	FSMTest::Day::light()
	{
		// ignore
		return this;
	}

	FSMTest::Day::StateInterface*
	FSMTest::Day::dark()
	{
		return getFSM()->createState<Night>();
	}
STATIC_FACTORY_REGISTER_WITH_CREATOR(FSMTest::Night,
				     FSMTest::LightControlInterface::StateInterface,
				     "wns_fsm_tests_Night",
				     FSMConfigCreator);

	FSMTest::Night::StateInterface*
	FSMTest::Night::movementDetected()
	{
		vars().on = true;
		return getFSM()->createState<LampOn>();
	}

	FSMTest::Night::StateInterface*
	FSMTest::Night::timeElapsed()
	{
		throw(Exception("Signal only valid in state LampOn"));
		return this;
	}

	FSMTest::Night::StateInterface*
	FSMTest::Night::light()
	{
		return getFSM()->createState<Day>();
	}

	FSMTest::Night::StateInterface*
	FSMTest::Night::dark()
	{
		// ignore
		return this;
	}
STATIC_FACTORY_REGISTER_WITH_CREATOR(FSMTest::LampOn,
				     FSMTest::LightControlInterface::StateInterface,
				     "wns_fsm_tests_LampOn",
				     FSMConfigCreator);

	FSMTest::LampOn::StateInterface*
	FSMTest::LampOn::movementDetected()
	{
		// ignore
		return this;
	}

	FSMTest::LampOn::StateInterface*
	FSMTest::LampOn::timeElapsed()
	{
		vars().on = false;
		return getFSM()->createState<Night>();
	}

	FSMTest::LampOn::StateInterface*
	FSMTest::LampOn::light()
	{
		vars().on = false;
		return getFSM()->createState<Day>();
	}

	FSMTest::LampOn::StateInterface*
	FSMTest::LampOn::dark()
	{
		// ignore
		return this;
	}

Note

The name of a state is defined by the string used to register the state at the static factory and has to be unique throughout the WNS. This name has to be used also in the constructor of the state.