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:
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:
Summary of the model:
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.
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.
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;
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).
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.