A C++ template library for embedded applications
Designed and maintained by
Aster Consulting Ltd

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

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();
Starts the FSM.
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()
Resets the FSM to its pre-started state.

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