The behavior of each active object in QP Framework is specified by means of a hierarchical state machine (UML statechart), which is the most effective and elegant technique of describing event-driven behavior. The most important innovation of UML state machines over classical finite state machines (FSMs) is the hierarchical state nesting. The value of state nesting lies in avoiding repetitions, which are inevitable in the traditional "flat" FSM formalism and are the main reason for the "state-transition explosion" in FSMs. The semantics of state nesting allow substates to define only the differences of behavior from the superstates, thus promoting sharing and reusing behavior.
The Quantum Leaps Application Note A Crash Course in UML State Machines introduces the main state machine concepts backed up by examples.
This section describes how to implement hierarchical state machines with the QP™/C real-time embedded framework, which is quite a mechanical process consisting of just a few simple rules. (In fact, the process of coding state machines in QP™/C has been automated by the QM model-based design and code-generating tool.)
To focus this discussion, this section uses the Calculator example, located in the directory qpcpp/examples/workstation/calc. This example has been used in the PSiCC2 book (Section 4.6 "Summary of Steps for Implementing HSMs with QEP")
This section explains how to code the following (marked) elements of a hierarchical state machine:
[1]
The top-most initial pseudo-state
[2]
A state (nested in the implicit top
state)
[3]
An entry action to a state
[4]
An exit action from a state
[5]
An initial transition nested in a state
[6]
A regular state transition
[7]
A state (substate) nested in another state (superstate)
[8]
Even more deeply nested substate
[9]
A choice point with a guard
Hierarchical state machines are represented in QP™/C as subclasses of the QHsm abstract base class, which is defined in the header file qpc\include\qep.h. Please note that abstract classes like ::QMsm, ::QActive and ::QMActive are also subclasses of ::QHsm, so their subclasses also can have state machines.
[1]
Class Calc
(Calculator) derives from ::QHsm, so it can have a state machine
[2]
The class can have data members (typically private), which will be accessible inside the state machine as the "extended-state variables".
[3]
The class needs to provide a constructor, typically without any parameters.
[4]
The constructor must call the appropriate superclass' constructor. The superclass' constructor takes the top-most a pointer to the Calc_initial
pseudo-state (see step [5]), which binds the state-machine to the class.
[5]
Each state machine must have exactly one initial pseudo-state, which by convention should be always called initial. The initial pseudo-state is declared with the shown parameter list.
[6]
All other state handlers are declared with the shown signature.
The definition of the state machine class is the actual code for your state machine. You need to define (i.e., write the code for) all "state-handler" member functions you declared in the state machine class declaration.
One important aspect to realize about coding "state-handler" functions is that they are always called during the process of dispatching events. The purpose of the "state-handlers" is to perform your specific actions and then to tell the event processor what needs to be done with the state machine. For example, if your "state-handler" performs a state transition, it executes some actions and then it calls the special QHsm_tran_(<target>)
function, where it specifies the <target>
state of this state transition. The state-handler then returns the status from the tran()
function, and through this return value it informs the dispatch operation what needs to be done with the state machine. Based on this information, the event-processor might decide to call this or other state-handler functions to process the same current event. The following code examples should make all this clearer.