JMU
Implementing State Machines
with Examples in C


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Background
Overview
The Example System
A Model of the Example System
images/OpeningSource_FourStateModel_detailed.gif
The Library for the Hardware
hardware.h
#ifndef HARDWARE_H
#define HARDWARE_H

// LED Settings.
typedef enum
  {
    LED_OFF,
    LED_ON,
    LED_NUMBER_OF_SETTINGS
  } led_setting;


// Motor Settings.
typedef enum
  {
    MOTOR_CLOSING,
    MOTOR_OFF,
    MOTOR_OPENING,
    MOTOR_NUMBER_OF_SETTINGS
  } motor_setting;


// Events.
typedef enum
  {
    CLOSE_BUTTON_PRESSED,
    CLOSED_DETECTED,
    OPEN_BUTTON_PRESSED,
    OPENED_DETECTED,
    NUMBER_OF_EVENTS
  } event;


// Control of the LED Indicators.
void 
set_closed_indicator(led_setting value);

void 
set_opened_indicator(led_setting value);


// Control of the Motor.
void 
set_motor(motor_setting value);

#endif
  

Aside: Enumeration constants have the values \(0, 1, \ldots, n-1\) which allows us to use the _NUMBER_OF_ idiom.

The Library for the Hardware (cont.)
hardware.c
#include "hardware.h"

// The LED Indicators
void 
set_closed_indicator(led_setting value)
{
  // Details omitted
}

void 
set_opened_indicator(led_setting value)
{
  // Details omitted
}

// The Motor
void
set_motor(motor_setting value)
{
  // Details omitted
}
  

Note: All of the functions and enumerations have file scope.

Different Implementations
Using Nested switch Statements
statemodel.h
#ifndef STATEMODEL_H
#define STATEMODEL_H

#include "hardware.h"

// The states
typedef enum 
  {
    CLOSED,
    CLOSING,
    OPENED,
    OPENING,
    NUMBER_OF_STATES
  } state;

// The event handler.
void 
handle_event(event current_event);

void
setup_states();

#endif
  
Using Nested switch Statements (cont.)
statemodel.c
#include "statemodel.h"

// The current state (which is initially OPENED).
static state current_state = OPENED;

// The event handler.
void
handle_event(event current_event)
{
  switch(current_state)
    {
    case CLOSED:
      switch(current_event)
	{
	case OPEN_BUTTON_PRESSED:
	  set_closed_indicator(LED_OFF); // exit from CLOSED
	  current_state = OPENING;       // transition to OPENING
	  set_motor(MOTOR_OPENING);      // entry to OPENING
	  break;
	}
      break;
    case CLOSING:
      switch (current_event)
	{
	case CLOSED_DETECTED:
	  set_motor(MOTOR_OFF);          // effect of CLOSED_DETECTED when CLOSING
	  current_state = CLOSED;        // transition to CLOSED
	  set_closed_indicator(LED_ON);  // entry to CLOSED
	  break;
	case OPEN_BUTTON_PRESSED:
	  current_state = OPENING;       // transition to OPENING
	  set_motor(MOTOR_OPENING);      // entry to OPENING
	  break;
	}
      break;
    case OPENED:
      switch (current_event)
	{
	case CLOSE_BUTTON_PRESSED:
	  set_opened_indicator(LED_OFF); // exit from OPENED
	  current_state = CLOSING;       // transition to CLOSING
	  set_motor(MOTOR_CLOSING);      // entry to CLOSING
	  break;
	}
      break;
    case OPENING:
      switch (current_event)
	{
	case CLOSE_BUTTON_PRESSED:
	  current_state = CLOSING;       // transition to CLOSING
	  set_motor(MOTOR_CLOSING);      // entry to CLOSING
	  break;
	case OPENED_DETECTED:
	  current_state = OPENED;        // transition to OPENED
	  set_opened_indicator(LED_ON);  // entry to OPENED
	  set_motor(MOTOR_OFF);          // effect of OPENED_DETECTED when OPENING
	  break;
	}
      break;
    }
}
  

Note: current_state has file scope and internal linkage (because it is decalred to be static)

Shortcomings of Nested switch Statements
Using the State Pattern
Using the State Pattern (cont.)
state.h
#ifndef STATE_H
#define STATE_H

