Simple Implementation of the State Machine
The simplest way of state machine implemetation is by nested switch-case instructions:
int sm_event_handler( struct * event_t ev ){
switch( state ){
case STATE_OFF:
//handling event in OFF state
switch( ev->signal ){
case SIG_ENABLE:
sm_transition( STATE_WAITING );
return 0;
}
return -1;
case STATE_ACTIVATED:
//handling event in ACTIVATED state
switch( ev->signal ){
case SIG_TIMER:
blink();
return 0;
case SIG_MATCH:
beep();
return 0;
...
Above implementation is not very clean as is its effectiveness.
It can be easily noticed that multiple times the state is tested (cases in external switch).
State Machine - Better Implementation
The issues with simple implemetation can be avoided by ordering numbers corresponding to symbolic state names (e.g. STATE_OFF, STATE_ACTIVATED etc.).
For example:
typedef enum{ STATE_OFF=0, STATE_ACTIVATED, STATE_WAITING, STATE_DETECTED } state_t;
state_t state = STATE_OFF; //variable for current state, initialized to STATE_OFF
Above change provides a way to splitting one handler into multiple events handlers each for one state. Pointers to these functions can be put into
a table:
typedef int (*sm_handler_t)( struct event_t * ev );//pointer to function
//table of pointers
sm_handler_t handlers[]={ sm_handler_state_off,
sm_handler_state_activated,
sm_handler_state_waiting,
sm_handler_state_detected
};
Now, instead of switch-case one need to call proper function from the table. It can be obtained by getting from the table the pointer to function
pointed by current state.
int sm_event_handler( struct event_t* ev ){
//function call:
return handlers[ state ](&ev);
}
//definition of single handler
static int sm_handler_state_off( struct event_t * ev )
{
switch( ev->signal ){
case SIG_ENABLE:
sm_transition( STATE_WAITING );
return 0;
}
return -1;
}
//consecutive handlers definitions follow
...
The simple means provided isolation of event handlers for each state.
The source code is more clear and the executed is only the part of code that belongs to current state.
More State Machines
Above trick only illustrates some method, but does not show how to deal with:
- multiple instances of the state machine the same type,
- definition of state machines of different types,
Some proposition is presented here: Implementation of state machines in DioneOS RTOS.
Definition of the instances can be solved by using separated structures that contains all state machine data, the data that were global before that:
struct state_machine_t{
sm_handler_t * handlers; //the table of handlers for the states
int state; //current state
int states_nb; //number of states
void * xdata; //some extra private data for state machine
}
Next, the structure should be passed as an argument to function-handlers and further to all operations on state machine.
This description uses C language (if you use C++ it will be much simpler: instances are just objects of the class).
int sm_handler_state_off( struct state_machine_t * sm, struct event_t* ev )
{
switch( ev->signal ){
case SIG_ENABLE:
sm_transition( sm, STATE_WAITING );
return 0;
return -1;
}
Definition of Different Types of the Machines
When you want to define different types of state machines, you need to plan proper naming convention (namespace).
You need to define states and signals for each type. You can consider usage of global signal naming (some enum)
where each new type appends its signals. This implementation is consistent with solution, that uses signals (i.e. kinds of events) independent on state machines and
are general set..
You can assume that each new type of state machine will be in separated source file with the name built on the type (this method is used in other programming languages -
new class code is put in a file with the same name). The file contains all sources necessary for control the state machine.
Separation the Definition and Usage (Framework or Library)
The more you use state machines, the more attempting is an idea of some universal structure where user (programmer) does not need to implement
general code for state machines but only defines its specific items (like states, signals, handlers, etc.)
There are ready to use solutions (e.g. in DioneOS system) that provides the framework that is some kind of template (pattern) of state machines.
This can be formed by source files in C, preprocessor macrodefinitions and header files.
Programmer defines specific type of the machine using this framework by definition only the handlers, events and the states.
In order to use the machine of this type he just creates the instance of this state machine. One can notice levels of abstraction: