Goals for HSM Implementation


Behavioral Inheritance Meta-Pattern

  • Narrowed focus from UML Statecharts:
    1. Hierarchical states and behavioral inheritance
    2. Guaranteed initialization and cleanup (entry/exit actions)
    3. Support for specialization of HSM using class inheritance
  • Ignored:
    1. External event handling (queue)
    2. External event dispatch (to the HSM)
    3. Execution model (threads)
    4. Timing services
  • These will return!
    Fig 4.1
  • HSM base class QHsm
    1. Parent of all concrete state machines
    2. Provides init(), dispatch(), and tran() services
    3. Method tran() is protected, intended to be called by state handlers
    4. State represented as QState, a pointer-to-method of QHsm
    5. Currently active state = myState
    6. Current transition source = mySource (when transition is inherited, not the same as myState)
  • Event base class QEvent
    1. Represents SignalEvent
    2. Events with parameters - derived classes
    3. Relates to QHsm through signature of state aka state handler method

Class QHsm


Macros


Events and Signals


Entry, Exit actions and Initial Transitions


State Transitions


Top State and Initial Transition


All-Cases Example

  1. Enumerate Signals
    enum QHsmTstSignals
    {
      A_SIG = Q_USER_SIG, B_SIG, C_SIG, 
      D_SIG, E_SIG, F_SIG, G_SIG, H_SIG
    };
    
    Fig 4.3
  1. Derive Concrete HSM
    class QHsmTst : public QHsm
    {
      public:
        QHsmTst() : QHsm((QPseudoState)initial) {}
      protected:
        void initial(QEvent const *e);              // initial pseudostate
        QSTATE s0(QEvent const *e);                 // state-handler
          QSTATE s1(QEvent const *e);               // state-handler
            QSTATE s11(QEvent const *e);            // state-handler 
          QSTATE s2(QEvent const *e);               // state-handler
            QSTATE s21(QEvent const *e);            // state-handler
              QSTATE s211(QEvent const *e);         // state-handler
      private:                               // extended state variables...
        int myFoo;
    };
    
    Note the use of indentation to self-document the state hierarchy
  1. Define State Handlers
    void QHsmTst::initial(QEvent const *)
    {
      printf("top-INIT;"); 
      myFoo = 0;                   // initialize extended state variable
      Q_INIT(&QHsmTst::s0);                        // initial transition
    }
    
    QSTATE QHsmTst::s0(QEvent const *e)
    {
      switch (e->sig)
      {
        case Q_ENTRY_SIG:
          printf("s0-ENTRY;");
          return 0;
        case Q_EXIT_SIG:
          printf("s0-EXIT;");
          return 0;
        case Q_INIT_SIG:
          printf("s0-INIT;");
          Q_INIT(&QHsmTst::s1);   
          return 0;
        case E_SIG:
          printf("s0-E;");
          Q_TRAN(&QHsmTst::s211);
          return 0;
      }
      return (QSTATE)&QHsmTst::top;
    }
    
    QSTATE QHsmTst::s1(QEvent const *e)
    {
      switch (e->sig)
      {
        case Q_ENTRY_SIG:
          printf("s1-ENTRY;");
          return 0;
        case Q_EXIT_SIG:
          printf("s1-EXIT;");
          return 0;
        case Q_INIT_SIG:
          printf("s1-INIT;");
          Q_INIT(&QHsmTst::s11);
          return 0;
        case A_SIG:
          printf("s1-A;");
          Q_TRAN(&QHsmTst::s1);
          return 0;
        case B_SIG:
          printf("s1-B;");
          Q_TRAN(&QHsmTst::s11);
          return 0;
        case C_SIG:
          printf("s1-C;");
          Q_TRAN(&QHsmTst::s2);
          return 0;
        case D_SIG:
          printf("s1-D;");
          Q_TRAN(&QHsmTst::s0);
          return 0;
        case F_SIG:
          printf("s1-F;");
          Q_TRAN(&QHsmTst::s211);
          return 0;
      } 
      return (QSTATE)&QHsmTst::s0;
    }
    
    QSTATE QHsmTst::s11(QEvent const *e)
    {
      switch (e->sig)
      {
        case Q_ENTRY_SIG:
          printf("s11-ENTRY;");
          return 0;
        case Q_EXIT_SIG:
          printf("s11-EXIT;");
          return 0;
        case G_SIG:
          printf("s11-G;");
          Q_TRAN(&QHsmTst::s211);
          return 0;
        case H_SIG:  // internal transition with a guard
          if (myFoo) // test the guard condition
          {
            printf("s11-H;");
            myFoo = 0;
            return 0;
          }
          break; // else return superstate
       } 
       return (QSTATE)&QHsmTst::s1;
    }
    
    QSTATE QHsmTst::s2(QEvent const *e)
    {
      switch (e->sig)
      {
        case Q_ENTRY_SIG:
          printf("s2-ENTRY;");
          return 0;
        case Q_EXIT_SIG:
          printf("s2-EXIT;");
            return 0;
        case Q_INIT_SIG:
          printf("s2-INIT;");
          Q_INIT(&QHsmTst::s21); 
          return 0;
        case C_SIG:
          printf("s2-C;");
          Q_TRAN(&QHsmTst::s1);
          return 0;
        case F_SIG:
          printf("s2-F;");
          Q_TRAN(&QHsmTst::s11);
          return 0;
      } 
      return (QSTATE)&QHsmTst::s0;
    }
    
    QSTATE QHsmTst::s21(QEvent const *e)
    {
      switch (e->sig)
      {
        case Q_ENTRY_SIG:
          printf("s21-ENTRY;");
          return 0;
        case Q_EXIT_SIG:
          printf("s21-EXIT;");
          return 0;
        case Q_INIT_SIG:
          printf("s21-INIT;");
          Q_INIT(&QHsmTst::s211); 
          return 0;
        case B_SIG:
          printf("s21-B;");
          Q_TRAN(&QHsmTst::s211);
          return 0;
        case H_SIG: // self transition with a guard
          if (!myFoo) // test the guard condition
          {
            printf("s21-H;");
            myFoo = 1;
            Q_TRAN(&QHsmTst::s21); // self transition
            return 0;
          }
          break; // else return the superstate
        } 
        return (QSTATE)&QHsmTst::s2;  // return the superstate
    }
    
    QSTATE QHsmTst::s211(QEvent const *e)
    {
      switch (e->sig)
      {
        case Q_ENTRY_SIG:
          printf("s211-ENTRY;");
          return 0;
        case Q_EXIT_SIG:
          printf("s211-EXIT;");
          return 0;
        case D_SIG:
          printf("s211-D;");
          Q_TRAN(&QHsmTst::s21);
          return 0;
        case G_SIG:
          printf("s211-G;");
          Q_TRAN(&QHsmTst::s0);
          return 0;
      } 
      return (QSTATE)&QHsmTst::s21;
    }
    
  1. Test Harness
  2. static const QEvent testQEvt[] =
    { 
      {A_SIG, 0, 0}, {B_SIG, 0, 0}, {C_SIG, 0, 0}, {D_SIG, 0, 0}, 
      {E_SIG, 0, 0}, {F_SIG, 0, 0}, {G_SIG, 0, 0}, {H_SIG, 0, 0}
    };
    static QHsmTst test;         
    
    int main()
    {
      printf("QHsmTst example, version 1.00, libraries: %s\n", 
               QHsm::getVersion());
      test.init();                        // take the initial transition
      for (;;)
      {
        printf("\nSignal<-");
        char c = getc(stdin); 
        getc(stdin);                                    // discard '\n'
        if (c < 'a' || 'h' < c)                            // in range?
        {
          return 0; 
        }
        test.dispatch(&testQEvt[c - 'a']);            // dispatch event
      }
      return 0;
    }
    
    extern "C" void onAssert__(char const *file, unsigned line)
    {
      fprintf(stderr, "Assertion failed in %s, line %d", file, line);
      exit(-1); 
    }
    

