From 055ce0d442a2869dbf54dbe6be9c1f4c2fed563e Mon Sep 17 00:00:00 2001 From: David Ockey Date: Thu, 16 Feb 2023 02:53:17 -0600 Subject: [PATCH] Added HFSM-specific start() and reset() (#672) * Added HFSM specific Start and Reset * Added unit tests for HFSM start() and reset() * Updated FSM generator --- include/etl/fsm.h | 4 +- include/etl/generators/fsm_generator.h | 4 +- include/etl/hfsm.h | 35 +++++++ test/test_hfsm.cpp | 132 ++++++++++++++++++++++++- 4 files changed, 168 insertions(+), 7 deletions(-) diff --git a/include/etl/fsm.h b/include/etl/fsm.h index b7f2f915..5c71795d 100644 --- a/include/etl/fsm.h +++ b/include/etl/fsm.h @@ -346,7 +346,7 @@ namespace etl /// Subsequent calls will do nothing. ///\param call_on_enter_state If true will call on_enter_state() for the first state. Default = true. //******************************************* - void start(bool call_on_enter_state = true) + virtual void start(bool call_on_enter_state = true) { // Can only be started once. if (p_state == ETL_NULLPTR) @@ -451,7 +451,7 @@ namespace etl /// Reset the FSM to pre-started state. ///\param call_on_exit_state If true will call on_exit_state() for the current state. Default = false. //******************************************* - void reset(bool call_on_exit_state = false) + virtual void reset(bool call_on_exit_state = false) { if ((p_state != ETL_NULLPTR) && call_on_exit_state) { diff --git a/include/etl/generators/fsm_generator.h b/include/etl/generators/fsm_generator.h index a510de99..c30c8b9c 100644 --- a/include/etl/generators/fsm_generator.h +++ b/include/etl/generators/fsm_generator.h @@ -372,7 +372,7 @@ namespace etl /// Subsequent calls will do nothing. ///\param call_on_enter_state If true will call on_enter_state() for the first state. Default = true. //******************************************* - void start(bool call_on_enter_state = true) + virtual void start(bool call_on_enter_state = true) { // Can only be started once. if (p_state == ETL_NULLPTR) @@ -477,7 +477,7 @@ namespace etl /// Reset the FSM to pre-started state. ///\param call_on_exit_state If true will call on_exit_state() for the current state. Default = false. //******************************************* - void reset(bool call_on_exit_state = false) + virtual void reset(bool call_on_exit_state = false) { if ((p_state != ETL_NULLPTR) && call_on_exit_state) { diff --git a/include/etl/hfsm.h b/include/etl/hfsm.h index 673e2cec..73eee88b 100644 --- a/include/etl/hfsm.h +++ b/include/etl/hfsm.h @@ -50,6 +50,41 @@ namespace etl { } + //******************************************* + /// Starts the HFSM. + /// Can only be called once. + /// Subsequent calls will do nothing. + ///\param call_on_enter_state If true will call on_enter_state() for the first state. Default = true. + //******************************************* + void start(bool call_on_enter_state = true) ETL_OVERRIDE + { + // Can only be started once. + if (p_state == ETL_NULLPTR) + { + p_state = state_list[0]; + ETL_ASSERT(p_state != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception)); + + if (call_on_enter_state) + { + do_enters(ETL_NULLPTR, p_state, true); + } + } + } + + //******************************************* + /// Reset the HFSM to pre-started state. + ///\param call_on_exit_state If true will call on_exit_state() for the current state. Default = false. + //******************************************* + virtual void reset(bool call_on_exit_state = false) ETL_OVERRIDE + { + if ((p_state != ETL_NULLPTR) && call_on_exit_state) + { + do_exits(ETL_NULLPTR, p_state); + } + + p_state = ETL_NULLPTR; + } + using fsm::receive; //******************************************* diff --git a/test/test_hfsm.cpp b/test/test_hfsm.cpp index 375d1e98..dc1e3459 100644 --- a/test/test_hfsm.cpp +++ b/test/test_hfsm.cpp @@ -33,6 +33,7 @@ SOFTWARE. #include "etl/container.h" #include "etl/packet.h" #include "etl/queue.h" +#include "etl/circular_buffer.h" #include @@ -196,6 +197,9 @@ namespace stoppedCount = 0; isLampOn = false; speed = 0; + + stateEnterHistory.clear(); + stateExitHistory.clear(); } //*********************************** @@ -238,6 +242,10 @@ namespace int stoppedCount; bool isLampOn; int speed; + + // A circular buffer is used so that data overflows won't assert in tests which don't use this data + etl::circular_buffer stateEnterHistory{}; + etl::circular_buffer stateExitHistory{}; }; //*********************************** @@ -271,9 +279,19 @@ namespace //*********************************** etl::fsm_state_id_t on_enter_state() { - get_fsm_context().TurnRunningLampOff(); + auto& context = get_fsm_context(); + context.stateEnterHistory.push(static_cast(get_state_id())); + context.TurnRunningLampOff(); + return No_State_Change; } + + //*********************************** + void on_exit_state() + { + auto& context = get_fsm_context(); + context.stateExitHistory.push(static_cast(get_state_id())); + } }; //*********************************** @@ -301,10 +319,19 @@ namespace //*********************************** etl::fsm_state_id_t on_enter_state() { - get_fsm_context().TurnRunningLampOn(); + auto& context = get_fsm_context(); + context.stateEnterHistory.push(static_cast(get_state_id())); + context.TurnRunningLampOn(); return No_State_Change; } + + //*********************************** + void on_exit_state() + { + auto& context = get_fsm_context(); + context.stateExitHistory.push(static_cast(get_state_id())); + } }; //*********************************** @@ -336,11 +363,21 @@ namespace return No_State_Change; } + //*********************************** etl::fsm_state_id_t on_enter_state() { - ++get_fsm_context().windUpStartCount; + auto& context = get_fsm_context(); + context.stateEnterHistory.push(static_cast(get_state_id())); + ++context.windUpStartCount; return No_State_Change; } + + //*********************************** + void on_exit_state() + { + auto& context = get_fsm_context(); + context.stateExitHistory.push(static_cast(get_state_id())); + } }; //*********************************** @@ -371,6 +408,13 @@ namespace ++get_fsm_context().unknownCount; return No_State_Change; } + + //*********************************** + void on_exit_state() + { + auto& context = get_fsm_context(); + context.stateExitHistory.push(static_cast(get_state_id())); + } }; //*********************************** @@ -852,5 +896,87 @@ namespace CHECK_THROW(mc.set_states(stateList, StateId::Number_Of_States), etl::fsm_state_list_order_exception); } + + //************************************************************************* + TEST(test_hfsm_start_enters) + { + MotorControl mc; + + motorControl.Initialise(stateList, std::size(stateList)); + motorControl.reset(); + motorControl.ClearStatistics(); + + CHECK(!motorControl.is_started()); + + // Start the HFSM (with enters) + motorControl.start(true); + 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())); + + CHECK_EQUAL(false, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(0, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.windUpCompleteCount); + CHECK_EQUAL(0, motorControl.windUpStartCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.unknownCount); + + etl::array expectedEnters{StateId::Idle}; + CHECK_EQUAL(expectedEnters.size(), motorControl.stateEnterHistory.size()); + // No enters should have been performed + CHECK(motorControl.stateExitHistory.empty()); + + bool entersCorrect = std::equal(motorControl.stateEnterHistory.begin(), motorControl.stateEnterHistory.end(), expectedEnters.begin()); + CHECK(entersCorrect); + } + + //************************************************************************* + TEST(test_hfsm_reset_exits) + { + MotorControl mc; + + motorControl.Initialise(stateList, std::size(stateList)); + motorControl.reset(); + motorControl.ClearStatistics(); + + CHECK(!motorControl.is_started()); + + // Start the HFSM (with enters) + motorControl.start(false); + CHECK(motorControl.is_started()); + + // Now in Idle state. + + // Send Start event. + motorControl.receive(Start()); + + // Send Timeout event + motorControl.receive(Timeout()); + + // Send SetSpeed event. + motorControl.receive(SetSpeed(100)); + CHECK_EQUAL(StateId::At_Speed, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::At_Speed, int(motorControl.get_state().get_state_id())); + + // Clean statistics before reset for clean results + motorControl.ClearStatistics(); + + // Reset HFSM (with exits) + motorControl.reset(true); + + etl::array expectedExits{StateId::At_Speed, StateId::Running}; + CHECK_EQUAL(expectedExits.size(), motorControl.stateExitHistory.size()); + // No enters should have been performed + CHECK(motorControl.stateEnterHistory.empty()); + + bool exitsCorrect = std::equal(motorControl.stateExitHistory.begin(), motorControl.stateExitHistory.end(), expectedExits.begin()); + CHECK(exitsCorrect); + } }; }