Main Page | Modules | Class Hierarchy | Compound List | File List | Compound Members | File Members | Related Pages

State machines: A toy example

This tutorial and the next illustrate another aspect of RHexLib which allows the programmer to define discrete event driven controllers called state machines. We first build a very simple state machine in this section, to illustrate the tools involved, and then build a more complicated machine to show how the facility can be used to supervise other controllers (which may themselves be state machines).

We first implement the machine shown in the Figure below which has two states, called stateOne and stateTwo and one event called eventOne. We will construct the machine so that it starts in the first state and stays there for one second and then transitions to the second state. RHexLib provides a perl script to set up new state machine classes. The first step is to define a description file called ToyMachine.dsc which contains the same information that is depicted in the following figure

toymach.gif

A simple state machine, called Toymachine

  Transition stateOne eventOne stateTwo
  Initial stateOne

This file specifies a StateMachine module with two states and one transition. To translate it into C++ code, we run the perl script sm-setup.pl (for ``state machine setup'') as follows:

 $RHEX_DIR/util/sm-setup.pl ToyMachine

This creates two generic files: ToyMachine.hh and ToyMachine.cc to which we must add our own custom functionality. The first file contains the definition of class ToyMachine which is derived from the virtual class StateMachine which is derived from the virtual class Module. Thus a state machine is a kind of module, and therefore deals with the module manager in much the same way as the modules we have already encountered. However, the five virtual functions defined in the Module base class, and in particular the update method, are already defined in any class that derives from StateMachine. To give a state machine interesting functionality, we must define the semantics of the states and events.

First, look at ToyMachine.hh which the perl script produced from the ToyMachine.dsc file. It is as follows, except we have added a data variable called mark to the ToyMachine class definition. We will use this variable to mark the time at which the first state of the machine is entered.

  #ifndef _TOYMACHINE_HH
  #define _TOYMACHINE_HH

  #include "ModuleManager.hh"
  #include "StateMachine.hh"

  class ToyMachine : public StateMachine {
    public:

      ToyMachine ( void );
      ~ToyMachine ( void );
      void init ( void );
      void activate ( void );
      void deactivate ( void );

    private:

      // events
      EventObject ( EventOne ) * eventOne;

      // states
      StateObject ( StateOne ) * stateOne;
      StateObject ( StateTwo ) * stateTwo;

      // data
      double mark;
  };

  #endif

Note how the states and event are declared using the macros EventObject and StateObject. These macros, defined in StateMachine.hh, declare new types whose scope is the ToyMachine class. Thus the line

  EventObject ( EventOne ) * eventOne;

does several things. First, it defines a new class EventOne which is derived from the virtual class Event. Note that the scope of the new class EventOne does not extend beyond the ToyMachine, thus, other State Machines may also use the same class name without causing any conflicts. Second, it declares this class a friend of class ToyMachine. And third, it sets eventOne to be a pointer to an object of class EventOne. The StateObject lines are similar.

Next, look at ToyMachine.cc, also produced by the sm-setup.pl script. Here, the methods associated with the ToyMachine class and the state and event classes StateOne, StateTwo and EventOne and defined. This file is as follows:

  #include "ToyMachine.hh"

  #define OWNER ( ( ToyMachine * ) owner )

  // Events ------------------------------------------------------------
  bool ToyMachine::EventOne::check ( void ) { return false; }

  // States ------------------------------------------------------------
  void ToyMachine::StateOne::entry ( void ) {}
  void ToyMachine::StateOne::during ( void ) {}
  void ToyMachine::StateOne::exit ( void ) {}

  void ToyMachine::StateTwo::entry ( void ) {}
  void ToyMachine::StateTwo::during ( void ) {}
  void ToyMachine::StateTwo::exit ( void ) {}

  ToyMachine::ToyMachine ( void ) : StateMachine ( "toymachine" ) {

    // allocate events
    eventOne = new EventOne ( this );

    // allocate states
    stateOne = new StateOne ( this );
    stateTwo = new StateTwo ( this );

    // transitions
    Transition ( stateOne, eventOne, stateTwo );

    // the initial state
    initialize ( stateOne );

  } 

  ToyMachine::~ToyMachine ( void ) {

    if ( eventOne ) delete ( eventOne );
    if ( stateOne ) delete ( stateOne );
    if ( stateTwo ) delete ( stateTwo );

  }
 
  void ToyMachine::init ( void ) {

    StateMachine::init();

  }

  void ToyMachine::activate ( void ) {

    StateMachine::activate();

  }

  void ToyMachine::deactivate ( void ) {

    StateMachine::deactivate();

  }