Programming Advice

Match code to model

  • In general, try to make the static code structure map 1-1 to a statechart diagram representation.
  • Research question: Structure code so that a statechart diagram can be produced automatically from code file?

Construct complete state handlers.

  • This means not delegating state transitions to function calls, rather keep transitions explicitly in the main case block of the handler.
  • Code solution to avoid:
  • QSTATE MyHsm::stateAB (QEvent const * e)
    {
      switch
      {
        ...
        case MYSIG1_SIG:
          onMySig1(); // hides state transition
          return 0;
        ...
      }
      return (QSTATE)&MyHsm::stateA;
    }
    void MyHsm::onMySig1()
    {
      if (myA && !myB && myC > 0 ...)
      {
        Q_TRAN(&MyHsm::stateB);
      }
    }
    
  • better code solution:
  • QSTATE MyHsm::stateAB (QEvent const * e)
    {
      switch
      {
        ...
        case MYSIG1_SIG:
          if (sig1Guard())
          {
            Q_TRAN(&MyHsm::stateB);
          }
          return 0;
        ...
      }
      return (QSTATE)&MyHsm::stateA;
    }
    int MyHsm::sig1Guard()
    {
      if (myA && !myB && myC > 0 ...)
      {
        return 1;
      }
      return 0;
    }
    

