The State Dynamics Viewpoint explains how QP Application shall implement hierarchical state machines. This design viewpoint is most important and relevant to QP Application developers, whose primary activity is designing and ultimately implementing the hierarchical state machines of Active Objects.
This viewpoint consists of two parts:
The design concerns in this viewpoint correspond to the "recipes" for implementing various state machine elements. The state diagram and the table below list the elements and provide links to the design views in the "QHsm-based implementation" and "QMsm-based implementation".
The state machine elements labeled in Figure SDS-SM are as follows:
| Label | Design concern | QHsm-based implementation | QMsm-based implementation |
|---|---|---|---|
| declaring application-level state machine subclass | SDS_QA_QHsm_decl | SDS_QA_QMsm_decl | |
| [1] | implementing top-most initial pseudo-state | SDS_QA_QHsm_top_init | SDS_QA_QMsm_top_init |
| [2] | implementing states (including states nested in other states) | SDS_QA_QHsm_state | SDS_QA_QMsm_state |
| [3] | implementing state entry actions | SDS_QA_QHsm_entry | SDS_QA_QMsm_entry |
| [4] | implementing state exit actions | SDS_QA_QHsm_exit | SDS_QA_QMsm_exit |
| [5] | implementing nested initial transitions | SDS_QA_QHsm_nest_init | SDS_QA_QMsm_nest_init |
| [6] | implementing state transitions | SDS_QA_QHsm_tran | SDS_QA_QMsm_tran |
| [7] | implementing internal transitions | SDS_QA_QHsm_intern | SDS_QA_QMsm_intern |
| [8] | implementing choice points and guard conditions | SDS_QA_QHsm_choice | SDS_QA_QMsm_choice |
| [9] | Guard condition with internal transition | SDS_QA_QHsm_choice | SDS_QA_QMsm_choice |
| [10] | Guard condition with regular transition | SDS_QA_QHsm_choice | SDS_QA_QMsm_choice |
| [11] | implementing state history | SDS_QA_QHsm_hist | SDS_QA_QMsm_hist |
| [12] | implementing transition to state history | SDS_QA_QHsm_hist_tran | SDS_QA_QMsm_hist_tran |
The model kind used to illustrate the State Dynamics Viewpoint is the UML state diagram shown in Figure SDS-SM and Figure SDS-SM (static views). These state diagrams depict hierarchical state machines (for a toaster oven) with labeled elements, which are subsequently elaborated in various traceable Design Views.
This section presents the hierarchical state machine implementation strategy "optimized for manual coding", which means that changing a single element in the state machine design (e.g., nesting of the state hierarchy) should require changing only a single matching component of the implementation. This strategy imposes restrictions on the implementation, but does not mean that the code must be written manually. In the presence of a modeling tool, such code can also be generated automatically, and most of the code presented in this section has indeed been generated by the QM modeling tool↑ (from the state diagram presented in Figure SDS-SM).
Declaring QP::QHsm subclass.
Description
The first step in coding a hierarchical state machine based on the QP::QHsm implementation is to declare a subclass of the QP::QHsm or the QP::QActive base class.
Example
The following snippet of code shows the complete declaration of the ToasterOven subclass of QP::QHsm shown in Figure SDS-SM:
[1] Declaration of class ToasterOven
[2] Class ToasterOven inherits QP::QHsm, so it can have a state machine that must be implemented according to the rules specified in the QHsm Design View.
[3] The class can have data members (typically private), which will be accessible inside the state machine as the "extended-state variables".
[4] If the state machine uses state history (Figure SDS-SM [11]), a data member must be provided to remember each state's history.
[10] The class needs to provide a constructor, typically without any parameters.
[11] The class constructor must call the superclass' constructor (QHsm constructor in this case). The superclass's constructor initializes this state machine to the top-most initial pseudostate declared in step [5].
[12] The class constructor must call the constructors of its data members, if such constructors exist.
[20] Each state machine must have exactly one initial pseudo-state, which is a member function of the class as shown in the listing. By convention, the initial pseudo-state should always be called initial.
[30] All states are represented as member functions of the state machine class (hence they are called state-handler functions). Every state-handler function takes the const pointer to the current event as a parameter.
[40] The class might also provide any number of action functions that will be called in the state machine.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing the top-most initial pseudo-state.
Description
The top-most initial pseudostate is a member function of the state machine class (see Figure SDS-SM [1]). This pseudostate defines the top-most initial transition, which will be taken when the state machine is initialized (see SDS_QP_QAsm interface init() function).
Example
The following example code shows a top-most initial pseudo-state function with various elements explained in the labeled annotations following the listing.
[1] The top-most pseudostate definition starts with Q_STATE_DEF() macro (see also SDS_QA_QHsm_decl [5]). The macro parameters are the state machine name and the state name.
[2] The top-most initial pseudostate initializes internal variables (see also SDS_QA_QHsm_decl [3])
[3] The top-most initial pseudostate initializes state histories for all states with history (see also SDS_QA_QHsm_hist)
[4] If QS software tracing is used, the top-most initial pseudostate produces function dictionaries for all its state-handler functions
[5] The top-most initial pseudostate must return with the tran() function. The argument of this function is the target state indicated in the diagram (see Figure SDS-SM [1])
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing a state and its nesting.
Description
States are member functions of the state machine class (see Figure SDS-SM [2]). State-handler functions are called during the process of dispatching events. Their purpose is to perform your specific actions and then to tell the %QP event processor what your state-handler has just done. For example, if your state-handler performs a state transition, it executes some actions and then it calls the special Q_TRAN() macro, where it specifies the target state of this state transition. The state-handler then returns the status from the Q_TRAN() macro, 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.
Example
The following example code shows two state-handler functions with almost identical structure. The first one implements a top-level state. The second one implements a state that is explicitly nested in another state.
[1] The state definition starts with Q_STATE_DEF() macro (see also SDS_QA_QHsm_decl [6]). The macro parameters are the state machine name and the state name.
[2] The local status_ variable to return at the end of the state-handler function
[3] The state-handler function is structured as a switch statement that discriminates based on the signal of the current event e->sig (see SDS_QP_QEvt)
[4] The case of the switch implements various state elements explained in the remaining Design Elements in this viewpoint.
[5] The switch must always have the default case, which should be executed without side effects.
[6a] For a state that does not nest in any other state, you need to set the status_ variable to the super() function, whereas the argument top() is the top-most state handler function defined in the QP::QHsm base class.
[6b] For a state that explicitly nests in another state, you need to set the status_ variable to the super() function, whereas the argument is the superstate of that state.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing state entry action.
Description
State entry action (see Figure SDS-SM [3]) is coded as special case for the reserved signal QP::Q_ENTRY_SIG. The QP event processor executes the entry action during a state transition processing by calling the state-handler function, passing in the reserved event with the QP::Q_ENTRY_SIG. Please note that the entry action has no access to the original event that triggered the state transition.
Example
The following example code shows a state-handler function with an entry action.
[1] State with an entry action needs a case-statement on the reserved signal Q_ENTRY_SIG.
[2] Inside the case, you code all the actions to be called upon entry to the state.
[3] The action list must end with setting the status to the Q_RET_HANDLED value, which informs the QP event processor that the entry action has been handled.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing state exit action.
Description
State exit action (see Figure SDS-SM [4]) is coded as special case on the reserved signal QP::Q_EXIT_SIG. The QP event processor executes the exit action during a state transition processing by calling the state-handler function, passing in the reserved event with the QP::Q_EXIT_SIG. Please note that the entry action has no access to the original event that triggered the state transition.
Example
The following example code shows a state-handler function with an exit action.
[1] State with an exit action needs a case statement on the reserved signal Q_EXIT_SIG
[2] Inside the case, you code all the actions to be called upon the exit from the state.
[3] The action list must end with setting the status to the Q_RET_HANDLED value, which informs the QP event processor that the exit action has been handled.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing nested initial transition.
Description
Nested initial transition (see Figure SDS-SM [5]) in a state is coded as special case for the reserved signal QP::Q_INIT_SIG. If a state has no nested initial transition, the whole case can be just omitted.
Example
The following example code shows a state-handler function with nested initial transition.
[1] State with a nested initial transition needs a case-statement on the reserved signal Q_INIT_SIG
[2] Inside the case, you code all the actions to be called upon the initial transition.
[3] The action list must end with setting the status to the tran() function. The argument of this function is the target state indicated in the diagram (see Figure SDS-SM [5])
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing state transition.
Description
A state transition (see Figure SDS-SM [6]) is coded as a special case for the user-defined signal (transition trigger).
Example
The following example code shows a state-handler function with a state transition.
[1] State with a transition needs a case-statement on the user-defined signal (transition trigger).
[2] Inside the case, you code all the actions to be called upon the transition.
[3] The action list must end with setting the status to the tran() function. The argument of this function is the target state indicated in the diagram (see Figure SDS-SM [6])
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing internal transition.
Description
An internal transition (see Figure SDS-SM [7]) is coded as a special case for the user-defined signal (transition trigger).
Example
The following example code shows a state-handler function with an internal transition.
[1] State with a transition needs a case-statement on the user-defined signal (transition trigger).
[2] Inside the case, you code all the actions to be called upon the transition.
[3] The action list must end with setting the status to the Q_RET_HANDLED value, which informs the QP event processor that the internal transition has been handled.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing choice point and guard conditions.
Description
A choice point with guard conditions (see Figure SDS-SM [8]) is coded as a special case for the user-defined signal (transition trigger).
Example
The following example code shows a state-handler function with an internal transition.
[1] State with a choice needs a case-statement for the user-defined signal (transition trigger).
[2] Inside the case, you code all the actions to be called before any guard conditions are evaluated.
[3] The guard condition is represented as a condition of the if statement.
[4] Any actions to be executed if the guard evaluates to 'true'.
[5] Regular state transition is coded as usual with the tran() function with the target of the transition specified as the argument.
[6] The complementary guard condition to all evaluated before is specified with the else statement.
[7] any actions to be executed for this guard condition.
[8] The action list must end with setting the status to the Q_RET_HANDLED value, which informs the QP event processor that the internal transition has been handled..
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing state history.
Description
State history (see Figure SDS-SM [11]) requires remembering the most recent active substate when the given state is exited, which is accomplished in the exit action from the state coded as already described in SDS_QA_QHsm_exit.
Example
The following example code shows an exit action that additionally saves the state history.
[1] State with an exit action needs a case-statement on the reserved signal Q_EXIT_SIG
[2] Inside the case, you code all the actions to be called upon the exit from the state.
[3] One of the actions must be to set the history variable for the given state to the currently active state. The QP::QHsm base class provides two member functions to obtain the current state:
[4] The action list must end with setting the status to the Q_RET_HANDLED value, which informs the QP event processor that the exit action has been executed.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing transition to history.
Description
Transition to state history (see Figure SDS-SM [12]) is coded similarly to a regular state transition, but takes the transition with the
Example
The following example code shows a transition to state history.
[1] State with a transition to history needs a case-statement for the user-defined signal (transition trigger).
[2] Inside the case, you code all the actions to be called upon the transition.
[3] The action list must end with setting the status to the tran_hist() function with the state history variable of the transition specified as the argument.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
This section presents the hierarchical state machine implementation strategy "optimized for automatic code generation" (see SRS_QP_SM_21), which means the implementation may contain some redundant information to improve the speed of the state machine execution at the expense of larger code and data (typically in ROM).
This strategy requires a code generator, such as the QM modeling tool↑. Indeed, the presented code in the Design Views below has been automatically generated by QM (from the state diagram presented in Figure SDS-SM).
Declaring QP::QMsm subclass.
Description
The first step in coding a hierarchical state machine based on the QP::QMsm implementation is to declare a subclass of the QP::QMsm or the QP::QMActive base class.
Example
The following snippet of code shows the complete declaration of the ToasterOven subclass of QP::QMsm shown in Figure SDS-SM:
[1] Declaration of class ToasterOven
[2] Class ToasterOven inherits QP::QMsm, so it can have a state machine that must be implemented according to the rules specified in the QMsm Design View.
[3] The class can have data members (typically private), which will be accessible inside the state machine as the "extended-state variables".
[4] If the state machine uses state history (Figure SDS-SM [11]), a data member must be provided to remember each state's history.
[10] The class needs to provide a constructor, typically without any parameters.
[11] The class constructor must call the superclass' constructor (QP::QMsm::QMsm() constructor in this case). The superclass's constructor initializes this state machine to the top-most initial pseudostate declared in step [20].
[12] The class constructor must call the constructors of its data members, if such constructors exist.
[20] Each state machine must have exactly one initial pseudo-state, which is a member function of the class as shown in the listing. By convention, the initial pseudo-state should always be called initial.
[30-34] States are represented as a group of member functions of the state machine class plus a QP::QMState data structure. The member functions are as follows:
[40] The class might also provide any number of action functions that will be called in the state machine.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing the top-most initial pseudo-state.
Description
The top-most initial pseudostate is a member function of the state machine class (see Figure SDS-SM [1]). This pseudostate defines the top-most initial transition, which will be taken when the state machine is initialized (see SDS_QP_QAsm interface init() function).
Example
The following example code shows a top-most initial pseudo-state function with various elements explained in the labeled annotations following the listing.
[1] The top-most initial pseudostate function signature (see also SDS_QA_QMsm_decl [9])
[2] The top-most initial pseudostate initializes internal variables (see also SDS_QA_QMsm_decl [3])
[3] The top-most initial pseudostate initializes state histories for all states with history (see also SDS_QA_QMsm_hist)
[4] If QS software tracing is used, the top-most initial pseudostate produces function dictionaries for all its state-handler functions
[5-9] The top-most initial pseudostate declares a static and const struct called "tran-action table", which specifies the sequence of actions to execute:
[10] The top-most initial pseudostate must end with the top-most initial transition, which is coded with the QM_TRAN_INIT() macro. The argument of this macro is the "tran-action table" specified in steps [5-9] (see also Figure SDS-SM [1])
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing a state and its nesting.
Description
States in the QMsm-based implementation strategy are constant data structures of type QP::QMState that store:
The QP event processor uses this information to perform state transitions according to the hierarchical state machine semantics specified in SRS_QP_SM_30. Specifically, the action functions are called through the "tran-action tables" to exit the previous state configuration and enter the next state configuration.
Example
The following example code shows two state data structures and the corresponding state-handler functions. The first one implements a top-level state. The second one implements a state that is explicitly nested in another state.
[1-6] The const state data struct for the ToasterOven state "doorClosed" is defined and initialized with:
[10] The state-handler function to process application-defined events
[11] The local status_ variable to return at the end of the state-handler function
[12] The state-handler function is structured as a switch statement that discriminates based on the signal of the current event e->sig (see SDS_QP_QEvt).
[13] The switch must always have the default case, which should be executed without side effects.
[14] The default case must set the status_ to the Q_RET_SUPER value.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing state entry action.
Description
State entry action (see Figure SDS-SM [3]) is coded as a separate member function of the state machine class. The QP event processor executes this state-entry action function through the "tran-action table" when a state transition is taken. Please note that the state-entry action function has no access to the original event that triggered the state transition.
Example
The following example code shows a state-handler function with an entry action.
[1] The state-entry action function is a member of the state machine class.
[2] The state action function calls all the actions to be executed upon entry to the state.
[3] The state-entry action function must return the qm_entry() function, with the argument being the state struct corresponding to the state being entered.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing state exit action.
Description
State exit action (see Figure SDS-SM [3]) is coded as a separate member function of the state machine class. The QP event processor executes this state-exit action function through the "tran-action table" when a state transition is taken. Please note that the state-exit action function has no access to the original event that triggered the state transition.
Example
The following example code shows a state-handler function with an exit action.
[1] The state-exit action function is a member of the state machine class.
[2] The state action function calls all the actions to be executed upon the exit from the state.
[3] If the state has a history transition, the state-exit action function must set the state history variable.
[4] The state-exit action function must return the qm_exit() function, with the argument being the state struct corresponding to the state being exited.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing nested initial transition.
Description
Nested initial transition (see Figure SDS-SM [5]) is coded as a separate member function of the state machine class. The QP event processor executes this initial transition action function through the "tran-action table" when a state transition is taken. Please note that the initial transition function has no access to the original event that triggered the state transition.
Example
The following example code shows an initial transition function.
[1] The initial-transition function has the usual signature of an action-handler.
[2] Any actions on this initial transition are executed
[3-6] The static and const struct called "tran-action table" specifies the sequence of actions to execute:
[7] The state-exit action function must return the qm_tran_init() function. The argument of this macro is the "tran-action table" specified in steps [3-6] (see also Figure SDS-SM [5]).
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing state transition.
Description
A state transition (see Figure SDS-SM [6]) is coded as a special case in the state-handler function.
Example
The following example code shows a state-handler function with a state transition.
[1] State with a transition needs a case-statement on the user-defined signal (transition trigger).
[7] Any actions on this initial transition are executed
[3-7] The static and const struct called "tran-action table" specifies the sequence of actions to execute:
[8] The transition must be requested by means of the qm_tran() function. The argument of this function is the "tran-action table" specified in steps [3-7]
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing internal transition.
Description
An internal transition (see Figure SDS-SM [7]) is coded as a special case-statement in the state-handler function.
Example
The following example code shows a state-handler function with an internal transition.
[1] State with a transition needs a case-statement on the user-defined signal (transition trigger).
[2] Inside the case, you code all the actions to be called upon the transition.
[3] The action list must end with setting the status to the Q_RET_HANDLED value, which informs the QP event processor that the internal transition has been handled.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing choice point and guard conditions.
Description
A choice point with guard conditions (see Figure SDS-SM [8]) is coded as a special case for the user-defined signal (transition trigger).
Example
The following example code shows a state-handler function with an internal transition.
[1] State-handler needs a case-statement on the user-defined signal (transition trigger).
[2] Inside the case, you code all the actions to be called before any guard conditions are evaluated.
[3] The guard condition is represented as a condition of the if statement.
[4] Any actions to be executed if the guard evaluates to 'true'.
[5] As usual in all QMsm transitions, every transition needs a "tran-action table."
[6] state transition to a regular state is coded as usual with the qm_tran() function with the argument being the "tran-action table".
[7] The complementary guard condition to all evaluated before is specified with the else statement.
[8] any actions to be executed for this guard condition.
[9] The action list must end with setting the status to the Q_RET_HANDLED value, which informs the QP event processor that the internal transition has been handled.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing state history.
Description
State history (see Figure SDS-SM [11]) requires remembering the most recent active substate when the given state is exited, which is accomplished in the exit action from the state coded as already described in SDS_QA_QMsm_exit.
Example
The following example code shows an exit action that additionally saves the state history.
[1] The state-exit action function is a member of the state machine class.
[2] The state action function calls all the actions to be executed upon the exit from the state.
[3] If the state has a history transition, the state-exit action function must set the state history variable. The QP::QMsm base class provides two member functions to obtain the current state:
[4] The state-exit action function must return the qm_exit() function, with the argument being the state struct corresponding to the state being entered.
Backward Traceability
Forward Traceability (truncated to 2 level(s))
Implementing transition to history.
Description
Transition to state history (see Figure SDS-SM [12]) is coded similarly to a regular state transition, but uses a different macro to take the transition QM_TRAN_HIST().
Example
The following example code shows a transition to state history.
[1] State with a transition to history needs a case-statement for the user-defined signal (transition trigger).
[2] Inside the case, you code all the actions to be called upon the transition.
[3] The static and const struct called "tran-action table" specifies the sequence of actions to execute.
[4] The transition to history must be requested using the qm_tran_hist() function. The arguments of this function are the history variable for the given state and the "tran-action table" specified in step [3]
Backward Traceability
Forward Traceability (truncated to 2 level(s))