From a7aa0db5ed11da9bf7bd709669c0ef73ea9f17aa Mon Sep 17 00:00:00 2001 From: Glenn Andrews Date: Thu, 21 Dec 2023 08:12:10 -0800 Subject: [PATCH] Lib: SMF: Add initial conditions 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. 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 RAM-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 --- doc/services/smf/index.rst | 32 +- include/zephyr/smf.h | 36 ++ lib/smf/Kconfig | 6 + lib/smf/smf.c | 40 ++ tests/lib/smf/CMakeLists.txt | 4 +- .../src/test_lib_initial_transitions_smf.c | 501 ++++++++++++++++++ tests/lib/smf/src/test_lib_smf.h | 1 + tests/lib/smf/testcase.yaml | 4 + 8 files changed, 622 insertions(+), 2 deletions(-) create mode 100644 tests/lib/smf/src/test_lib_initial_transitions_smf.c diff --git a/doc/services/smf/index.rst b/doc/services/smf/index.rst index d5f1f918471900c..17cf72731ba8199 100644 --- a/doc/services/smf/index.rst +++ b/doc/services/smf/index.rst @@ -39,6 +39,10 @@ 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 @@ -71,6 +75,21 @@ And this example creates three hierarchical states:: }; +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 ``smf_set_initial`` function should be called. It has the following prototype: ``void smf_set_initial(smf_ctx *ctx, smf_state *state)`` @@ -91,6 +110,13 @@ 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 ``smf_set_handled`` prevents calling the run action of parent states. +It is not required to call ``smf_set_handled`` if the state calls +``smf_set_state``. + State Machine Termination ========================= @@ -317,7 +343,11 @@ When designing hierarchical state machines, the following should be considered: 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. + without transitioning to another state, ie. calling smf_set_state, or if + ``smf_set_handled`` is called within the child_run function. + - 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 ================================== diff --git a/include/zephyr/smf.h b/include/zephyr/smf.h index 2fdcbbdced60210..b4aee26fa747578 100644 --- a/include/zephyr/smf.h +++ b/include/zephyr/smf.h @@ -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) * diff --git a/lib/smf/Kconfig b/lib/smf/Kconfig index 4204e9c8ed428c4..19cd165637deb86 100644 --- a/lib/smf/Kconfig +++ b/lib/smf/Kconfig @@ -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 diff --git a/lib/smf/smf.c b/lib/smf/smf.c index 01fb42e6cfcc48b..d869de47c6ee8c7 100644 --- a/lib/smf/smf.c +++ b/lib/smf/smf.c @@ -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; diff --git a/tests/lib/smf/CMakeLists.txt b/tests/lib/smf/CMakeLists.txt index 147223b02886970..08844e75aa62b2b 100644 --- a/tests/lib/smf/CMakeLists.txt +++ b/tests/lib/smf/CMakeLists.txt @@ -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() diff --git a/tests/lib/smf/src/test_lib_initial_transitions_smf.c b/tests/lib/smf/src/test_lib_initial_transitions_smf.c new file mode 100644 index 000000000000000..3d9563b2f93b011 --- /dev/null +++ b/tests/lib/smf/src/test_lib_initial_transitions_smf.c @@ -0,0 +1,501 @@ +/* + * Copyright 2024 Glenn Andrews + * based on test_lib_hierarchical_smf.c + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/* + * 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_inital_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[STATE_A]); + + 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[STATE_A]); + + 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[STATE_A]); + + 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[STATE_A]); + + 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[STATE_A]); + + 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[STATE_A]); + + 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"); +} diff --git a/tests/lib/smf/src/test_lib_smf.h b/tests/lib/smf/src/test_lib_smf.h index 998e3321ad115fc..36c3449dcd16d81 100644 --- a/tests/lib/smf/src/test_lib_smf.h +++ b/tests/lib/smf/src/test_lib_smf.h @@ -10,5 +10,6 @@ void test_smf_flat(void); void test_smf_hierarchical(void); void test_smf_hierarchical_5_ancestors(void); +void test_smf_inital_transitions(void); #endif /* ZEPHYR_TEST_LIB_SMF_H_ */ diff --git a/tests/lib/smf/testcase.yaml b/tests/lib/smf/testcase.yaml index 761f86e8fff1564..fff0bf0e9741853 100644 --- a/tests/lib/smf/testcase.yaml +++ b/tests/lib/smf/testcase.yaml @@ -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