From 1d4dbc89769c8dae2118a2f5f49702fa0c0ee479 Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Mon, 4 Aug 2025 16:10:04 +0100 Subject: [PATCH] Added etl::fsm_state_pack and corresponding constructor --- include/etl/fsm.h | 92 ++++++++++++++++++++++++++++++++++++++++++++- include/etl/tuple.h | 2 +- test/test_fsm.cpp | 62 +++++++++++++++++++++++++++--- 3 files changed, 147 insertions(+), 9 deletions(-) diff --git a/include/etl/fsm.h b/include/etl/fsm.h index bcd86c5a..bb5fdc9a 100644 --- a/include/etl/fsm.h +++ b/include/etl/fsm.h @@ -60,6 +60,7 @@ SOFTWARE. #include "message_router.h" #include "integral_limits.h" #include "largest.h" +#include "tuple.h" #include @@ -207,8 +208,78 @@ namespace etl template ETL_CONSTANT fsm_state_id_t ifsm_state_helper::Self_Transition; + + // Compile-time: TState::ID must equal its index in the type list (0..N-1) + template struct check_ids : etl::true_type + { + }; + + template + struct check_ids + : etl::integral_constant::value> + { + }; } + class ifsm_state; + + //*************************************************************************** + /// A class to store FSM states. + //*************************************************************************** + template + class fsm_state_pack + { + public: + + friend class etl::fsm; + + ETL_STATIC_ASSERT((private_fsm::check_ids<0, TStates...>::value), "State IDs must be 0..N-1 and in order"); + ETL_STATIC_ASSERT(sizeof...(TStates) > 0, "At least one state is required"); + ETL_STATIC_ASSERT(sizeof...(TStates) < private_fsm::ifsm_state_helper<>::No_State_Change, "State IDs mst be less than ifsm_state::No_State_Change"); + + //********************************* + // The number of states. + //********************************* + static constexpr size_t size() + { + return sizeof...(TStates); + } + + //********************************* + /// Gets a reference to the state. + //********************************* + template + TState& get() + { + return etl::get(storage); + } + + //********************************* + /// Gets a const reference to the state. + //********************************* + template + const TState& get() const + { + return etl::get(storage); + } + + private: + + //********************************* + /// Gets a pointer to the state list. + //********************************* + etl::ifsm_state** get_state_list() + { + return &states[0]; + } + + /// A tuple to store the states. + etl::tuple storage{}; + + /// Pointers to the states. + etl::ifsm_state* states[sizeof...(TStates)]{ &etl::get(storage)... }; + }; + //*************************************************************************** /// Interface class for FSM states. //*************************************************************************** @@ -333,8 +404,8 @@ namespace etl ifsm_state* p_default_child; // Disabled. - ifsm_state(const ifsm_state&); - ifsm_state& operator =(const ifsm_state&); + ifsm_state(const ifsm_state&) ETL_DELETE; + ifsm_state& operator =(const ifsm_state&) ETL_DELETE; }; //*************************************************************************** @@ -360,6 +431,7 @@ namespace etl //******************************************* /// Set the states for the FSM + /// From a pointer to etl::ifsm_state and size. //******************************************* template void set_states(etl::ifsm_state** p_states, TSize size) @@ -378,6 +450,22 @@ namespace etl } } + //******************************************* + /// Set the states for the FSM + /// From an etl::fsm_state_pack. + //******************************************* + template + void set_states(etl::fsm_state_pack& state_pack) + { + state_list = state_pack.get_state_list(); + number_of_states = etl::fsm_state_id_t(state_pack.size()); + + for (etl::fsm_state_id_t i = 0; i < number_of_states; ++i) + { + state_list[i]->set_fsm_context(*this); + } + } + //******************************************* /// Starts the FSM. /// Can only be called once. diff --git a/include/etl/tuple.h b/include/etl/tuple.h index 50cf1050..c4a9440b 100644 --- a/include/etl/tuple.h +++ b/include/etl/tuple.h @@ -240,7 +240,7 @@ namespace etl //********************************* ETL_CONSTEXPR14 tuple() - : value(THead()) + : value() { } diff --git a/test/test_fsm.cpp b/test/test_fsm.cpp index a1b29b04..331ef363 100644 --- a/test/test_fsm.cpp +++ b/test/test_fsm.cpp @@ -154,6 +154,14 @@ namespace ClearStatistics(); } + //*********************************** + template + void Initialise(etl::fsm_state_pack& state_pack) + { + set_states(state_pack); + ClearStatistics(); + } + //*********************************** void ClearStatistics() { @@ -367,8 +375,6 @@ namespace //************************************************************************* TEST(test_fsm) { - etl::null_message_router nmr; - CHECK(motorControl.is_producer()); CHECK(motorControl.is_consumer()); @@ -509,8 +515,6 @@ namespace //************************************************************************* TEST(test_fsm_emergency_stop) { - etl::null_message_router nmr; - motorControl.Initialise(stateList, ETL_OR_STD17::size(stateList)); motorControl.reset(); motorControl.ClearStatistics(); @@ -558,8 +562,6 @@ namespace //************************************************************************* TEST(test_fsm_recursive_event) { - etl::null_message_router nmr; - motorControl.Initialise(stateList, ETL_OR_STD17::size(stateList)); motorControl.reset(); motorControl.ClearStatistics(); @@ -680,5 +682,53 @@ namespace CHECK_THROW(mc.receive(Start()), etl::fsm_not_started); } + + //************************************************************************* + TEST(test_fsm_initialise_from_state_pack) + { + 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())); + + // Send Start event. + motorControl.receive(Start()); + + // Now in Running state. + CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id())); + + // Send SetSpeed event. + motorControl.receive(SetSpeed(100)); + + // Still in Running state. + CHECK_EQUAL(StateId::Running, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Running, int(motorControl.get_state().get_state_id())); + + // Send Stop event. + motorControl.receive(Stop()); + + // Now in WindingDown state. + CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Winding_Down, int(motorControl.get_state().get_state_id())); + + // Send Stopped event. + motorControl.receive(Stopped()); + + // Now in Locked state via Idle state. + CHECK_EQUAL(StateId::Locked, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::Locked, int(motorControl.get_state().get_state_id())); + } }; }