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:
Glenn Andrews 2023-12-21 08:12:10 -08:00 committed by Henrik Brix Andersen
parent 112bcb229c
commit 0569809c80
8 changed files with 687 additions and 21 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View 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");
}

View file

@ -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_ */

View file

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