Keep HSM topology static

  • It's possible to inadvertantly introduce dynamic topology with code
  • code that changes transition target at runtime:
  • QSTATE MyHsm::stateAB (QEvent const * e)
    {
      switch
      {
        ...
        case MYSIG1_SIG:
          Q_TRAN((...) ? (QState)stateB : (QState)stateC); // NO!
          return 0;
        ...
      }
      return (QSTATE)&MyHsm::stateA;
    }
    
  • static (compile time) determination of target:
  • QSTATE MyHsm::stateAB (QEvent const * e)
    {
      switch
      {
        ...
        case MYSIG1_SIG:
          if (...)
            Q_TRAN(stateB);
          else
            Q_TRAN(stateC);
          return 0;
        ...
      }
      return (QSTATE)&MyHsm::stateA;
    }
    

Choose "right" signal granularity

  • Too fine -- repeated signal handling code
  • Possible fixes:
    1. Combine signals into one type
    2. Define superstate to handle at higher level
  • Too coarse -- many guard conditions
  • Possible fixes:
    1. Expand/subdivide signals
    2. Define substates to handle some at lower level, keep others at higher level
    3. DON'T delegate handling, keep state handlers complete

Allow active composit states

  • Relax the UML rule requiring a composit state to have at least one active substate:


    1. Satisfies rule


    2. Breaks rule - better solution
    Fig 4.4

Inside The Event Processor

    Assumptions: Enforced by DBC where possible
    The Is In State Query: int  QHsm::isIn(QState state)
    Initializing the HSM : void QHsm::init(QEvent const *e)
    Dispatching Events : void QHsm::dispatch(QEvent const *e)
    Dynamic Transitions : void QHsm::tran(QState target)
    Static Transitions : void QHsm::tranStat(Tran * tran, QState target)
    The Transition Path : void QHsm::tranSetup(Tran * tran, QState target)

Assumptions

  • Initial transitions must go one level deep
  • Cannot transition to top state
  • Static State Machine Topology (see Transition to History Pattern)
  • One more signal, for internal use only:
    enum
    {
      Q_EMPTY_SIG = 0  // internal server signal
    };
    
    (in addition to those for client program use:)
    enum  // standard signals - for use by clients
    {
      Q_INIT_SIG = 1,
      Q_ENTRY_SIG,
      Q_EXIT_SIG,
      Q_USER_SIG
    };
    
  • Externally allocated standard events
  • extern QEvent const pkgStdEvt[]; // preallocated standard events
    

The Is In State Query

  • Uses handling of empty signal as probe for parent state
  • #define TRIGGER(state_, sig_) \
       Q_STATE_CAST((this->*(state_))(&pkgStdEvt[sig_]))
    
    int QHsm::isIn(QState state)
    {
      register QState s;
      for (s = myState; s != 0; s = TRIGGER(s, Q_EMPTY_SIG))
      {
        if (s == state)                           // do the states match?
        {
          return !0;                          // match found, return true
        }
      }
      return 0;                           // no match found, return false
    } 
    

