How not to code a state machine in C++ for an embedded system

The premise is simple: try to write a state machine in C++ for embedded systems following the State Pattern. But before you start coding there’s a catch: you cannot use dynamic memory:

  • Embedded systems based on low resources microcontrollers might not have support for malloc()/free() functions.
  • A safe embedded system should not use dynamic memory.
  • Just for fun.

The overall objective of the State Pattern is to add and delete states, and to run the transitions among states in the cleanest way. Although my solution is not by far the most elegant around … it’s what I got!

My clumsy and naive solution

First, let’s define the problem: a state machine with 3 states and 4 triggers (or events):

A simple state machine for our example.

We stated that states cannot be created inside states, so we need to define states and triggers as enums:

enum class State_e : uint8_t
{
   locked,
   unlocked,
   broken,
};

enum class Event_e: uint8_t
{
   coin,
   pass,
   failed,
   fixed,
};

And this how I use them:

int main()
{
   Context context( State_e::locked );

   while( 1 )
   {
      Event_e ev = menu();
      context.exec( ev );
   }
}

Here the function menu() acts as event generator. In a real embedded system, the events come from hardware: UART rx, digital sensors, analog readings, etc; while outputs can be relays, LCD messages, LEDs, etc.

Let’s see what we want to achieve:

A crude, yet very insightful, UML state chart for the solution.

And now the most critical issue: we cannot create states on-the-fly (through new) neither destroy them (through delete), so states must be created in the stack as needed. Polymorphism uses a switch construction under the hood anyway, but in our problem we need to code it by hand:

   void Context::exec( Event_e ev )
   {
      switch( this->state_e ){
         case State_e::locked:
            {
            Locked locked;
            this->state_e = locked.run( ev );
            break;
            }

         case State_e::unlocked:
            {
            Unlocked unlocked;
            this->state_e = unlocked.run( ev );
            break;
            }

         case State_e::broken:
            {
            Broken broken;
            this->state_e = broken.run( ev );
            break;
            }

         default: break;
      }
   }

In this example the object locked (line 6) lives only inside the braces, and this is the way it must be. In that fashion every time a state change must be exercised we need to choose the correct in-the-stack object. The this->state_e variable lets us decode the next state.

Why can’t we declare the new state inside a concrete state (more on this later)? We’ll have a stack-problem if state locked calls (and create) the state unlocked, and then state unlocked calls back (and create again) the state locked. In order to address this problem we would need to keep track of who calls who. And this is really a bad idea.

State base class

Much of the clumsy and nasty implementation of this naive solution is carried on the State base class. We could have implemented this solution in plain C, but inheritance, virtual functions and constructors/destructors are a plus:

class State
{
protected:
   State_e state_e;

   virtual State_e run( State* s, Event_e ev )
   {
      switch( ev ){
         case Event_e::coin:   s->coin_handler(); break;
         case Event_e::pass:   s->pass_handler(); break;
         case Event_e::fixed:  s->fixed_handler(); break;
         case Event_e::failed: s->failed_handler(); break;
      }

      return this->state_e;
   }

public:
   void set_context( Context* context )
   {
      this->context = context;
   }

   virtual void coin_handler()
   {
      cout << "coin_handler() default\n";
   }

   virtual void pass_handler()
   {
      cout << "pass_handler() default\n";
   }

   virtual void fixed_handler()
   {
      cout << "fixed_handler() default\n";
   }

   virtual void failed_handler()
   {
      cout << "failed_handler() default\n";
   }

   State_e run( Event_e ev )
   {
      return State::run( this, ev );
   }
};

The method State::run( State* s, Event_e ev ) decodes the event handlers. I have 2 ::run() methods, one for the class’ client and one private for the internal decoding. I’m pretty sure there’s a better way to do it, or maybe I’m forgotting something, but for now it works!

NOTE: The calling of the std::cout stream is only for debugging purposes.

Concrete states

All that base class clumsy code lets us to write the concrete states with the minimum code, e.g. only the handlers the state handles and recongnizes:

class Locked : public State
{
public:
   void coin_handler() override
   {
      cout << "Locked::coin_handler()\n";

      this->state_e = State_e::unlocked;
   }

   void failed_handler() override
   {
      cout << "Locked::failed_handler()\n";

      this->state_e = State_e::broken;
   }
};

The default handlers in the base class handle those events that each concrete classes don’t handle (thank you virtual functions!).

What’s next?

