diff --git a/doc/services/smf/index.rst b/doc/services/smf/index.rst index d5f1f918471900..38e68cfcf51c09 100644 --- a/doc/services/smf/index.rst +++ b/doc/services/smf/index.rst @@ -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,34 +78,62 @@ 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 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. + +.. 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: -``void smf_terminate(smf_ctx *ctx, int32_t val)`` +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)`` 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`. + - 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 ================================== @@ -466,3 +504,48 @@ 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 + } + + + +API Reference +************* + +.. doxygengroup:: smf diff --git a/include/zephyr/smf.h b/include/zephyr/smf.h index 2fdcbbdced6021..c7a2c4fbff4add 100644 --- a/include/zephyr/smf.h +++ b/include/zephyr/smf.h @@ -4,11 +4,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* State Machine Framework */ +/** + * @file + * + * @brief State Machine Framework header file + */ #ifndef ZEPHYR_INCLUDE_SMF_H_ #define ZEPHYR_INCLUDE_SMF_H_ +/** + * @brief State Machine Framework API + * @defgroup smf State Machine Framework API + * @ingroup os_services + * @{ + */ + #ifdef CONFIG_SMF_ANCESTOR_SUPPORT /** * @brief Macro to create a hierarchical state. @@ -18,6 +29,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 +37,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 +118,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 +174,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) * @@ -151,4 +198,8 @@ int32_t smf_run_state(struct smf_ctx *ctx); } #endif +/** + * @} + */ + #endif /* ZEPHYR_INCLUDE_SMF_H_ */ diff --git a/lib/smf/Kconfig b/lib/smf/Kconfig index 4204e9c8ed428c..19cd165637deb8 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 01fb42e6cfcc48..d869de47c6ee8c 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 147223b0288697..08844e75aa62b2 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 00000000000000..2e00dc2d553d90 --- /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_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"); +} diff --git a/tests/lib/smf/src/test_lib_smf.h b/tests/lib/smf/src/test_lib_smf.h index 998e3321ad115f..6296352fb73e8d 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_initial_transitions(void); #endif /* ZEPHYR_TEST_LIB_SMF_H_ */ diff --git a/tests/lib/smf/testcase.yaml b/tests/lib/smf/testcase.yaml index 761f86e8fff156..fff0bf0e974185 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