A C++ template library for embedded applications
Designed and maintained by
Aster Consulting Ltd
Join the ETL community

Finite State Machine

A finite state machine driven by the reception of events (messages) . The incoming messages will be automatically routed
to specific handlers based on the message list defined in the template parameters. Optional 'on entry' and 'on exit'
handlers are available.

This FSM is slightly more involved to set up than the traditional simple table driven method, but provides great flexibility in
implementation. It may also be faster due to  the fact that all messages are routed through a direct switch/case/call
method rather than scanning a lookup table and calling indirectly through function pointers.

The on_event functions are not virtual. The template class uses CRTP to directly call the derived class's functions.

Defines the following classes:-
etl::ifsm_state
etl::fsm_state <<template>>
etl::fsm
etl::fsm_exception
etl::fsm_null_state_exception
etl::fsm_state_id_exception
etl::fsm_state_list_exception

Note: This header is a generated from fsm_generator.h. To handle more than the standard 16 message types then a new
one must be generated.
See Generators

Types

etl::fsm_state_id_t
By default is defined as uint_least8_t.
If the user defines ETL_FSM_STATE_ID_TYPE then the type will be set to this.

____________________________________________________________________________________________________
etl::ifsm_state

The base for all FSM states.


etl::fsm_state_id_t get_state_id() const
Returns the id of the state.

virtual fsm_state_id_t process_event(etl::imessage_router& sender, const etl::imessage& message) = 0
The etl::fsm_state will override this.
Processes the event message from the specified source.

virtual fsm_state_id_t on_enter_state()
By default returns the state id.
The derived state should override this to provide alternative behaviour.

virtual void on_exit_state()
By default does nothing.
The derived state should override this to provide alternative behaviour.

____________________________________________________________________________________________________
etl::fsm_state

A templated state base. Inherits from etl::ifsm_state.

Template parameters

TContext The FSM context class. i.e. The class derived from etl::fsm.
TDerived The derived state.
T1       The first message type.
T2...    The additional message types.
         The maximum number of types can be set by running the generator for this file. The default is 16

The derived class must define the following member functions.

etl::fsm_state_id_t on_event(etl::imessage_router& sender, const Type& msg);
sender The originator of the message.
msg    The event message. A const reference to the concrete message type.
Replace Type with the concrete message type.
And so on for all of the template parameter types.
Returns the id of the next state.
____________________________________________________________________________________________________

etl::fsm_state_id_t on_event_unknown(etl::imessage_router& sender, const etl::imessage& msg);
sender The originator of the message.
msg    The event message.  A const reference to the base message type.
Called when a message type is received that is not in the template list.
Returns the id of the next state.

____________________________________________________________________________________________________

Member Functions


TContext& get_fsm_context() const
Gets a reference to the FSM context.
This is the class that was derived from etl::fsm.

____________________________________________________________________________________________________

Enumerations


STATE_ID The id of this state.

____________________________________________________________________________________________________
etl::fsm

The state machine.
Inherits from etl::imessage_router.
____________________________________________________________________________________________________

fsm(etl::message_router_id_t id)
Constructor.
Sets the router id for the FSM.
The FSM is not started.
____________________________________________________________________________________________________

template <typename TSize>
void set_states(etl::ifsm_state** p_states, TSize size)
Set the states for the FSM
Emits an etl::fsm_state_list_exception if size is zero.
Emits an etl::fsm_null_state_exception if any of the state pointers is null.
____________________________________________________________________________________________________

void start(bool call_on_enter_state = true);
Starts the FSM.
If call_on_enter_state is true then on_enter_state is called for the initial state. Default is true.
Can only be called once.
Subsequent calls will do nothing.
____________________________________________________________________________________________________

void receive(const etl::imessage& message)
Top level message handler for the FSM.
Sets the sender router to an instance of etl::null_message_router.
____________________________________________________________________________________________________

void receive(etl::imessage_router& source, const etl::imessage& message)
Top level message handler for the FSM.
____________________________________________________________________________________________________

bool accepts(etl::message_id_t id) const
Returns true.
____________________________________________________________________________________________________

etl::fsm_state_id_t get_state_id() const
Gets the current state id.
____________________________________________________________________________________________________

ifsm_state& get_state()
Gets a reference to the current state interface.
Emits an etl::etl::fsm_null_state_exception if the current state is null.
____________________________________________________________________________________________________

const ifsm_state& get_state() const
Gets a const reference to the current state interface.
Emits an etl::etl::fsm_null_state_exception if the current state is null.
____________________________________________________________________________________________________

bool is_started() const
Checks if the FSM has been started.
____________________________________________________________________________________________________

void reset(bool call_on_exit_state = false)
Resets the FSM to its pre-started state.
If call_on_exit_state is true then on_exit_state is called for the current state. Default is false.
____________________________________________________________________________________________________

Errors

fsm_exception
Inherits from etl::exception

fsm_null_state_exception
Inherits from etl::fsm_exception

fsm_state_id_exception
Inherits from etl::fsm_exception