Initializing the HSM

  • Constructor initializes myState = top and mySource = initial
  • Machine initialized in separate method init()
    1. Make initial transition to child (direct) substate; enforces direct substate constraint
    2. Loop: calls the state handler with the reserved signal Q_ENTRY_SIG, continuing as long as event is handled, enforcing the direct substate constraint at each step
    3. Direct substate constraint does not prevent initialization from descending as deep as desired; it just makes it happen one level at a time
    4. Note all the design by contract checks
  • initial() must be implemented by derived class (specific HSM)
  • init() must be called (via the macro Q_INIT)
    1. in the (required) implementation of the initial handler initial
    2. in any handlers that handle the Q_INIT_SIG signal
  • QHsm::QHsm(QPseudoState initial) 
       : myState(&QHsm::top), mySource(Q_STATE_CAST(initial))
    {}
    
    void QHsm::init(QEvent const *e)
    {
      REQUIRE(myState == &QHsm::top &&           // HSM not executed yet
              mySource != 0);    // we are about to dereference mySource
      register QState s = myState;        // save myState in a temporary
      (this->*(QPseudoState)mySource)(e); // top-most initial transition
                          // initial transition must go *one* level deep
      ASSERT(s == TRIGGER(myState, Q_EMPTY_SIG));
      s = myState;                               // update the temporary
      TRIGGER(s, Q_ENTRY_SIG);                        // enter the state
      while (TRIGGER(s, Q_INIT_SIG) == 0)               // init handled?
      {                   
                          // initial transition must go *one* level deep
        ASSERT(s == TRIGGER(myState, Q_EMPTY_SIG));
        s = myState;
        TRIGGER(s, Q_ENTRY_SIG);                  // enter the substate
      }
    }
    

Dispatching Events

  • Climbs the state hierarchy until a state handler returns 0
  • The handler that returns 0 is the one that actually handles the event
  • Note that during this last call to a handler, mySource is the state from which the transition occurs. (Is there a DBC mechanism to guarantee this? It depends on client program making topologically correct returns from state handlers.)
  • void QHsm::dispatch(QEvent const *e)
    {
      for (mySource = myState; mySource != 0;
           mySource = Q_STATE_CAST((this->*mySource)(e))) 
      {}
    }
    

Dynamic Transitions

  • tran() call made via macro:
  • #define Q_TRAN_DYN(target_) tran(Q_STATIC_CAST(QState, target_))
    
  • tran()Implementation Plan:
    1. Note that call is made from dispatch() by handler of source state, so mySource is correct
    2. Ascend from myState to mySource, invoking exit actions
    3. Take cases (a) - (g) as an optimization, seeking LCA and path from target to LCA
    4. Use TRIGGER(s, Q_EMPTY_SIG) to elicit superstate info
    5. Descend path to target, invoking entry actions
    6. Transition Illustration
    7. Transition Illustration Detail
    8. Case Illustrations
    void QHsm::tran(QState target)
    {
      REQUIRE(target != &QHsm::top);        // cannot target "top" state
      QState entry[7], p, q, s, *e, *lca;
      for (s = myState; s != mySource; )
      {
        ASSERT(s != 0);                // we are about to dereference s
        QState t = TRIGGER(s, Q_EXIT_SIG);
        if (t != 0)    // exit action unhandled, t points to superstate
        {
             s = t;
        }
        else                  // exit action handled, elicit superstate
        {
           s = TRIGGER(s, Q_EMPTY_SIG);
        }
      }
    
      *(e = &entry[0]) = 0;
      *(++e) = target;                         // assume entry to target
    
      // (a) check mySource == target (transition to self)
      if (mySource == target)
      {
        TRIGGER(mySource, Q_EXIT_SIG);                    // exit source
        goto inLCA;
      }
    
      // (b) check mySource == target->super
      p = TRIGGER(target, Q_EMPTY_SIG);
      if (mySource == p)
      {
        goto inLCA;
      }
    
      // (c) check mySource->super == target->super (most common)
      q = TRIGGER(mySource, Q_EMPTY_SIG);
      if (q == p)
      {
        TRIGGER(mySource, Q_EXIT_SIG);                   // exit source
        goto inLCA;
      }
    
      // (d) check mySource->super == target
      if (q == target)
      {
        TRIGGER(mySource, Q_EXIT_SIG);                   // exit source
        --e;                                    // do not enter the LCA
        goto inLCA;
      }
    
      // (e) check rest of mySource == target->super->super... hierarchy
      *(++e) = p;
      for (s = TRIGGER(p, Q_EMPTY_SIG); s != 0;
           s = TRIGGER(s, Q_EMPTY_SIG)) 
      {
        if (mySource == s)
        {
           goto inLCA;
        }
        *(++e) = s;
      }
      TRIGGER(mySource, Q_EXIT_SIG);                      // exit source
    
      // (f) check rest of mySource->super == target->super->super...
      for (lca = e; *lca != 0; --lca)
      {
        if (q == *lca)
        {
          e = lca - 1;                           // do not enter the LCA
          goto inLCA;
        }
      }
    
      // (g) check each mySource->super->super..for each target...
      for (s = q; s != 0; s = TRIGGER(s, Q_EMPTY_SIG))
      {
        for (lca = e; *lca != 0; --lca)
        {
          if (s == *lca)
          {
            e = lca - 1;                         // do not enter the LCA
            goto inLCA;
          }
        }
      TRIGGER(s, Q_EXIT_SIG);                                  // exit s
      }   
      ASSERT(0);                                        // malformed HSM
    
    inLCA:               // now we are in the LCA of mySource and target
      ASSERT(e < &entry[DIM(entry)]);         // new entry e must fit in
      while ((s = *e--) != 0)  //retrace the entry path in reverse order
      {
        TRIGGER(s, Q_ENTRY_SIG);                              // enter s
      }
      myState = target;                          // update current state
      while (TRIGGER(target, Q_INIT_SIG) == 0)
      {
        // initial transition must go *one* level deep
        ASSERT(target == TRIGGER(myState, Q_EMPTY_SIG));
        target = myState;
        TRIGGER(target, Q_ENTRY_SIG);                     // enter target
      }
    }
    