The constructor for the ToyMachine class performs several steps. It sets eventOne to point to a new EventOne object. Note that the constructor to EventOne requires a pointer (in this case, this) to a state machine class that ``owns'' the event. This will be used by the event's method later to access the data (such as the datum mark that we added above) in the state machine that owns it. It then similarly sets the event object pointers to point to new state objects. The next step sets up the transitions in the state machine --- in this case there is only one transition. The line

  // transitions
  Transition ( stateOne, eventOne, stateTwo );

adds a transition or arc to the ToyMachine being constructed. The source state of the transition is stateOne, the destination state is stateTwo. The event that guards the transition from the source to the destination is eventOne. The last step is to set up the initial state ( stateOne) of the state machine and this is doen by the last line of the constructor.

The behavior of a ToyMachine is as follows. When it becomes active, it first calls the entry method of its initial state (in this case of stateOne) and then continuously calls the update method of this state. The ToyMachine also continuously checks the check method of eventOne until it returns true. When this happens, it then calls the exit method of stateOne and then then the entry method of stateTwo and then continuously calls the update function of stateTwo.

As it is, the script sm-setup.pl has not put any meaning in the states and event. It has made all the methods of stateOne and stateTwo empty. And it has defined the check method of eventOne to always return false! Recall that we wanted stateOne to be active for one second and then for stateTwo to be active. To arrange for this, we first redefine the methods of stateOne as follows:

  void ToyMachine::StateOne::entry ( void ) {

    OWNER->mark = MMReadTime();
    printf ( "State One Entered at Time %f\n", OWNER->mark );

  }

  void ToyMachine::StateOne::during ( void ) {}

  void ToyMachine::StateOne::exit ( void ) {
 
    printf ( "State One Exited at Time %f\n", MMReadTime() );

  }

Now, when stateOne is entered, it sets the datum mark in ToyMachine to the current time. Note that we use the macro OWNER at the top of Toymachine.cc to access the mark. We've also added printf statements so that some activity may be observed when we compile and run this machine. Now we can redefine EventOne::check:

  bool ToyMachine::EventOne::check ( void ) { 

    return ( MMReadTime() >= OWNER->mark + 1.0 );

  }

This will return true when the current time is greater than or equal to one second after the time stateOne was entered.

Finally, we make a main function which instantiates a ToyMachine and sets it running:

  #include <stdio.h>
  #include "ModuleManager.hh"
  #include "StateMachine.hh"
  #include "ToyMachine.hh"

  VirtualHW hw;

  int main( void ) {

    initHardware();

    // declare a toy machine
    ToyMachine *tm;

    // Add and activate the machine
    MMAddModule( tm = new ToyMachine, 10, 0, 10 );
    MMActivateModule( &tm );

    MMMainLoop();

    cleanupHardware();

    MMShutdown();

    delete tm;

    return 0;
  }

Once this code is compiled, it can be run to produce something like the following output:

  > example_tm 
  State One Entered at Time 0.001000
  State One Exited at Time 1.011000
  State Two Entered at Time 1.011000

Note that EventOne::check() could equally well have corresponded to checking a remote control signal range, checking for a pattern on a certain sensor such as touchdown of a leg, and so on. The update functions of the two states could correspond to modes of control such as lifting a leg to some position and so on.


RHexLib Reference Documentation