Lib: SMF: Add initial transition and smf_set_handled()
Brings SMF framework closer into alignment with accepted Hierarchical State Machine operation by: 1. Allowing 'programming by difference' by having some child states handle events and prevent propagation up to the parent run actions while others propagate events up to a common handler in a parent state. 2. Optionally allow initial transitions within a parent state to determine the most nested child state to transition to. 3. Adding a test case for `CONFIG_SMF_INITIAL_TRANSITION` and `smf_set_handled()` 4. Updating documentation for the new API (and fixing some references) There was discussion in https://github.com/zephyrproject-rtos/zephyr/issues/55344 about not making the initial transition a Kconfig option, but I'm not sure of any way else of doing it without permanently adding a pointer to each `smf_state` entry, which is a problem for resource-constrained devices. This does not fix https://github.com/zephyrproject-rtos/zephyr/issues/66341 but documentation has been updated to warn users of the issue. Signed-off-by: Glenn Andrews <glenn.andrews.42@gmail.com>
This commit is contained in:
parent
112bcb229c
commit
0569809c80
|
@ -20,7 +20,7 @@ A state is represented by three functions, where one function implements the
|
|||
Entry actions, another function implements the Run actions, and the last
|
||||
function implements the Exit actions. The prototype for these functions is as
|
||||
follows: ``void funct(void *obj)``, where the ``obj`` parameter is a user
|
||||
defined structure that has the state machine context, ``struct smf_ctx``, as
|
||||
defined structure that has the state machine context, :c:struct:`smf_ctx`, as
|
||||
its first member. For example::
|
||||
|
||||
struct user_object {
|
||||
|
@ -28,9 +28,9 @@ its first member. For example::
|
|||
/* All User Defined Data Follows */
|
||||
};
|
||||
|
||||
The ``struct smf_ctx`` member must be first because the state machine
|
||||
framework's functions casts the user defined object to the ``struct smf_ctx``
|
||||
type with the following macro: ``SMF_CTX(o)``
|
||||
The :c:struct:`smf_ctx` member must be first because the state machine
|
||||
framework's functions casts the user defined object to the :c:struct:`smf_ctx`
|
||||
type with the :c:macro:`SMF_CTX` macro.
|
||||
|
||||
For example instead of doing this ``(struct smf_ctx *)&user_obj``, you could
|
||||
use ``SMF_CTX(&user_obj)``.
|
||||
|
@ -39,12 +39,19 @@ 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
|
||||
to child states on entering a superstate. To enable them the
|
||||
:kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION` option must be enabled.
|
||||
|
||||
The following macro can be used for easy state creation:
|
||||
|
||||
* :c:macro:`SMF_CREATE_STATE` Create a state
|
||||
|
||||
**NOTE:** The :c:macro:`SMF_CREATE_STATE` macro takes an additional parameter
|
||||
when :kconfig:option:`CONFIG_SMF_ANCESTOR_SUPPORT` is enabled.
|
||||
.. note:: The :c:macro:`SMF_CREATE_STATE` macro takes an additional parameter
|
||||
for the parent state when :kconfig:option:`CONFIG_SMF_ANCESTOR_SUPPORT` is
|
||||
enabled . The :c:macro:`SMF_CREATE_STATE` macro takes two additional
|
||||
parameters for the parent state and initial transition when the
|
||||
:kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION` option is enabled.
|
||||
|
||||
State Machine Creation
|
||||
======================
|
||||
|
@ -71,33 +78,61 @@ And this example creates three hierarchical states::
|
|||
};
|
||||
|
||||
|
||||
To set the initial state, the ``smf_set_initial`` function should be
|
||||
This example creates three hierarchical states with an initial transition
|
||||
from parent state S0 to child state S2::
|
||||
|
||||
enum demo_state { S0, S1, S2 };
|
||||
|
||||
/* Forward declaration of state table */
|
||||
const struct smf_state demo_states[];
|
||||
|
||||
const struct smf_state demo_states[] = {
|
||||
[S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit, NULL, demo_states[S2]),
|
||||
[S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit, demo_states[S0], NULL),
|
||||
[S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit, demo_states[S0], NULL)
|
||||
};
|
||||
|
||||
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)``
|
||||
|
||||
To transition from one state to another, the ``smf_set_state`` function is
|
||||
used and it has the following prototype:
|
||||
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)``
|
||||
|
||||
**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:: 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 they will 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.
|
||||
|
||||
.. 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.
|
||||
|
||||
State Machine Execution
|
||||
=======================
|
||||
|
||||
To run the state machine, the ``smf_run_state`` function should be called in
|
||||
some application dependent way. An application should cease calling
|
||||
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)``
|
||||
|
||||
Preventing Parent Run Actions
|
||||
=============================
|
||||
|
||||
Calling :c:func:`smf_set_handled` prevents calling the run action of parent
|
||||
states. It is not required to call :c:func:`smf_set_handled` if the state
|
||||
calls :c:func:`smf_set_state`.
|
||||
|
||||
State Machine Termination
|
||||
=========================
|
||||
|
||||
To terminate the state machine, the ``smf_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 ``smf_run_state``
|
||||
function. The function has the following prototype:
|
||||
To terminate the state machine, the :c:func:`smf_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_terminate(smf_ctx *ctx, int32_t val)``
|
||||
|
||||
Flat State Machine Example
|
||||
|
@ -316,8 +351,11 @@ When designing hierarchical state machines, the following should be considered:
|
|||
- 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.
|
||||
- The parent_run function only executes if the child_run function returns
|
||||
without transitioning to another state, ie. calling smf_set_state.
|
||||
- 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`.
|
||||
- When a parent state intitiates a transition to self, the parents's exit
|
||||
action is not called, e.g. instead of child_exit, parent_exit, parent_entry
|
||||
it performs child_exit, parent_entry
|
||||
|
||||
Event Driven State Machine Example
|
||||
==================================
|
||||
|
@ -466,3 +504,41 @@ Code::
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Hierarchical State Machine Example With Initial Transitions
|
||||
===========================================================
|
||||
|
||||
: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 trnasitions and ``smf_set_handled``
|
||||
|
||||
digraph smf_hierarchical_initial {
|
||||
compound=true;
|
||||
node [style = rounded];
|
||||
smf_set_initial [shape=plaintext];
|
||||
ab_init_state [shape = point];
|
||||
STATE_A [shape = box];
|
||||
STATE_B [shape = box];
|
||||
STATE_C [shape = box];
|
||||
STATE_D [shape = box];
|
||||
|
||||
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_C -> STATE_C
|
||||
}
|
||||
|
||||
smf_set_initial -> STATE_A [lhead=cluster_ab]
|
||||
STATE_B -> STATE_C
|
||||
STATE_C -> STATE_D
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
* @param _exit State exit function
|
||||
* @param _parent State parent object or NULL
|
||||
*/
|
||||
#ifndef CONFIG_SMF_INITIAL_TRANSITION
|
||||
#define SMF_CREATE_STATE(_entry, _run, _exit, _parent) \
|
||||
{ \
|
||||
.entry = _entry, \
|
||||
|
@ -25,6 +26,25 @@
|
|||
.exit = _exit, \
|
||||
.parent = _parent \
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* @brief Macro to create a hierarchical state.
|
||||
*
|
||||
* @param _entry State entry function
|
||||
* @param _run State run function
|
||||
* @param _exit State exit function
|
||||
* @param _parent State parent object or NULL
|
||||
* @param _initial State initial transition object or NULL
|
||||
*/
|
||||
#define SMF_CREATE_STATE(_entry, _run, _exit, _parent, _initial) \
|
||||
{ \
|
||||
.entry = _entry, \
|
||||
.run = _run, \
|
||||
.exit = _exit, \
|
||||
.parent = _parent, \
|
||||
.initial = _initial \
|
||||
}
|
||||
#endif /* CONFIG_SMF_INITIAL_TRANSITION */
|
||||
|
||||
#else
|
||||
|
||||
|
@ -87,6 +107,13 @@ struct smf_state {
|
|||
* that parent's exit and entry functions do not execute.
|
||||
*/
|
||||
const struct smf_state *parent;
|
||||
|
||||
#ifdef CONFIG_SMF_INITIAL_TRANSITION
|
||||
/**
|
||||
* Optional initial transition state. NULL for leaf states.
|
||||
*/
|
||||
const struct smf_state *initial;
|
||||
#endif
|
||||
};
|
||||
|
||||
/** Defines the current context of the state machine. */
|
||||
|
@ -136,6 +163,15 @@ void smf_set_state(struct smf_ctx *ctx, const struct smf_state *new_state);
|
|||
*/
|
||||
void smf_set_terminate(struct smf_ctx *ctx, int32_t val);
|
||||
|
||||
/**
|
||||
* @brief Tell the SMF to stop propagating the event to ancestors. This allows
|
||||
* HSMs to implement 'programming by difference' where substates can
|
||||
* handle events on their own or propagate up to a common handler.
|
||||
*
|
||||
* @param ctx State machine context
|
||||
*/
|
||||
void smf_set_handled(struct smf_ctx *ctx);
|
||||
|
||||
/**
|
||||
* @brief Runs one iteration of a state machine (including any parent states)
|
||||
*
|
||||
|
|
|
@ -13,4 +13,10 @@ config SMF_ANCESTOR_SUPPORT
|
|||
help
|
||||
If y, then the state machine framework includes ancestor state support
|
||||
|
||||
config SMF_INITIAL_TRANSITION
|
||||
depends on SMF_ANCESTOR_SUPPORT
|
||||
bool "Support initial transitions for ancestor states"
|
||||
help
|
||||
If y, then each state can have an initial transition to a sub-state
|
||||
|
||||
endif # SMF
|
||||
|
|
|
@ -18,6 +18,7 @@ struct internal_ctx {
|
|||
bool new_state : 1;
|
||||
bool terminate : 1;
|
||||
bool exit : 1;
|
||||
bool handled : 1;
|
||||
};
|
||||
|
||||
static bool share_paren(const struct smf_state *test_state,
|
||||
|
@ -118,6 +119,12 @@ __unused static bool smf_execute_ancestor_run_actions(struct smf_ctx *ctx)
|
|||
return true;
|
||||
}
|
||||
|
||||
if (internal->handled) {
|
||||
/* Event was handled by this state. Stop propagating */
|
||||
internal->handled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Try to run parent run actions */
|
||||
for (const struct smf_state *tmp_state = ctx->current->parent;
|
||||
tmp_state != NULL;
|
||||
|
@ -133,6 +140,12 @@ __unused static bool smf_execute_ancestor_run_actions(struct smf_ctx *ctx)
|
|||
if (internal->new_state) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (internal->handled) {
|
||||
/* Event was handled by this state. Stop propagating */
|
||||
internal->handled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,6 +188,16 @@ void smf_set_initial(struct smf_ctx *ctx, const struct smf_state *init_state)
|
|||
{
|
||||
struct internal_ctx * const internal = (void *) &ctx->internal;
|
||||
|
||||
|
||||
#ifdef CONFIG_SMF_INITIAL_TRANSITION
|
||||
/*
|
||||
* The final target will be the deepest leaf state that
|
||||
* the target contains. Set that as the real target.
|
||||
*/
|
||||
while (init_state->initial) {
|
||||
init_state = init_state->initial;
|
||||
}
|
||||
#endif
|
||||
internal->exit = false;
|
||||
internal->terminate = false;
|
||||
ctx->current = init_state;
|
||||
|
@ -234,6 +257,16 @@ void smf_set_state(struct smf_ctx *const ctx, const struct smf_state *target)
|
|||
|
||||
internal->exit = false;
|
||||
|
||||
#ifdef CONFIG_SMF_INITIAL_TRANSITION
|
||||
/*
|
||||
* The final target will be the deepest leaf state that
|
||||
* the target contains. Set that as the real target.
|
||||
*/
|
||||
while (target->initial) {
|
||||
target = target->initial;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* update the state variables */
|
||||
ctx->previous = ctx->current;
|
||||
ctx->current = target;
|
||||
|
@ -262,6 +295,13 @@ void smf_set_terminate(struct smf_ctx *ctx, int32_t val)
|
|||
ctx->terminate_val = val;
|
||||
}
|
||||
|
||||
void smf_set_handled(struct smf_ctx *ctx)
|
||||
{
|
||||
struct internal_ctx *const internal = (void *)&ctx->internal;
|
||||
|
||||
internal->handled = true;
|
||||
}
|
||||
|
||||
int32_t smf_run_state(struct smf_ctx *const ctx)
|
||||
{
|
||||
struct internal_ctx * const internal = (void *) &ctx->internal;
|
||||
|
|
|
@ -6,7 +6,9 @@ project(smf)
|
|||
|
||||
target_sources(app PRIVATE src/main.c)
|
||||
|
||||
if(CONFIG_SMF_ANCESTOR_SUPPORT)
|
||||
if(CONFIG_SMF_INITIAL_TRANSITION)
|
||||
target_sources(app PRIVATE src/test_lib_initial_transitions_smf.c)
|
||||
elseif(CONFIG_SMF_ANCESTOR_SUPPORT)
|
||||
target_sources(app PRIVATE src/test_lib_hierarchical_smf.c
|
||||
src/test_lib_hierarchical_5_ancestor_smf.c)
|
||||
else()
|
||||
|
|
501
tests/lib/smf/src/test_lib_initial_transitions_smf.c
Normal file
501
tests/lib/smf/src/test_lib_initial_transitions_smf.c
Normal file
|
@ -0,0 +1,501 @@
|
|||
/*
|
||||
* Copyright 2024 Glenn Andrews
|
||||
* based on test_lib_hierarchical_smf.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/ztest.h>
|
||||
#include <zephyr/smf.h>
|
||||
|
||||
/*
|
||||
* Hierarchical Test Transition:
|
||||
*
|
||||
* PARENT_AB_ENTRY --> A_ENTRY --> A_RUN --> PARENT_AB_RUN ---|
|
||||
* |
|
||||
* |----------------------------------------------------------|
|
||||
* |
|
||||
* |--> A_EXIT --> B_ENTRY --> B_RUN --> B_EXIT --------------|
|
||||
* |
|
||||
* |----------------------------------------------------------|
|
||||
* |
|
||||
* |--> PARENT_AB_EXIT --> PARENT_C_ENTRY --> C_ENTRY --------|
|
||||
* |
|
||||
* |----------------------------------------------------------|
|
||||
* |
|
||||
* |--> C_RUN(#1) --> C_RUN(#2) --> C_EXIT --> PARENT_C_RUN --|
|
||||
* |
|
||||
* |----------------------------------------------------------|
|
||||
* |
|
||||
* |--> PARENT_C_EXIT
|
||||
*/
|
||||
|
||||
#define TEST_OBJECT(o) ((struct test_object *)o)
|
||||
|
||||
#define SMF_RUN 4
|
||||
|
||||
/* Initial Setup */
|
||||
#define PARENT_AB_ENTRY_BIT (1 << 0)
|
||||
#define STATE_A_ENTRY_BIT (1 << 1)
|
||||
|
||||
/* Run 0 */
|
||||
#define STATE_A_RUN_BIT (1 << 2)
|
||||
#define PARENT_AB_RUN_BIT (1 << 3)
|
||||
#define STATE_A_EXIT_BIT (1 << 4)
|
||||
#define STATE_B_ENTRY_BIT (1 << 5)
|
||||
|
||||
/* Run 1 */
|
||||
#define STATE_B_RUN_BIT (1 << 6)
|
||||
#define STATE_B_EXIT_BIT (1 << 7)
|
||||
#define PARENT_AB_EXIT_BIT (1 << 8)
|
||||
#define PARENT_C_ENTRY_BIT (1 << 9)
|
||||
#define STATE_C_ENTRY_BIT (1 << 10)
|
||||
|
||||
/* Run 2 */
|
||||
#define STATE_C_1ST_RUN_BIT (1 << 11)
|
||||
|
||||
/* Run 3 */
|
||||
#define STATE_C_2ND_RUN_BIT (1 << 12)
|
||||
#define PARENT_C_RUN_BIT (1 << 13)
|
||||
#define STATE_C_EXIT_BIT (1 << 14)
|
||||
#define PARENT_C_EXIT_BIT (1 << 15)
|
||||
|
||||
#define TEST_PARENT_ENTRY_VALUE_NUM 0
|
||||
#define TEST_PARENT_RUN_VALUE_NUM 3
|
||||
#define TEST_PARENT_EXIT_VALUE_NUM 8
|
||||
#define TEST_ENTRY_VALUE_NUM 1
|
||||
#define TEST_RUN_VALUE_NUM 6
|
||||
#define TEST_EXIT_VALUE_NUM 14
|
||||
#define TEST_VALUE_NUM 16
|
||||
static uint32_t test_value[] = {
|
||||
0x00, /* PARENT_AB_ENTRY */
|
||||
0x01, /* STATE_A_ENTRY */
|
||||
0x03, /* STATE_A_RUN */
|
||||
0x07, /* PARENT_AB_RUN */
|
||||
0x0f, /* STATE_A_EXIT */
|
||||
0x1f, /* STATE_B_ENTRY */
|
||||
0x3f, /* STATE_B_RUN */
|
||||
0x7f, /* STATE_B_EXIT */
|
||||
0xff, /* PARENT_AB_EXIT */
|
||||
0x1ff, /* PARENT_C_ENTRY */
|
||||
0x3ff, /* STATE_C_ENTRY */
|
||||
0x7ff, /* STATE_C_1ST_RUN */
|
||||
0xfff, /* STATE_C_2ND_RUN */
|
||||
0x1fff, /* STATE_C_EXIT */
|
||||
0x3fff, /* PARENT_C_RUN */
|
||||
0x7fff, /* PARENT_C_EXIT */
|
||||
0xffff, /* FINAL VALUE */
|
||||
};
|
||||
|
||||
/* Forward declaration of test_states */
|
||||
static const struct smf_state test_states[];
|
||||
|
||||
/* List of all TypeC-level states */
|
||||
enum test_state {
|
||||
PARENT_AB,
|
||||
PARENT_C,
|
||||
STATE_A,
|
||||
STATE_B,
|
||||
STATE_C,
|
||||
STATE_D
|
||||
};
|
||||
|
||||
enum terminate_action {
|
||||
NONE,
|
||||
PARENT_ENTRY,
|
||||
PARENT_RUN,
|
||||
PARENT_EXIT,
|
||||
ENTRY,
|
||||
RUN,
|
||||
EXIT
|
||||
};
|
||||
|
||||
static struct test_object {
|
||||
struct smf_ctx ctx;
|
||||
uint32_t transition_bits;
|
||||
uint32_t tv_idx;
|
||||
enum terminate_action terminate;
|
||||
uint32_t first_time;
|
||||
} test_obj;
|
||||
|
||||
static void parent_ab_entry(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx = 0;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test Parent AB entry failed");
|
||||
|
||||
if (o->terminate == PARENT_ENTRY) {
|
||||
smf_set_terminate(obj, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
o->transition_bits |= PARENT_AB_ENTRY_BIT;
|
||||
}
|
||||
|
||||
static void parent_ab_run(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test Parent AB run failed");
|
||||
|
||||
if (o->terminate == PARENT_RUN) {
|
||||
smf_set_terminate(obj, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
o->transition_bits |= PARENT_AB_RUN_BIT;
|
||||
|
||||
smf_set_state(SMF_CTX(obj), &test_states[STATE_B]);
|
||||
}
|
||||
|
||||
static void parent_ab_exit(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test Parent AB exit failed");
|
||||
|
||||
if (o->terminate == PARENT_EXIT) {
|
||||
smf_set_terminate(obj, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
o->transition_bits |= PARENT_AB_EXIT_BIT;
|
||||
}
|
||||
|
||||
static void parent_c_entry(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test Parent C entry failed");
|
||||
o->transition_bits |= PARENT_C_ENTRY_BIT;
|
||||
}
|
||||
|
||||
static void parent_c_run(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
if (o->first_time) {
|
||||
/* This state should not be reached */
|
||||
zassert_true(0, "Test Parent C run failed");
|
||||
} else {
|
||||
o->transition_bits |= PARENT_C_RUN_BIT;
|
||||
|
||||
smf_set_state(SMF_CTX(obj), &test_states[STATE_D]);
|
||||
}
|
||||
}
|
||||
|
||||
static void parent_c_exit(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test Parent C exit failed");
|
||||
o->transition_bits |= PARENT_C_EXIT_BIT;
|
||||
}
|
||||
|
||||
static void state_a_entry(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test State A entry failed");
|
||||
|
||||
if (o->terminate == ENTRY) {
|
||||
smf_set_terminate(obj, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
o->transition_bits |= STATE_A_ENTRY_BIT;
|
||||
}
|
||||
|
||||
static void state_a_run(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test State A run failed");
|
||||
|
||||
o->transition_bits |= STATE_A_RUN_BIT;
|
||||
|
||||
/* Return to parent run state */
|
||||
}
|
||||
|
||||
static void state_a_exit(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test State A exit failed");
|
||||
o->transition_bits |= STATE_A_EXIT_BIT;
|
||||
}
|
||||
|
||||
static void state_b_entry(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test State B entry failed");
|
||||
o->transition_bits |= STATE_B_ENTRY_BIT;
|
||||
}
|
||||
|
||||
static void state_b_run(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test State B run failed");
|
||||
|
||||
if (o->terminate == RUN) {
|
||||
smf_set_terminate(obj, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
o->transition_bits |= STATE_B_RUN_BIT;
|
||||
|
||||
smf_set_state(SMF_CTX(obj), &test_states[STATE_C]);
|
||||
}
|
||||
|
||||
static void state_b_exit(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test State B exit failed");
|
||||
o->transition_bits |= STATE_B_EXIT_BIT;
|
||||
}
|
||||
|
||||
static void state_c_entry(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test State C entry failed");
|
||||
o->transition_bits |= STATE_C_ENTRY_BIT;
|
||||
}
|
||||
|
||||
static void state_c_run(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test State C run failed");
|
||||
|
||||
if (o->first_time) {
|
||||
o->first_time = false;
|
||||
o->transition_bits |= STATE_C_1ST_RUN_BIT;
|
||||
smf_set_handled(SMF_CTX(obj));
|
||||
} else {
|
||||
/* Do nothing, Let parent handle it */
|
||||
o->transition_bits |= STATE_C_2ND_RUN_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
static void state_c_exit(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
|
||||
zassert_equal(o->transition_bits, test_value[o->tv_idx],
|
||||
"Test State C exit failed");
|
||||
|
||||
if (o->terminate == EXIT) {
|
||||
smf_set_terminate(obj, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
o->transition_bits |= STATE_C_EXIT_BIT;
|
||||
}
|
||||
|
||||
static void state_d_entry(void *obj)
|
||||
{
|
||||
struct test_object *o = TEST_OBJECT(obj);
|
||||
|
||||
o->tv_idx++;
|
||||
}
|
||||
|
||||
static void state_d_run(void *obj)
|
||||
{
|
||||
/* Do nothing */
|
||||
}
|
||||
|
||||
static void state_d_exit(void *obj)
|
||||
{
|
||||
/* Do nothing */
|
||||
}
|
||||
|
||||
static const struct smf_state test_states[] = {
|
||||
[PARENT_AB] = SMF_CREATE_STATE(parent_ab_entry, parent_ab_run,
|
||||
parent_ab_exit, NULL, &test_states[STATE_A]),
|
||||
[PARENT_C] = SMF_CREATE_STATE(parent_c_entry, parent_c_run,
|
||||
parent_c_exit, NULL, &test_states[STATE_C]),
|
||||
[STATE_A] = SMF_CREATE_STATE(state_a_entry, state_a_run, state_a_exit,
|
||||
&test_states[PARENT_AB], NULL),
|
||||
[STATE_B] = SMF_CREATE_STATE(state_b_entry, state_b_run, state_b_exit,
|
||||
&test_states[PARENT_AB], NULL),
|
||||
[STATE_C] = SMF_CREATE_STATE(state_c_entry, state_c_run, state_c_exit,
|
||||
&test_states[PARENT_C], NULL),
|
||||
[STATE_D] = SMF_CREATE_STATE(state_d_entry, state_d_run, state_d_exit,
|
||||
NULL, NULL),
|
||||
};
|
||||
|
||||
ZTEST(smf_tests, test_smf_initial_transitions)
|
||||
{
|
||||
/* A) Test state transitions */
|
||||
|
||||
test_obj.transition_bits = 0;
|
||||
test_obj.first_time = 1;
|
||||
test_obj.terminate = NONE;
|
||||
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
|
||||
|
||||
for (int i = 0; i < SMF_RUN; i++) {
|
||||
if (smf_run_state((struct smf_ctx *)&test_obj) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
zassert_equal(TEST_VALUE_NUM, test_obj.tv_idx,
|
||||
"Incorrect test value index");
|
||||
zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx],
|
||||
"Final state not reached");
|
||||
|
||||
/* B) Test termination in parent entry action */
|
||||
|
||||
test_obj.transition_bits = 0;
|
||||
test_obj.first_time = 1;
|
||||
test_obj.terminate = PARENT_ENTRY;
|
||||
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
|
||||
|
||||
for (int i = 0; i < SMF_RUN; i++) {
|
||||
if (smf_run_state((struct smf_ctx *)&test_obj) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
zassert_equal(TEST_PARENT_ENTRY_VALUE_NUM, test_obj.tv_idx,
|
||||
"Incorrect test value index for parent entry termination");
|
||||
zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx],
|
||||
"Final parent entry termination state not reached");
|
||||
|
||||
/* C) Test termination in parent run action */
|
||||
|
||||
test_obj.transition_bits = 0;
|
||||
test_obj.first_time = 1;
|
||||
test_obj.terminate = PARENT_RUN;
|
||||
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
|
||||
|
||||
for (int i = 0; i < SMF_RUN; i++) {
|
||||
if (smf_run_state((struct smf_ctx *)&test_obj) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
zassert_equal(TEST_PARENT_RUN_VALUE_NUM, test_obj.tv_idx,
|
||||
"Incorrect test value index for parent run termination");
|
||||
zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx],
|
||||
"Final parent run termination state not reached");
|
||||
|
||||
/* D) Test termination in parent exit action */
|
||||
|
||||
test_obj.transition_bits = 0;
|
||||
test_obj.first_time = 1;
|
||||
test_obj.terminate = PARENT_EXIT;
|
||||
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
|
||||
|
||||
for (int i = 0; i < SMF_RUN; i++) {
|
||||
if (smf_run_state((struct smf_ctx *)&test_obj) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
zassert_equal(TEST_PARENT_EXIT_VALUE_NUM, test_obj.tv_idx,
|
||||
"Incorrect test value index for parent exit termination");
|
||||
zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx],
|
||||
"Final parent exit termination state not reached");
|
||||
|
||||
/* E) Test termination in child entry action */
|
||||
|
||||
test_obj.transition_bits = 0;
|
||||
test_obj.first_time = 1;
|
||||
test_obj.terminate = ENTRY;
|
||||
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
|
||||
|
||||
for (int i = 0; i < SMF_RUN; i++) {
|
||||
if (smf_run_state((struct smf_ctx *)&test_obj) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
zassert_equal(TEST_ENTRY_VALUE_NUM, test_obj.tv_idx,
|
||||
"Incorrect test value index for entry termination");
|
||||
zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx],
|
||||
"Final entry termination state not reached");
|
||||
|
||||
/* F) Test termination in child run action */
|
||||
|
||||
test_obj.transition_bits = 0;
|
||||
test_obj.first_time = 1;
|
||||
test_obj.terminate = RUN;
|
||||
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
|
||||
|
||||
for (int i = 0; i < SMF_RUN; i++) {
|
||||
if (smf_run_state((struct smf_ctx *)&test_obj) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
zassert_equal(TEST_RUN_VALUE_NUM, test_obj.tv_idx,
|
||||
"Incorrect test value index for run termination");
|
||||
zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx],
|
||||
"Final run termination state not reached");
|
||||
|
||||
/* G) Test termination in child exit action */
|
||||
|
||||
test_obj.transition_bits = 0;
|
||||
test_obj.first_time = 1;
|
||||
test_obj.terminate = EXIT;
|
||||
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
|
||||
|
||||
for (int i = 0; i < SMF_RUN; i++) {
|
||||
if (smf_run_state((struct smf_ctx *)&test_obj) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
zassert_equal(TEST_EXIT_VALUE_NUM, test_obj.tv_idx,
|
||||
"Incorrect test value index for exit termination");
|
||||
zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx],
|
||||
"Final exit termination state not reached");
|
||||
}
|
|
@ -10,5 +10,6 @@
|
|||
void test_smf_flat(void);
|
||||
void test_smf_hierarchical(void);
|
||||
void test_smf_hierarchical_5_ancestors(void);
|
||||
void test_smf_initial_transitions(void);
|
||||
|
||||
#endif /* ZEPHYR_TEST_LIB_SMF_H_ */
|
||||
|
|
|
@ -8,3 +8,7 @@ tests:
|
|||
libraries.smf.hierarchical:
|
||||
extra_configs:
|
||||
- CONFIG_SMF_ANCESTOR_SUPPORT=y
|
||||
libraries.smf.initial_transition:
|
||||
extra_configs:
|
||||
- CONFIG_SMF_ANCESTOR_SUPPORT=y
|
||||
- CONFIG_SMF_INITIAL_TRANSITION=y
|
||||
|
|
Loading…
Reference in a new issue