Static Transitions

  • tranStat() call made via macro:
  • #define Q_TRAN(target_) if (1)
    { \
      static Tran t_; \
      tranStat(&t_, Q_STATIC_CAST(QState, target_));\
    }
    else ((void)0)
    
    Effect: substitute the code block
    {
      static Tran t_;
      tranStat(&t_, Q_STATIC_CAST(QState, target_));
    }
    
    Note the subtlty: t_ is locally scoped in association with the particular transition, yet it is a persistent (static) data item.

  • tranStat() implementation plan:
    1. Use algorithm implemented for tran() once for each transition
    2. Store transition path in static Tran t_ associated with transition
    3. Re-use transition path info
    4. Delegate transition path discovery to method tranSetup()
    void QHsm::tranStat(Tran *tran, QState target)
    {
      REQUIRE(target != &QHsm::top);       // cannot target "top" state
      register QState s;
    
      // ascend to mySource, invoking exit actions
      for (s = myState; s != mySource; )
      {
        ASSERT(s != 0);                // we are about to dereference s
        QState t = TRIGGER(s, Q_EXIT_SIG);
        if (t != 0)    // exit action unhandled, t points to superstate
        {
          s = t;
        }
        else                     exit action handled, elicit superstate
        {
          s = TRIGGER(s, Q_EMPTY_SIG);
        }
      }
    
      // check for transition object
      if (tran->myActions == 0)      // is the tran object initialized?
      {
        tranSetup(tran, target);         // setup the transition object
      }
    
      // transition object initialized, execute transition chain
      {
        register QState *c = &tran->myChain[0];
        register unsigned short a;
        for (a = tran->myActions >> 1; a != 0; a >>= 2, ++c)
        {
          (this->*(*c))(&pkgStdEvt[a & 3]);
        }
        myState = *c;
      }
    }
    