Although this solution is not by far the most elegant, I tried to keep at minimum the places in where the programmer needs to customize her/his code. The less places to edit, the better.

Why C++? Because we can use constructors to do independent work when we enter into the states, and use destructors to do independent work when we leave the states, and most important, we can override code through virtual functions.

Why didn’t I use pure virtual functions for the State class? Because in that case each state should handle all states, even if they don’t use them, and it makes non-sense for this application.

Can we use the CRTP pattern in order to implement static polymorphism? I’m pretty sure we can, but that’s a topic for other article. (Would like to see how the CRTP pattern can be used for embedded systems? If yes, visit this other post of my own.)

I think I’ll need to write a second part so that we all see how this solution works in a real embedded system application, for example in one of my LPC1114 boards or with an Arduino-based boards. Keep post!

Complete working code

#include <iostream>
// for debugging purposes

using namespace std;


enum class Event_e: uint8_t;

Event_e menu();
// event generator simulator


enum class State_e : uint8_t
{
   locked,
   unlocked,
   broken,
};

enum class Event_e: uint8_t
{
   coin,
   pass,
   failed,
   fixed,
};

//----------------------------------------------------------------------
//                        Class State
//----------------------------------------------------------------------

class State
{
protected:
   State_e state_e;

public:
   virtual void coin_handler()
   {
      cout << "coin_handler() default\n";
   }

   virtual void pass_handler()
   {
      cout << "pass_handler() default\n";
   }

   virtual void fixed_handler()
   {
      cout << "fixed_handler() default\n";
   }

   virtual void failed_handler()
   {
      cout << "failed_handler() default\n";
   }

   State_e run( Event_e ev )
   {
      return State::run( this, ev );
   }

protected:
   virtual State_e run( State* s, Event_e ev )
   {
      switch( ev ){
         case Event_e::coin:   s->coin_handler(); break;
         case Event_e::pass:   s->pass_handler(); break;
         case Event_e::fixed:  s->fixed_handler(); break;
         case Event_e::failed: s->failed_handler(); break;
      }

      return this->state_e;
   }
};

//----------------------------------------------------------------------
//                      Class Locked
//----------------------------------------------------------------------

class Locked : public State
{
public:
   void coin_handler() override
   {
      cout << "Locked::coin_handler()\n";

      this->state_e = State_e::unlocked;
   }

   void failed_handler() override
   {
      cout << "Locked::failed_handler()\n";

      this->state_e = State_e::broken;
   }
};

//----------------------------------------------------------------------
//                      Class Unlocked
//----------------------------------------------------------------------

class Unlocked : public State
{
public:
   void pass_handler() override
   {
      cout << "Unlocked::pass_handler()\n";

      this->state_e = State_e::locked;
   }
};

//----------------------------------------------------------------------
//                      Class Broken
//----------------------------------------------------------------------

class Broken : public State
{
public:
   void fixed_handler() override
   {
      cout << "Broken::fixed_handler()\n";

      this->state_e = State_e::locked;
   }
};

//----------------------------------------------------------------------
//                         Class Context
//----------------------------------------------------------------------

class Context
{
private:
   State_e state_e;
   State* state;

public:
   Context( State_e initial_state ) : state_e{ initial_state }
   {
   }

   void exec( Event_e ev )
   {
      switch( this->state_e ){
         case State_e::locked:
            {
            Locked locked;
            this->state_e = locked.run( ev );
            break;
            }

         case State_e::unlocked:
            {
            Unlocked unlocked;
            this->state_e = unlocked.run( ev );
            break;
            }

         case State_e::broken:
            {
            Broken broken;
            this->state_e = broken.run( ev );
            break;
            }

         default: break;
      }
   }
};

//----------------------------------------------------------------------
//                        Driver program
//----------------------------------------------------------------------

int main()
{
   Context context( State_e::locked );

   while( 1 )
   {
      Event_e ev = menu();
      context.exec( ev );
   }
}

Event_e menu()
{
   while( 1 )
   {
      cout << "\n0 Exit\n";
      cout << "1 Coin\n";
      cout << "2 Pass\n";
      cout << "3 Failed\n";
      cout << "4 Fixed\n";
      cout << "\n? ";

      int event;
      cin >> event;

      if( event == 0 ) exit( 0 );

      switch( event ){
         case 1: return Event_e::coin;
         case 2: return Event_e::pass;
         case 3: return Event_e::failed;
         case 4: return Event_e::fixed;
         default:
            cout << "Option no valid.\n";
            break;
      }
   }
}

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s