fsm_state_list_exception
Inherits from etl::fsm_exception

Example


const etl::message_router_id_t MOTOR_CONTROL = 0;

//***************************************************************************
// Events
struct EventId
{
  enum
  {
    START,
    STOP,
    STOPPED,
    SET_SPEED
  };
};

//***********************************
class Start : public etl::message<EventId::START>
{
};

//***********************************
class Stop : public etl::message<EventId::STOP>
{
public:

  Stop() : isEmergencyStop(false) {}
  Stop(bool emergency) : isEmergencyStop(emergency) {}

  const bool isEmergencyStop;
};

//***********************************
class SetSpeed : public etl::message<EventId::SET_SPEED>
{
public:

  SetSpeed(int speed) : speed(speed) {}

  const int speed;
};

//***********************************
class Stopped : public etl::message<EventId::STOPPED>
{
};

//***************************************************************************
// States
struct StateId
{
  enum
  {
    IDLE,
    RUNNING,
    WINDING_DOWN,
    NUMBER_OF_STATES
  };
};

//***********************************
// The motor control FSM.
//***********************************
class MotorControl : public etl::fsm
{
public:

  MotorControl(etl::ifsm_state** p_states, size_t size)
    : fsm(MOTOR_CONTROL)
  {
    set_states(p_states, size);
  }

  void SetRunningLampOn()
  {
  }

  void SetRunningLampOff()
  {
  }

  void SetEmergencyLampOn()
  {
  }

  void SpeedChangeWarning()
  {
  }

  void LogUnknownEvent(etl::imessage& msg)
  {
  }
};

//***********************************
// The idle state.
// Accepts Start events.
//***********************************
class Idle : public etl::fsm_state<MotorControl, Idle, StateId::IDLE, Start>
{
public:

  //***********************************
  etl::fsm_state_id_t on_event(etl::imessage_router& sender, const Start& event)
  {
    return StateId::RUNNING;
  }

  //***********************************
  etl::fsm_state_id_t on_event_unknown(etl::imessage_router& sender, const etl::imessage& event)
  {
    get_fsm_context().LogUnknownEvent(event);

    return STATE_ID;
  }
};

//***********************************
// The running state.
// Accepts Stop and SetSpeed events.
//***********************************
class Running : public etl::fsm_state<MotorControl, Running, StateId::RUNNING, Stop, SetSpeed>
{
public:

  //***********************************
  etl::fsm_state_id_t on_event(etl::imessage_router& sender, const Stop& event)
  {
    if (event.isEmergencyStop)
    {
      get_fsm_context().SetEmergencyLampOn();     

      return StateId::IDLE;
    }
    else
    {
      return StateId::WINDING_DOWN;
    }
  }

  //***********************************
  etl::fsm_state_id_t on_event(etl::imessage_router& sender, const SetSpeed& event)
  {
    get_fsm_context().SpeedChangeWarning();

    return STATE_ID;
  }

  //***********************************
  etl::fsm_state_id_t on_event_unknown(etl::imessage_router& sender, const etl::imessage& event)
  {
    get_fsm_context().LogUnknownEvent(event);

    return STATE_ID;
  }

  //***********************************
  etl::fsm_state_id_t on_enter_state()
  {
    get_fsm_context().SetRunningLampOn();

    return STATE_ID;
  }

  //***********************************
  etl::fsm_state_id_t on_exit_state()
  {
    get_fsm_context().SetRunningLampOff();

    return STATE_ID;
  }
};

//***********************************
// The winding down state.
// Accepts Stopped events.
//***********************************
class WindingDown : public etl::fsm_state<MotorControl, WindingDown, StateId::WINDING_DOWN, Stopped>
{
public:

  //***********************************
  etl::fsm_state_id_t on_event(etl::imessage_router& sender, const Stopped& event)
  {
    return StateId::IDLE;
  }

  //***********************************
  etl::fsm_state_id_t on_event_unknown(etl::imessage_router& sender, const etl::imessage& event)
  {
    get_fsm_context().LogUnknownEvent(event);

    return STATE_ID;
  }
};

// The states.
Idle        idle;
Running     running;
WindingDown windingDown;

etl::ifsm_state* stateList[StateId::NUMBER_OF_STATES] =
{
  &idle, &running, &windingDown
};

The FSM.
MotorControl motorControl(stateList, etl::size(stateList));

// Start the motor. The first state is 'IDLE'.
motorControl.start();

// Receive a Start event. The next state is 'RUNNING'.
motorControl.receive(Start());

// Receive a SetSpeed event. The state is still 'RUNNING'.
motorControl.receive(SetSpeed(100));

// Receive a Stop event. The next state is 'WINDING_DOWN'.
motorControl.receive(Stop);

// Receive a Stopped event. The next state is 'IDLE'.
motorControl.receive(Stopped);

// Receive a Start event. The next state is 'RUNNING'.
motorControl.receive(Start());

// Receive a Stop(emergency) event. The next state is 'IDLE'.
motorControl.receive(Stop(true));
fsm.h