The Transition Path

    void QHsm::tranSetup(Tran *tran, QState target)
    {
      QState entry[DIM(tran->myChain)], p, q, s, *c, *e, *lca;
      unsigned short a = 0;
    
      #define RECORD(state_, sig_) \
      if (TRIGGER(state_, sig_) == 0)\
      {\
        a |= ((sig_) << 14);   \
        a >>= 2;               \
        *c++ = (state_);       \
      }\
      else ((void)0)
    
      c = &tran->myChain[0];
      *(e = &entry[0]) = 0;
      *(++e) = target;                        // assume entry to target
    
      // (a) check mySource == target (transition to self)
      if (mySource == target)
      {
        RECORD(mySource, Q_EXIT_SIG);                    // exit source
        goto inLCA;
      }
     
      // (b) check mySource == target->super
      p = TRIGGER(target, Q_EMPTY_SIG);
      if (mySource == p)
      {
        goto inLCA;
      }
    
      // (c) check mySource->super == target->super (most common)
      q = TRIGGER(mySource, Q_EMPTY_SIG);
      if (q == p)
      {
        RECORD(mySource, Q_EXIT_SIG);                    // exit source
        goto inLCA;
      }
    
      // (d) check mySource->super == target
      if (q == target)
      {
        RECORD(mySource, Q_EXIT_SIG);                    // exit source
        --e;                                    // do not enter the LCA
        goto inLCA;
      }
    
      // (e) check rest of mySource == target->super->super... hierarchy
      *(++e) = p;
      for (s = TRIGGER(p, Q_EMPTY_SIG); s != 0; 
           s = TRIGGER(s, Q_EMPTY_SIG)) 
      {
        if (mySource == s)
        {
          goto inLCA;
        }
        *(++e) = s;
      }
      RECORD(mySource, Q_EXIT_SIG);                     // exit source
    
      // (f) check rest of mySource->super == target->super->super...
      for (lca = e; *lca != 0; --lca)
      {
        if (q == *lca)
        {
          e = lca - 1;                         // do not enter the LCA
          goto inLCA;
        }
      }
    
      // (g) check each mySource->super->super..for each target...
      for (s = q; s != 0; s = TRIGGER(s, Q_EMPTY_SIG))
      {
        for (lca = e; *lca != 0; --lca)
        {
          if (s == *lca)
          {
            e = lca - 1;                       // do not enter the LCA
            goto inLCA;
          }
        }
        RECORD(s, Q_EXIT_SIG);                               // exit s
      }   
      ASSERT(0);                                      // malformed HSM
    
    inLCA:             // now we are in the LCA of mySource and target
      ASSERT(e < &entry[DIM(entry)]);       // new entry e must fit in
      while ((s = *e--) != 0) //retrace the entry path in reverse order
      {
          RECORD(s, Q_ENTRY_SIG);                           // enter s
      }
      myState = target;                        // update current state
      while (TRIGGER(target, Q_INIT_SIG) == 0)
      {
        // initial transition must go *one* level deep
        ASSERT(target == TRIGGER(myState, Q_EMPTY_SIG));
        a |= (Q_INIT_SIG << 14);
        a >>= 2;
        *c++ = target;
        target = myState;
        RECORD(target, Q_ENTRY_SIG);                   // enter target
      }
      #undef RECORD
      *c = target;
      tran->myActions = (a >> (13 - (c - &tran->myChain[0])*2)) & 0x1;
    
      // transition initialized
      ENSURE(tran->myChain[0] != 0 &&       
        c < &tran->myChain[DIM(tran->myChain)]);     // check overflow
    }
    

Review

  • Assumptions/Limitations
    1. Initial transitions must go one level deep (DBC enforced)
    2. Cannot transition to top state (DBC enforced)
    3. Static State Machine Topology (NOT DBC enforced)
      (see Transition to History Pattern)
    4. No Mealey transitions
    5. Event processor only, no execution environment
  • Straightforward and logical to create client HSM application
    1. Client class: Derive MyHsm publicly from QHsm
      provide implementation for initial() in top state
    2. States: Handler methods in class MyHsm derived from QHsm
    3. Transitions: Represented as case in source state handler, ending with
      Q_TRAN(target); return 0;
    4. Superstates: Named in return statement ending handler methods, as in
      return (QSTATE)&MyHsm::superState;
    5. Entry, exit, and init actions: Handled as cases triggered by reserve signals
      Q_ENTRY_SIG, Q_EXIT_SIG, Q_INIT_SIG
  • Easy to change HSM topology
    1. New state: Add new state handler to MyHsm
    2. New transition: Add case to state sourceState handler ending with Q_TRAN(targetState)
    3. Change target: Change argument of call Q_TRAN(target)
    4. Change superstate: Modify the return statement at end of substate handler
  • Good runtime efficiency and small memory footprint
    1. Internal dispatch = de-reference pointer-to-member (once for each step up through state hierarchy to state that handles event)
    2. QHsm adds only two function pointers to memory footprint of client
  • Pay only for what you use
    1. Static topology efficiency
    2. Add dynamic cases when needed (see Transition to History)


Notes

1. Definition of QPseudoState expanded for reading:

    typedef        // defining signature of pseudostate handler method
      void                                    // return type of method
        (QHsm::*                         // class membership of method
          QPseudoState                             // typename defined
            )
              (QEvent const*)                         // argument list
    ;
    

2. Definition of QState expanded for reading:

    typedef              // defining signature of state handler method
      QPseudoState                            // return type of method
        (QHsm::*                         // class membership of method
          QState                                   // typename defined
            )
             (QEvent const*)                // argument list of method
    ;
    

3. State and Superstate

    typedef QHsm::QPseudoState QSTATE;        // state-handler return type
    1. State event handler returns the parent state as default case
    2. Minimal info required to construct state hierarchy
    3. Example from QCalc:
    QSTATE Calc::operand1(QEvent const *e)         // state-handler signature
    {
      switch (e->sig)
      {
        case Q_ENTRY_SIG:            // handle entry-sig event (entry action)
          dispState("operand1");
          return 0;
        case IDC_CE:                              // handle clear-entry event
          clear();
          Q_TRAN(&Calc::begin);                      // transition to "begin"
          return 0;
        case IDC_OPER:                       // handle operator-entered event
          sscanf(myDisplay, "%lf", &myOperand1);
          myOperator = (static_cast<CalcEvt *>(e))->keyId; 
          Q_TRAN(&Calc::opEntered);              // transition to "opEntered"
          return 0;
      }
      return (QSTATE)&Calc::calc;   // event not handled, return parent state
    }
    
    Fig 1.3

4. Hierarchical event dispatching

    void QHsm::dispatch(QEvent const *e)
    {
      for (mySource = myState; mySource != 0;
           mySource = Q_STATE_CAST((this->*mySource)(e))) 
      {}
    }
    

5. Class Tran

    class Tran
    {
      public:
        QState myChain[7];
        unsigned short myActions;      // action mask (2-bits for action)
    };
    
    1. Records transition chain data for re-use during static transitions
    2. myChain records complete transition chain for a static transition
    3. myActions encodes signals to state handlers for transition chain

6. Protected constructor and destructor

    1. Public constructor must be defined in derived class (HSM instance)
    2. Forces derived class to define initial state handler
    3. Typically derived constructor will be "default" and call parent class constructor
    4. Example:
    class QHsm
    {
      protected:
        QHsm(QPseudoState initial);
      // ...
    };
    
    class QHsmTst : public QHsm
    {
      public:
        QHsmTst() : QHsm ((QPseudoState)initial)  // default constructor
        {}
      // ...
    };
    

7. Events and Signals Example

    QSTATE Calc::zero1(QEvent const *e)         // state-handler signature
    {
      switch (e->sig)
      {
        case Q_ENTRY_SIG:         // handle entry signal event (entry action)
          dispState("zero1");
          return 0;
        case IDC_1_9:                             // handle digit(1..9) event
          insert(static_cast<CalcEvt *>(e)->keyId); 
          Q_TRAN(&Calc::int1);                        // transition to "int1"
          return 0;
        case IDC_POINT:                                   // handle "." event
          insert(((CalcEvt *)e)->keyId);
          Q_TRAN(&Calc::frac1);                      // transition to "frac1"
          return 0;
      }
      return (QSTATE)&Calc::operand1; // event not handled, return parent state
    }
    
    Fig 2.8

8. Initial Transtion and Entry Action Example

    QSTATE Calc::calc(QEvent const *e)
    {
      switch (e->sig)
      {
        case Q_ENTRY_SIG:  // no state transition
          dispState("calc");
          return 0;
        case Q_INIT_SIG:   // transition to initial via Q_INIT()
          clear();
          Q_INIT(&Calc::ready);
          return 0;
        case IDC_C:
          clear();
          Q_TRAN(&Calc::calc);
          return 0;
        case TERMINATE:
          Q_TRAN(&Calc::final);
          return 0;
      }
      if (e->sig >= Q_USER_SIG) // a form of exception handling
      {
        isHandled = FALSE;
      }
      return (QSTATE)&Calc::top;
    }
    

9. Initial transition for QCalc

    void Calc::initial(QEvent const *)
    {
      clear();
      Q_INIT(&Calc::calc);
    }