A C++ template library for embedded applications
MIT licensed
Designed and
maintained by
John Wellbelove
Like the ETL? Become a patron!
or
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.
____________________________________________________________________________________________________
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.
____________________________________________________________________________________________________
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.
____________________________________________________________________________________________________
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_state_list_exception if states are in the incorrect id order.
Emits an etl::fsm_null_state_exception if any of the state pointers is null.

Note: The pointers to the states in the state list must be at the index specified by the state id.
i.e. In the example below stateList[StateId::IDLE] == &idle
____________________________________________________________________________________________________
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.
____________________________________________________________________________________________________
void receive(etl::imessage_router&    sender,
             etl::message_router_id_t destination_router_id,
             const etl::imessage&     message)
Top level message handler for the FSM.
If the destination id is not the FSM's id, then the message is ignored.
____________________________________________________________________________________________________
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

An example of a queued FSM can be found in the repository in examples\QueuedFSM
____________________________________________________________________________________________________

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()
    : fsm(MOTOR_CONTROL)
  {
  }

  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;

// The states must be in state id order.
etl::ifsm_state* stateList[StateId::NUMBER_OF_STATES] =
{
  &idle, &running, &windingDown
};

The FSM.
MotorControl motorControl;

// Set the FSM's state list
motorControl.set_states(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