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:
Glenn Andrews 2024-04-20 20:43:43 -07:00 committed by Anas Nashif
commit 531c457550
8 changed files with 501 additions and 296 deletions

View file

@ -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
====

View file

@ -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

View file

@ -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