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, who's 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 provides 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 |
SDS_QA_...
(with the component part set to QA
rather than QP
).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 element in 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.
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.
[5]
). Most other data members are initialized in the top-most initial transition, which is taken later, when when the state machine is initialized (see SDS_QA_QHsm_top_init).[11]
The class constructor, must call the superclass' constructor (QHsm_ctor() constructor in this case). The superclass' 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 be always called initial
.
[30]
All states are represented as member functions of the sate 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.
Implementing the top-most initial pseudo-state.
init()
function). [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])
Implementing a state and its nesting.
[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 implement 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.
Implementing state entry action.
[1]
State with an entry action needs a case-statement on the reserved signal Q_ENTRY_SIG. case Q_ENTRY_SIG
is needed only if a state actually has something to do upon the state entry. If there is nothing to do, the whole case can be just omitted.[2]
Inside the case, you code all the actions to be called upon the 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.
Implementing state exit action.
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. [1]
State with an exit action needs a case
statement on the reserved signal Q_EXIT_SIG case Q_EXIT_SIG
is needed only if a state actually has something to do upon the state exit. If there is nothing to do, the whole case can be just omitted.[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.
Implementing 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])
Implementing 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])
Implementing 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.
Implementing choice point and guard conditions.
[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.
else if (guard)
[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..
Implementing 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.
Implementing transition to 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.
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.
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.
[5]
). Most other data members are initialized in the top-most initial transition, which is taken later, when when the state machine is initialized (see SDS_QA_QHsm_top_init).[11]
The class constructor, must call the superclass' constructor (QP::QMsm::QMsm() constructor in this case). The superclass' 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 be always 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:
[30]
state-handler for all transitions (including internal transitions)[31]
state action handler for state entry[32]
state action handler for state exit[33]
state action handler for nested initial transition within this state[34]
state object that summarizes the information about this state (const in ROM)toasting
does not have an exit action, so it does not define the QM_ACTION_DECL(toasting_x)
action function. [40]
The class might also provide any number of action functions that will be called in the state machine.
Implementing the top-most initial pseudo-state.
init()
function). [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:
[6]
the target state of the initial transition[7]
the entry action to that target state[8]
the nested initial transition in that target state[9]
the "zero terminator" that indicates the end of the "tran-action table"[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])
Implementing a state and its nesting.
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.
[1-6]
The const
state data struct for the ToasterOven
state "doorClosed" is defined and initialized with:
[2a]
The superstate of this state (QM_STATE_NULL in case of a top-level state)[2b]
The superstate of this state (pointer to the state data struct in case of a state nested in another state)[3]
The state-handler function pointer to process application-defined events[4]
The action-handler function pointer to execute upon the entry to the state (might be Q_ACTION_NULL)[5]
The action-handler function pointer to execute upon the exit from the state (might be Q_ACTION_NULL)[6]
The action-handler function pointer to execute upon the nested initial transition in the state (might be Q_ACTION_NULL)[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.
Implementing state 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 the 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.
Implementing state 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.
Implementing nested initial transition.
[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:
[4]
the target state of the initial transition[5]
the entry action to that target state[6]
the "zero terminator" that indicates the end of the "tran-action table"[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]).
Implementing 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:
[4]
the target state of the initial transition[5]
the exit action from the source state[6]
the entry action to that target state[7]
the "zero terminator" that indicates the end of the "tran-action table"[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]
Implementing 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.
Implementing choice point and guard conditions.
[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.
else if (guard)
[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.
Implementing 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.
Implementing transition to 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 by means of 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]