Lib: SMF Modify HSM operation for UML-Style transitions
Modify the SMF such that state transitions from parent states choose the correct Least Common Ancestor based on the transition source rather than the current state. SMF set as experimental. Signed-off-by: Glenn Andrews <glenn.andrews.42@gmail.com>
This commit is contained in:
parent
49d0dd810d
commit
531c457550
8 changed files with 501 additions and 296 deletions
|
@ -493,6 +493,14 @@ State Machine Framework
|
|||
now independent of the values of :kconfig:option:`CONFIG_SMF_ANCESTOR_SUPPORT` and
|
||||
:kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION`. If the additional arguments are not used, they
|
||||
have to be set to ``NULL``. (:github:`71250`)
|
||||
* SMF now follows a more UML-like transition flow when the transition source is a parent of the
|
||||
state called by :c:func:`smf_run_state`. Exit actions up to (but not including) the Least Common
|
||||
Ancestor of the transition source and target state will be executed, as will entry actions from
|
||||
(but not including) the LCA down to the target state. (:github:`71675`)
|
||||
* Previously, calling :c:func:`smf_set_state` with a ``new_state`` set to NULL would execute all
|
||||
exit actions from the current state to the topmost parent, with the expectation the topmost exit
|
||||
action would terminate the state machine. Passing ``NULL`` is now not allowed. Instead create a
|
||||
'terminate' state at the top level, and call :c:func:`smf_set_terminate` from its entry action.
|
||||
|
||||
ZBus
|
||||
====
|
||||
|
|
|
@ -375,6 +375,9 @@ Libraries / Subsystems
|
|||
* State Machine Framework
|
||||
|
||||
* The :c:macro:`SMF_CREATE_STATE` macro now always takes 5 arguments.
|
||||
* Transition sources that are parents of the state that was run now choose the correct Least
|
||||
Common Ancestor for executing Exit and Entry Actions.
|
||||
* Passing ``NULL`` to :c:func:`smf_set_state` is now not allowed.
|
||||
|
||||
* Storage
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ By default, a state can have no ancestor states, resulting in a flat state
|
|||
machine. But to enable the creation of a hierarchical state machine, the
|
||||
:kconfig:option:`CONFIG_SMF_ANCESTOR_SUPPORT` option must be enabled.
|
||||
|
||||
By default, the hierarchical state machine does not support initial transitions
|
||||
By default, the hierarchical state machines do not support initial transitions
|
||||
to child states on entering a superstate. To enable them the
|
||||
:kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION` option must be enabled.
|
||||
|
||||
|
@ -87,31 +87,29 @@ from parent state S0 to child state S2::
|
|||
};
|
||||
|
||||
To set the initial state, the :c:func:`smf_set_initial` function should be
|
||||
called. It has the following prototype:
|
||||
``void smf_set_initial(smf_ctx *ctx, smf_state *state)``
|
||||
called.
|
||||
|
||||
To transition from one state to another, the :c:func:`smf_set_state`
|
||||
function is used and it has the following prototype:
|
||||
``void smf_set_state(smf_ctx *ctx, smf_state *state)``
|
||||
function is used.
|
||||
|
||||
.. note:: If :kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION` is not set,
|
||||
:c:func:`smf_set_initial` and :c:func:`smf_set_state` function should
|
||||
not be passed a parent state as the parent state does not know which
|
||||
child state to transition to. Transitioning to a parent state is OK
|
||||
if an initial transition to a child state is defined. A well-formed
|
||||
HSM will have initial transitions defined for all parent states.
|
||||
HSM should have initial transitions defined for all parent states.
|
||||
|
||||
.. note:: While the state machine is running, smf_set_state should only be
|
||||
called from the Entry and Run functions. Calling smf_set_state from the
|
||||
Exit functions doesn't make sense and will generate a warning.
|
||||
.. note:: While the state machine is running, :c:func:`smf_set_state` should
|
||||
only be called from the Entry or Run function. Calling
|
||||
:c:func:`smf_set_state` from Exit functions will generate a warning in the
|
||||
log and no transition will occur.
|
||||
|
||||
State Machine Execution
|
||||
=======================
|
||||
|
||||
To run the state machine, the :c:func:`smf_run_state` function should be
|
||||
called in some application dependent way. An application should cease calling
|
||||
smf_run_state if it returns a non-zero value. The function has the following
|
||||
prototype: ``int32_t smf_run_state(smf_ctx *ctx)``
|
||||
smf_run_state if it returns a non-zero value.
|
||||
|
||||
Preventing Parent Run Actions
|
||||
=============================
|
||||
|
@ -124,13 +122,38 @@ State Machine Termination
|
|||
=========================
|
||||
|
||||
To terminate the state machine, the :c:func:`smf_set_terminate` function
|
||||
should be called. It can be called from the entry, run, or exit action. The
|
||||
function takes a non-zero user defined value that's returned by the
|
||||
:c:func:`smf_run_state` function. The function has the following prototype:
|
||||
``void smf_set_terminate(smf_ctx *ctx, int32_t val)``
|
||||
should be called. It can be called from the entry, run, or exit actions. The
|
||||
function takes a non-zero user defined value that will be returned by the
|
||||
:c:func:`smf_run_state` function.
|
||||
|
||||
UML State Machines
|
||||
==================
|
||||
|
||||
SMF follows UML hierarchical state machine rules for transitions i.e., the
|
||||
entry and exit actions of the least common ancestor are not executed on
|
||||
transition, unless said transition is a transition to self.
|
||||
|
||||
The UML Specification for StateMachines may be found in chapter 14 of the UML
|
||||
specification available here: https://www.omg.org/spec/UML/
|
||||
|
||||
SMF breaks from UML rules in:
|
||||
|
||||
1. Executing the actions associated with the transition within the context
|
||||
of the source state, rather than after the exit actions are performed.
|
||||
2. Only allowing external transitions to self, not to sub-states. A transition
|
||||
from a superstate to a child state is treated as a local transition.
|
||||
3. Prohibiting transitions using :c:func:`smf_set_state` in exit actions.
|
||||
|
||||
SMF also does not provide any pseudostates except the Initial Pseudostate.
|
||||
Terminate pseudostates can be modelled by calling :c:func:`smf_set_terminate`
|
||||
from the entry action of a 'terminate' state. Orthogonal regions are modelled
|
||||
by calling :c:func:`smf_run_state` for each region.
|
||||
|
||||
State Machine Examples
|
||||
======================
|
||||
|
||||
Flat State Machine Example
|
||||
==========================
|
||||
**************************
|
||||
|
||||
This example turns the following state diagram into code using the SMF, where
|
||||
the initial state is S0.
|
||||
|
@ -232,7 +255,7 @@ Code::
|
|||
}
|
||||
|
||||
Hierarchical State Machine Example
|
||||
==================================
|
||||
**********************************
|
||||
|
||||
This example turns the following state diagram into code using the SMF, where
|
||||
S0 and S1 share a parent state and S0 is the initial state.
|
||||
|
@ -342,17 +365,14 @@ When designing hierarchical state machines, the following should be considered:
|
|||
re-execute the ancestor\'s entry action or execute the exit action.
|
||||
For example, the parent_entry function is not called when transitioning
|
||||
from S0 to S1, nor is the parent_exit function called.
|
||||
- Ancestor exit actions are executed after the sibling exit actions. For
|
||||
example, the s1_exit function is called before the parent_exit function
|
||||
is called.
|
||||
- Ancestor exit actions are executed after the exit action of the current
|
||||
state. For example, the s1_exit function is called before the parent_exit
|
||||
function is called.
|
||||
- The parent_run function only executes if the child_run function does not
|
||||
call either :c:func:`smf_set_state` or :c:func:`smf_set_handled`.
|
||||
- Transitions to self in super-states containing sub-states are not supported.
|
||||
Transitions to self from the most-nested child state are supported and will
|
||||
call the exit and entry function of the child state correctly.
|
||||
|
||||
Event Driven State Machine Example
|
||||
==================================
|
||||
**********************************
|
||||
|
||||
Events are not explicitly part of the State Machine Framework but an event driven
|
||||
state machine can be implemented using Zephyr :ref:`events`.
|
||||
|
@ -499,47 +519,55 @@ Code::
|
|||
}
|
||||
}
|
||||
|
||||
Hierarchical State Machine Example With Initial Transitions
|
||||
===========================================================
|
||||
State Machine Example With Initial Transitions And Transition To Self
|
||||
*********************************************************************
|
||||
|
||||
:zephyr_file:`tests/lib/smf/src/test_lib_self_transition_smf.c` defines a state
|
||||
machine for testing the initial transitions and transitions to self in a parent
|
||||
state. The statechart for this test is below.
|
||||
|
||||
:zephyr_file:`tests/lib/smf/src/test_lib_initial_transitions_smf.c` defines
|
||||
a state machine for testing initial transitions and :c:func:`smf_set_handled`.
|
||||
The statechart for this test is below.
|
||||
|
||||
.. graphviz::
|
||||
:caption: Test state machine for initial transitions and ``smf_set_handled``
|
||||
:caption: Test state machine for UML State Transitions
|
||||
|
||||
digraph smf_hierarchical_initial {
|
||||
compound=true;
|
||||
node [style = rounded];
|
||||
smf_set_initial [shape=plaintext];
|
||||
"smf_set_initial()" [shape=plaintext fontname=Courier];
|
||||
ab_init_state [shape = point];
|
||||
STATE_A [shape = box];
|
||||
STATE_B [shape = box];
|
||||
STATE_C [shape = box];
|
||||
STATE_D [shape = box];
|
||||
DC[shape=point height=0 width=0 label=<>]
|
||||
|
||||
subgraph cluster_ab {
|
||||
label = "PARENT_AB";
|
||||
subgraph cluster_root {
|
||||
label = "ROOT";
|
||||
style = rounded;
|
||||
ab_init_state -> STATE_A;
|
||||
STATE_A -> STATE_B;
|
||||
|
||||
subgraph cluster_ab {
|
||||
label = "PARENT_AB";
|
||||
style = rounded;
|
||||
ab_init_state -> STATE_A;
|
||||
STATE_A -> STATE_B;
|
||||
}
|
||||
|
||||
subgraph cluster_c {
|
||||
label = "PARENT_C";
|
||||
style = rounded;
|
||||
STATE_B -> STATE_C [ltail=cluster_ab]
|
||||
}
|
||||
|
||||
STATE_C -> DC [ltail=cluster_c, dir=none];
|
||||
DC -> STATE_C [lhead=cluster_c];
|
||||
STATE_C -> STATE_D
|
||||
}
|
||||
|
||||
subgraph cluster_c {
|
||||
label = "PARENT_C";
|
||||
style = rounded;
|
||||
STATE_C -> STATE_C
|
||||
}
|
||||
|
||||
smf_set_initial -> STATE_A [lhead=cluster_ab]
|
||||
STATE_B -> STATE_C
|
||||
STATE_C -> STATE_D
|
||||
"smf_set_initial()" -> STATE_A [lhead=cluster_ab]
|
||||
}
|
||||
|
||||
|
||||
|
||||
API Reference
|
||||
*************
|
||||
=============
|
||||
|
||||
.. doxygengroup:: smf
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue