diff --git a/include/etl/fsm.h b/include/etl/fsm.h index 63beaf07..d12a301a 100644 --- a/include/etl/fsm.h +++ b/include/etl/fsm.h @@ -508,30 +508,7 @@ namespace etl { etl::fsm_state_id_t next_state_id = p_state->process_event(message); - if (have_changed_state(next_state_id)) - { - ETL_ASSERT_OR_RETURN(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); - etl::ifsm_state* p_next_state = state_list[next_state_id]; - - do - { - p_state->on_exit_state(); - p_state = p_next_state; - - next_state_id = p_state->on_enter_state(); - - if (have_changed_state(next_state_id)) - { - ETL_ASSERT_OR_RETURN(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); - p_next_state = state_list[next_state_id]; - } - } while (p_next_state != p_state); // Have we changed state again? - } - else if (is_self_transition(next_state_id)) - { - p_state->on_exit_state(); - p_state->on_enter_state(); - } + process_state_change(next_state_id); } else { @@ -539,6 +516,21 @@ namespace etl } } + //******************************************* + /// Invoke a state transition. + //******************************************* + etl::fsm_state_id_t transition_to(etl::fsm_state_id_t new_state_id) + { + if (is_started()) + { + return process_state_change(new_state_id); + } + else + { + return ifsm_state::No_State_Change; + } + } + using imessage_router::accepts; //******************************************* @@ -633,6 +625,39 @@ namespace etl return (next_state_id == ifsm_state::Self_Transition); } + //******************************************* + /// Core function to process a state change. + //******************************************* + virtual etl::fsm_state_id_t process_state_change(etl::fsm_state_id_t next_state_id) + { + if (have_changed_state(next_state_id)) + { + ETL_ASSERT_OR_RETURN(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + etl::ifsm_state* p_next_state = state_list[next_state_id]; + + do + { + p_state->on_exit_state(); + p_state = p_next_state; + + next_state_id = p_state->on_enter_state(); + + if (have_changed_state(next_state_id)) + { + ETL_ASSERT_OR_RETURN(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + p_next_state = state_list[next_state_id]; + } + } while (p_next_state != p_state); // Have we changed state again? + } + else if (is_self_transition(next_state_id)) + { + p_state->on_exit_state(); + p_state->on_enter_state(); + } + + return p_state->get_state_id(); + } + etl::ifsm_state* p_state; ///< A pointer to the current state. etl::ifsm_state** state_list; ///< The list of added states. etl::fsm_state_id_t number_of_states; ///< The number of states. diff --git a/include/etl/hfsm.h b/include/etl/hfsm.h index eab44f55..127ea9f4 100644 --- a/include/etl/hfsm.h +++ b/include/etl/hfsm.h @@ -91,42 +91,6 @@ namespace etl p_state = ETL_NULLPTR; } - using fsm::receive; - - //******************************************* - /// Top level message handler for the HFSM. - //******************************************* - void receive(const etl::imessage& message) ETL_OVERRIDE - { - if (is_started()) - { - etl::fsm_state_id_t next_state_id = p_state->process_event(message); - - if (have_changed_state(next_state_id)) - { - ETL_ASSERT_OR_RETURN(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); - - etl::ifsm_state* p_next_state = state_list[next_state_id]; - etl::ifsm_state* p_root = common_ancestor(p_state, p_next_state); - - do_exits(p_root, p_state); - next_state_id = do_enters(p_root, p_next_state, true); - - ETL_ASSERT_OR_RETURN(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); - p_state = state_list[next_state_id]; - } - else if (is_self_transition(next_state_id)) - { - p_state->on_exit_state(); - p_state->on_enter_state(); - } - } - else - { - ETL_ASSERT_FAIL(ETL_ERROR(etl::fsm_not_started)); - } - } - private: //******************************************* @@ -247,6 +211,33 @@ namespace etl p_current = p_current->p_parent; } } + + //******************************************* + /// Core function to process a state change. + //******************************************* + etl::fsm_state_id_t process_state_change(etl::fsm_state_id_t next_state_id) ETL_OVERRIDE + { + if (have_changed_state(next_state_id)) + { + ETL_ASSERT_OR_RETURN(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + + etl::ifsm_state* p_next_state = state_list[next_state_id]; + etl::ifsm_state* p_root = common_ancestor(p_state, p_next_state); + + do_exits(p_root, p_state); + next_state_id = do_enters(p_root, p_next_state, true); + + ETL_ASSERT_OR_RETURN(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + p_state = state_list[next_state_id]; + } + else if (is_self_transition(next_state_id)) + { + p_state->on_exit_state(); + p_state->on_enter_state(); + } + + return p_state->get_state_id(); + } }; } #endif diff --git a/test/test_fsm.cpp b/test/test_fsm.cpp index 331ef363..54745f3b 100644 --- a/test/test_fsm.cpp +++ b/test/test_fsm.cpp @@ -730,5 +730,55 @@ namespace CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id())); CHECK_EQUAL(StateId::Locked, int(motorControl.get_state().get_state_id())); } + + //************************************************************************* + TEST(test_fsm_force_state_changes) + { + MotorControl motorControl; + + etl::fsm_state_pack statePack; + + motorControl.Initialise(statePack); + motorControl.reset(); + motorControl.ClearStatistics(); + + // Start the FSM. + motorControl.start(false); + CHECK(motorControl.is_started()); + + // Now in Idle state. + CHECK_EQUAL(StateId::Idle, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Idle, int(motorControl.get_state().get_state_id())); + + auto id1 = motorControl.transition_to(StateId::Running); + + // Now in Running state. + CHECK_EQUAL(StateId::Running, int(id1)); + CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id())); + + auto id2 = motorControl.transition_to(StateId::Idle); + + // Now in Locked state. + CHECK_EQUAL(StateId::Locked, int(id2)); + CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Locked, int(motorControl.get_state().get_state_id())); + + auto id3 = motorControl.transition_to(StateId::Winding_Down); + + // Now in Locked state. + CHECK_EQUAL(StateId::Winding_Down, int(id3)); + CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state().get_state_id())); + + // Send a normal event message to make sure the FSM is still working. + + // Now send a Stopped event message. + motorControl.receive(Stopped()); + + // Now in Idle state. + CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Locked, int(motorControl.get_state().get_state_id())); + } }; } diff --git a/test/test_hfsm.cpp b/test/test_hfsm.cpp index adc794ac..30e422e7 100644 --- a/test/test_hfsm.cpp +++ b/test/test_hfsm.cpp @@ -192,6 +192,14 @@ namespace ClearStatistics(); } + //*********************************** + template + void Initialise(etl::fsm_state_pack& state_pack) + { + set_states(state_pack); + ClearStatistics(); + } + //*********************************** void ClearStatistics() { @@ -1028,5 +1036,56 @@ namespace CHECK_EQUAL(1, motorControl.selfTransitionCount); } + + //************************************************************************* + TEST(test_fsm_force_state_changes) + { + MotorControl motorControl; + + etl::fsm_state_pack statePack; + + motorControl.Initialise(statePack); + motorControl.reset(); + motorControl.ClearStatistics(); + + // Start the FSM. + motorControl.start(false); + CHECK(motorControl.is_started()); + + // Now in Idle state. + CHECK_EQUAL(StateId::Idle, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Idle, int(motorControl.get_state().get_state_id())); + + auto id1 = motorControl.transition_to(StateId::Winding_Up); + + // Now in Winding_Up state. + CHECK_EQUAL(StateId::Winding_Up, int(id1)); + CHECK_EQUAL(StateId::Winding_Up, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Winding_Up, int(motorControl.get_state().get_state_id())); + + auto id2 = motorControl.transition_to(StateId::At_Speed); + + // Now in At_Speed state. + CHECK_EQUAL(StateId::At_Speed, int(id2)); + CHECK_EQUAL(StateId::At_Speed, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::At_Speed, int(motorControl.get_state().get_state_id())); + + // Send some normal event messages to make sure the HFSM is still working. + + // Now send a Stop event message. + motorControl.receive(Stop()); + + CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state().get_state_id())); + + // Now send a Stopped event message. + motorControl.receive(Stopped()); + + // Now in Idle state. + CHECK_EQUAL(StateId::Idle, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Idle, int(motorControl.get_state().get_state_id())); + + + } }; }