// Add an alias: For a type (to the global name space)
//      |-type     -| |- alias -|
//            |-tag-|
typedef struct state    state; 

// Add an alias: For the event handlers
//      |-return type-| |- alias   -||- params -|
typedef      state*     event_handler(void);
 
// Add an alias: For the actions
typedef      void        action(void);

// Define the identifier/tag state as a struct.
struct state {
  event_handler*  close_button_pressed;
  event_handler*  closed_detected;
  event_handler*  open_button_pressed;
  event_handler*  opened_detected;
  action*         entry_to;
  action*         exit_from;
};

// Declare variables to hold pointers to the default_event_handler and
// the default_action
extern event_handler* default_event_handler;
extern action* default_action;

// Declare a variable to hold the default_state
//     |-   type  -| |-   name  -|
//           |-tag-|
extern struct state default_state;
// Note: Because of the typedef for the alias state, this could also be:
// extern state default_state;

#endif
  

Note: default_event_handler, default_action and default_state are extern because they must be declared in the scope of each individual state but are defined in states.c.

Using the State Pattern (cont.)
state.c
#include "state.h"
#include <stdlib.h> // For NULL

// Define all of the functions that are not exposed
// by the header file.

state* 
return_null()
{
  return NULL;
}

void 
return_void()
{
}

// Define the default_event_handler and default_action

event_handler* default_event_handler = return_null;
action* default_action = return_void;


// Define the default_state

struct state default_state = {
  return_null,   // close_button_pressed
  return_null,   // closed_detected
  return_null,   // open_button_pressed
  return_null,   // opened_detected
  return_void,   // entry_to
  return_void    // exit_from
};
  
Using the State Pattern (cont.)
closed.h
#ifndef CLOSED_H
#define CLOSED_H

#include "state.h"

// Declare all of the functions performed when in the closed state.
static state*
open_button_pressed();

static void
entry_to();

static void
exit_from();


// Define the closed state and initialize it to the default_state
// Note: setup_closed() must be called to complete the definition. 
struct state closed;

#endif
  
Using the State Pattern (cont.)
closed.c
#include "closed.h"
#include "hardware.h"
#include "statemodel.h" // For the other states

void
setup_closed()
{
  closed = default_state;
  closed.open_button_pressed = open_button_pressed;
  closed.entry_to = entry_to;
  closed.exit_from = exit_from;
}

static state*
open_button_pressed()
{
  exit_from();
  return &opening;
}

static void 
entry_to()
{
  set_closed_indicator(LED_ON);
}

static void 
exit_from()
{
  set_closed_indicator(LED_OFF);
}
  
Using the State Pattern (cont.)
statemodel.h
#ifndef STATEMODEL_H
#define STATEMODEL_H

#include "hardware.h"
#include "state.h"


// Declare all of the states used in the state model

extern state closed;
extern state closing;
extern state opened;
extern state opening;

// Declare all of the state setup functions
void
setup_closed();

void
setup_closing();

void
setup_opened();

void
setup_opening();



// Declare all of the functions used in the state model itself (i.e.,
// not in the individual states).

void
setup_states();

void
handle_event(event current_event);


#endif
  

Note: Each of the individual states are extern because, though they are declared here, they are defined in the header files for the individual states.

Using the State Pattern (cont.)
statemodel.c
#include "statemodel.h"
#include <stdlib.h> // For NULL

// Define the initial state.
static state* current_state = &opened;

void
setup_states()
{
  setup_closed();
  setup_closing();
  setup_opened();
  setup_opening();
}


// Define the functions.

void
handle_event(event current_event)
{
  state* next_state;

  next_state = NULL;
  switch(current_event) // exit current_state and have the appropriate effect
    {
    case CLOSE_BUTTON_PRESSED:
      next_state = current_state-%gt;close_button_pressed();
      break;
    case CLOSED_DETECTED:
      next_state = current_state-%gt;closed_detected();
      break;
    case OPEN_BUTTON_PRESSED:
      next_state = current_state-%gt;open_button_pressed();
      break;
    case OPENED_DETECTED:
      next_state = current_state-%gt;opened_detected();
      break;
    }    

  if (next_state != NULL)
    {
      current_state = next_state; // Change states
      current_state-%gt;entry_to();  // enter the new state
    }
}
  
Using the State Pattern (cont.)