diff --git a/include/etl/fsm.h b/include/etl/fsm.h index 816a164f..6123539b 100644 --- a/include/etl/fsm.h +++ b/include/etl/fsm.h @@ -68,6 +68,7 @@ SOFTWARE. namespace etl { class fsm; + class hfsm; /// Allow alternative type for state id. #if !defined(ETL_FSM_STATE_ID_TYPE) @@ -79,6 +80,13 @@ namespace etl // For internal FSM use. typedef typename etl::larger_type::type fsm_internal_id_t; + template + class fsm_state; + //*************************************************************************** /// Base exception class for FSM. //*************************************************************************** @@ -144,6 +152,18 @@ namespace etl } }; + //*************************************************************************** + /// Exception for forbidden state chages. + //*************************************************************************** + class fsm_state_composite_state_change_forbidden : public etl::fsm_exception + { + public: + fsm_state_composite_state_change_forbidden(string_type file_name_, numeric_type line_number_) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:change in composite state forbidden", ETL_FSM_FILE_ID"E"), file_name_, line_number_) + { + } + }; + //*************************************************************************** /// Interface class for FSM states. //*************************************************************************** @@ -151,8 +171,19 @@ namespace etl { public: + // Pass this whenever no state change is desired. + // The highest unsigned value of fsm_state_id_t. + static ETL_CONSTANT fsm_state_id_t No_State_Change = etl::integral_limits::max; + /// Allows ifsm_state functions to be private. friend class etl::fsm; + friend class etl::hfsm; + template + friend class etl::fsm_state; //******************************************* /// Gets the id for this state. @@ -162,6 +193,61 @@ namespace etl return state_id; } + //******************************************* + /// Adds a child to this state. + //******************************************* + void add_child_state(etl::ifsm_state& state) + { + ETL_ASSERT(state.p_parent == ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception)); + state.p_parent = this; + + if (p_default_child == ETL_NULLPTR) + { + p_active_child = &state; + p_default_child = &state; + } + } + + //******************************************* + /// Adds a list of child states. + //******************************************* + template + void set_child_states(etl::ifsm_state** state_list, TSize size) + { + p_active_child = ETL_NULLPTR; + p_default_child = ETL_NULLPTR; + + for (TSize i = 0; i < size; ++i) + { + ETL_ASSERT(state_list[i] != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception)); + add_child_state(*state_list[i]); + } + } + + //******************************************* + /// Get the parent state for this state. + //******************************************* + etl::ifsm_state* get_parent_state() const + { + return p_parent; + } + + //******************************************* + /// Get the active child state for this state. + //******************************************* + etl::ifsm_state* get_active_child_state() const + { + return p_active_child; + } + + //******************************************* + /// Get the default child state for this state. + //******************************************* + etl::ifsm_state* get_default_child_state() const + { + return p_default_child; + } + protected: //******************************************* @@ -169,7 +255,10 @@ namespace etl //******************************************* ifsm_state(etl::fsm_state_id_t state_id_) : state_id(state_id_), - p_context(ETL_NULLPTR) + p_context(ETL_NULLPTR), + p_parent(ETL_NULLPTR), + p_active_child(ETL_NULLPTR), + p_default_child(ETL_NULLPTR) { } @@ -190,7 +279,7 @@ namespace etl virtual fsm_state_id_t process_event(const etl::imessage& message) = 0; - virtual fsm_state_id_t on_enter_state() { return state_id; } // By default, do nothing. + virtual fsm_state_id_t on_enter_state() { return No_State_Change; } // By default, do nothing. virtual void on_exit_state() {} // By default, do nothing. //******************************************* @@ -205,6 +294,15 @@ namespace etl // A pointer to the FSM context. etl::fsm* p_context; + // A pointer to the parent. + ifsm_state* p_parent; + + // A pointer to the active child. + ifsm_state* p_active_child; + + // A pointer to the default active child. + ifsm_state* p_default_child; + // Disabled. ifsm_state(const ifsm_state&); ifsm_state& operator =(const ifsm_state&); @@ -217,6 +315,7 @@ namespace etl { public: + friend etl::hfsm; using imessage_router::receive; //******************************************* @@ -240,6 +339,7 @@ namespace etl number_of_states = etl::fsm_state_id_t(size); ETL_ASSERT(number_of_states > 0, ETL_ERROR(etl::fsm_state_list_exception)); + ETL_ASSERT(number_of_states < ifsm_state::No_State_Change, ETL_ERROR(etl::fsm_state_list_exception)); for (etl::fsm_state_id_t i = 0; i < size; ++i) { @@ -257,26 +357,29 @@ namespace etl //******************************************* void start(bool call_on_enter_state = true) { - // 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)); + // 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) - { - etl::fsm_state_id_t next_state_id; - etl::ifsm_state* p_last_state; + if (call_on_enter_state) + { + etl::fsm_state_id_t next_state_id; + etl::ifsm_state* p_last_state; - do - { - p_last_state = p_state; - next_state_id = p_state->on_enter_state(); - p_state = state_list[next_state_id]; - - } while (p_last_state != p_state); - } - } + do + { + p_last_state = p_state; + next_state_id = p_state->on_enter_state(); + if (next_state_id != ifsm_state::No_State_Change) + { + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + p_state = state_list[next_state_id]; + } + } while (p_last_state != p_state); + } + } } //******************************************* @@ -284,26 +387,27 @@ namespace etl //******************************************* void receive(const etl::imessage& message) ETL_OVERRIDE { - etl::fsm_state_id_t next_state_id = p_state->process_event(message); - ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + etl::fsm_state_id_t next_state_id = p_state->process_event(message); + if (have_changed_state(next_state_id)) + { + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); etl::ifsm_state* p_next_state = state_list[next_state_id]; - // Have we changed state? - if (p_next_state != p_state) + do { - 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)) { - p_state->on_exit_state(); - p_state = p_next_state; - - next_state_id = p_state->on_enter_state(); ETL_ASSERT(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? - } + } + } while (p_next_state != p_state); // Have we changed state again? + } } using imessage_router::accepts; @@ -386,6 +490,13 @@ namespace etl private: + //******************************************** + bool have_changed_state(etl::fsm_state_id_t next_state_id) const + { + return (next_state_id != p_state->get_state_id()) && + (next_state_id != ifsm_state::No_State_Change); + } + 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. @@ -395,10 +506,10 @@ namespace etl // The definition for all 16 message types. //*************************************************************************** template + typename T1, typename T2, typename T3, typename T4, + typename T5, typename T6, typename T7, typename T8, + typename T9, typename T10, typename T11, typename T12, + typename T13, typename T14, typename T15, typename T16> class fsm_state : public ifsm_state { public: @@ -449,7 +560,7 @@ namespace etl case T14::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T15::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T16::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -513,7 +624,7 @@ namespace etl case T13::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T14::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T15::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -576,7 +687,7 @@ namespace etl case T12::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T13::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T14::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -638,7 +749,7 @@ namespace etl case T11::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T12::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T13::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -698,7 +809,7 @@ namespace etl case T10::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T11::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T12::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -757,7 +868,7 @@ namespace etl case T9::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T10::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T11::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -815,7 +926,7 @@ namespace etl case T8::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T9::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T10::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -872,7 +983,7 @@ namespace etl case T7::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T8::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T9::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -927,7 +1038,7 @@ namespace etl case T6::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T7::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T8::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -981,7 +1092,7 @@ namespace etl case T5::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T6::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T7::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -1034,7 +1145,7 @@ namespace etl case T4::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T5::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T6::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -1086,7 +1197,7 @@ namespace etl case T3::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T4::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T5::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -1136,7 +1247,7 @@ namespace etl case T2::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T3::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T4::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -1185,7 +1296,7 @@ namespace etl case T1::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T2::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T3::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -1233,7 +1344,7 @@ namespace etl { case T1::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; case T2::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -1280,7 +1391,7 @@ namespace etl switch (event_id) { case T1::ID: new_state_id = static_cast(this)->on_event(static_cast(message)); break; - default: new_state_id = static_cast(this)->on_event_unknown(message); break; + default: new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); break; } return new_state_id; @@ -1319,7 +1430,7 @@ namespace etl etl::fsm_state_id_t process_event(const etl::imessage& message) { - return static_cast(this)->on_event_unknown(message); + return p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message); } }; } diff --git a/include/etl/generators/fsm_generator.h b/include/etl/generators/fsm_generator.h index d3c6eb8b..34fc133f 100644 --- a/include/etl/generators/fsm_generator.h +++ b/include/etl/generators/fsm_generator.h @@ -80,6 +80,7 @@ cog.outl("//******************************************************************** namespace etl { class fsm; + class hfsm; /// Allow alternative type for state id. #if !defined(ETL_FSM_STATE_ID_TYPE) @@ -91,6 +92,13 @@ namespace etl // For internal FSM use. typedef typename etl::larger_type::type fsm_internal_id_t; + template + class fsm_state; + //*************************************************************************** /// Base exception class for FSM. //*************************************************************************** @@ -156,6 +164,18 @@ namespace etl } }; + //*************************************************************************** + /// Exception for forbidden state chages. + //*************************************************************************** + class fsm_state_composite_state_change_forbidden : public etl::fsm_exception + { + public: + fsm_state_composite_state_change_forbidden(string_type file_name_, numeric_type line_number_) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:change in composite state forbidden", ETL_FSM_FILE_ID"E"), file_name_, line_number_) + { + } + }; + //*************************************************************************** /// Interface class for FSM states. //*************************************************************************** @@ -163,8 +183,19 @@ namespace etl { public: + // Pass this whenever no state change is desired. + // The highest unsigned value of fsm_state_id_t. + static ETL_CONSTANT fsm_state_id_t No_State_Change = etl::integral_limits::max; + /// Allows ifsm_state functions to be private. friend class etl::fsm; + friend class etl::hfsm; + template + friend class etl::fsm_state; //******************************************* /// Gets the id for this state. @@ -174,6 +205,61 @@ namespace etl return state_id; } + //******************************************* + /// Adds a child to this state. + //******************************************* + void add_child_state(etl::ifsm_state& state) + { + ETL_ASSERT(state.p_parent == ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception)); + state.p_parent = this; + + if (p_default_child == ETL_NULLPTR) + { + p_active_child = &state; + p_default_child = &state; + } + } + + //******************************************* + /// Adds a list of child states. + //******************************************* + template + void set_child_states(etl::ifsm_state** state_list, TSize size) + { + p_active_child = ETL_NULLPTR; + p_default_child = ETL_NULLPTR; + + for (TSize i = 0; i < size; ++i) + { + ETL_ASSERT(state_list[i] != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception)); + add_child_state(*state_list[i]); + } + } + + //******************************************* + /// Get the parent state for this state. + //******************************************* + etl::ifsm_state* get_parent_state() const + { + return p_parent; + } + + //******************************************* + /// Get the active child state for this state. + //******************************************* + etl::ifsm_state* get_active_child_state() const + { + return p_active_child; + } + + //******************************************* + /// Get the default child state for this state. + //******************************************* + etl::ifsm_state* get_default_child_state() const + { + return p_default_child; + } + protected: //******************************************* @@ -181,7 +267,10 @@ namespace etl //******************************************* ifsm_state(etl::fsm_state_id_t state_id_) : state_id(state_id_), - p_context(ETL_NULLPTR) + p_context(ETL_NULLPTR), + p_parent(ETL_NULLPTR), + p_active_child(ETL_NULLPTR), + p_default_child(ETL_NULLPTR) { } @@ -202,7 +291,7 @@ namespace etl virtual fsm_state_id_t process_event(const etl::imessage& message) = 0; - virtual fsm_state_id_t on_enter_state() { return state_id; } // By default, do nothing. + virtual fsm_state_id_t on_enter_state() { return No_State_Change; } // By default, do nothing. virtual void on_exit_state() {} // By default, do nothing. //******************************************* @@ -217,6 +306,15 @@ namespace etl // A pointer to the FSM context. etl::fsm* p_context; + // A pointer to the parent. + ifsm_state* p_parent; + + // A pointer to the active child. + ifsm_state* p_active_child; + + // A pointer to the default active child. + ifsm_state* p_default_child; + // Disabled. ifsm_state(const ifsm_state&); ifsm_state& operator =(const ifsm_state&); @@ -229,6 +327,7 @@ namespace etl { public: + friend etl::hfsm; using imessage_router::receive; //******************************************* @@ -252,6 +351,7 @@ namespace etl number_of_states = etl::fsm_state_id_t(size); ETL_ASSERT(number_of_states > 0, ETL_ERROR(etl::fsm_state_list_exception)); + ETL_ASSERT(number_of_states < ifsm_state::No_State_Change, ETL_ERROR(etl::fsm_state_list_exception)); for (etl::fsm_state_id_t i = 0; i < size; ++i) { @@ -269,26 +369,29 @@ namespace etl //******************************************* void start(bool call_on_enter_state = true) { - // 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)); + // 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) - { - etl::fsm_state_id_t next_state_id; - etl::ifsm_state* p_last_state; + if (call_on_enter_state) + { + etl::fsm_state_id_t next_state_id; + etl::ifsm_state* p_last_state; - do - { - p_last_state = p_state; - next_state_id = p_state->on_enter_state(); - p_state = state_list[next_state_id]; - - } while (p_last_state != p_state); - } - } + do + { + p_last_state = p_state; + next_state_id = p_state->on_enter_state(); + if (next_state_id != ifsm_state::No_State_Change) + { + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + p_state = state_list[next_state_id]; + } + } while (p_last_state != p_state); + } + } } //******************************************* @@ -296,26 +399,27 @@ namespace etl //******************************************* void receive(const etl::imessage& message) ETL_OVERRIDE { - etl::fsm_state_id_t next_state_id = p_state->process_event(message); - ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + etl::fsm_state_id_t next_state_id = p_state->process_event(message); + if (have_changed_state(next_state_id)) + { + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); etl::ifsm_state* p_next_state = state_list[next_state_id]; - // Have we changed state? - if (p_next_state != p_state) + do { - 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)) { - p_state->on_exit_state(); - p_state = p_next_state; - - next_state_id = p_state->on_enter_state(); ETL_ASSERT(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? - } + } + } while (p_next_state != p_state); // Have we changed state again? + } } using imessage_router::accepts; @@ -398,6 +502,13 @@ namespace etl private: + //******************************************** + bool have_changed_state(etl::fsm_state_id_t next_state_id) const + { + return (next_state_id != p_state->get_state_id()) && + (next_state_id != ifsm_state::No_State_Change); + } + 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. @@ -414,11 +525,11 @@ namespace etl cog.outl("template " % Handlers) + cog.outl("typename T%s>" % Handlers) cog.outl("class fsm_state : public ifsm_state") cog.outl("{") cog.outl("public:") @@ -458,7 +569,7 @@ namespace etl cog.out(" new_state_id = static_cast(this)->on_event(static_cast(message));" % n) cog.outl(" break;") cog.out(" default:") - cog.out(" new_state_id = static_cast(this)->on_event_unknown(message);") + cog.out(" new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message);") cog.outl(" break;") cog.outl(" }") cog.outl("") @@ -535,7 +646,7 @@ namespace etl cog.out(" new_state_id = static_cast(this)->on_event(static_cast(message));" % n) cog.outl(" break;") cog.out(" default:") - cog.out(" new_state_id = static_cast(this)->on_event_unknown(message);") + cog.out(" new_state_id = p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message);") cog.outl(" break;") cog.outl(" }") cog.outl("") @@ -584,7 +695,7 @@ namespace etl cog.outl("") cog.outl(" etl::fsm_state_id_t process_event(const etl::imessage& message)") cog.outl(" {") - cog.outl(" return static_cast(this)->on_event_unknown(message);") + cog.outl(" return p_parent ? p_parent->process_event(message) : static_cast(this)->on_event_unknown(message);") cog.outl(" }") cog.outl("};") ]]]*/ diff --git a/include/etl/hfsm.h b/include/etl/hfsm.h new file mode 100644 index 00000000..134f18d1 --- /dev/null +++ b/include/etl/hfsm.h @@ -0,0 +1,208 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2021 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_HFSM_INCLUDED +#define ETL_HFSM_INCLUDED + +#include "fsm.h" + +namespace etl +{ + //*************************************************************************** + /// The HFSM class. + /// Builds on the FSM class by overriding the receive function and adding + /// state hierarchy walking functions. + //*************************************************************************** + class hfsm : public etl::fsm + { + public: + + //******************************************* + /// Constructor. + //******************************************* + hfsm(etl::message_router_id_t id) + : fsm(id) + { + } + + using fsm::receive; + + //******************************************* + /// Top level message handler for the HFSM. + //******************************************* + void receive(const etl::imessage& message) ETL_OVERRIDE + { + etl::fsm_state_id_t next_state_id = p_state->process_event(message); + + if (next_state_id != ifsm_state::No_State_Change) + { + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + etl::ifsm_state* p_next_state = state_list[next_state_id]; + + // Have we changed state? + if (p_next_state != p_state) + { + etl::ifsm_state* p_root = common_ancestor(p_state, p_next_state); + do_exits(p_root, p_state); + + p_state = p_next_state; + + next_state_id = do_enters(p_root, p_next_state, true); + + if (next_state_id != ifsm_state::No_State_Change) + { + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + p_state = state_list[next_state_id]; + } + } + } + } + + private: + + //******************************************* + /// Return the first common ancester of the two states. + //******************************************* + static etl::ifsm_state* common_ancestor(etl::ifsm_state* p1, etl::ifsm_state* p2) + { + size_t depth1 = get_depth(p1); + size_t depth2 = get_depth(p2); + + // Adjust p1 and p2 to the same depth. + if (depth1 > depth2) + { + p1 = adjust_depth(p1, depth1 - depth2); + } + else + { + p2 = adjust_depth(p2, depth2 - depth1); + } + + // Now they're aligned to the same depth they can step towards the root together. + while (p1 != p2) + { + p1 = p1->p_parent; + p2 = p2->p_parent; + } + + return p1; + } + + //******************************************* + /// Find the depth of the state. + //******************************************* + static size_t get_depth(etl::ifsm_state* p) + { + size_t depth = 0; + + while (p != ETL_NULLPTR) + { + p = p->p_parent; + ++depth; + } + + return depth; + } + + //******************************************* + /// Align the depths of the states. + //******************************************* + static etl::ifsm_state* adjust_depth(etl::ifsm_state* p, size_t offset) + { + while (offset != 0U) + { + p = p->p_parent; + --offset; + } + + return p; + } + + //******************************************* + /// Entering the state. + //******************************************* + static etl::fsm_state_id_t do_enters(const etl::ifsm_state* p_root, etl::ifsm_state* p_target, bool activate_default_children) + { + ETL_ASSERT(p_target != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception)); + + // We need to go recursively up the tree if the target and root don't match + if ((p_root != p_target) && (p_target->p_parent != ETL_NULLPTR)) + { + if (p_target->p_parent != p_root) + { + // The parent we're calling shouldn't activate its defaults, or this state will be deactivated. + do_enters(p_root, p_target->p_parent, false); + } + + // Set us as our parent's active child + p_target->p_parent->p_active_child = p_target; + } + + etl::fsm_state_id_t next_state = p_target->on_enter_state(); + ETL_ASSERT(ifsm_state::No_State_Change == next_state, ETL_ERROR(etl::fsm_state_composite_state_change_forbidden)); + + // Activate default child if we need to activate any initial states in an active composite state. + if (activate_default_children) + { + while (p_target->p_default_child != ETL_NULLPTR) + { + p_target = p_target->p_default_child; + p_target->p_parent->p_active_child = p_target; + next_state = p_target->on_enter_state(); + ETL_ASSERT(ifsm_state::No_State_Change == next_state, ETL_ERROR(etl::fsm_state_composite_state_change_forbidden)); + } + + next_state = p_target->get_state_id(); + } + + return next_state; + } + + //******************************************* + /// Exiting the state. + //******************************************* + static void do_exits(const etl::ifsm_state* p_root, etl::ifsm_state* p_source) + { + etl::ifsm_state* p_current = p_source; + + // Iterate down to the lowest child + while (p_current->p_active_child != ETL_NULLPTR) + { + p_current = p_current->p_active_child; + } + + // Run exit state on all states up to the root + while (p_current != p_root) + { + p_current->on_exit_state(); + p_current = p_current->p_parent; + } + } + }; +} +#endif diff --git a/meson.build b/meson.build index f01d5a17..e5f24c8e 100644 --- a/meson.build +++ b/meson.build @@ -54,6 +54,7 @@ etl_test_sources = files( 'test/test_fnv_1.cpp', 'test/test_forward_list.cpp', 'test/test_fsm.cpp', + 'test/test_hfsm.cpp', 'test/test_functional.cpp', 'test/test_function.cpp', 'test/test_hash.cpp', diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 25c4bb03..e91d9b3c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -121,6 +121,7 @@ set(TEST_SOURCE_FILES test_functional.cpp test_gamma.cpp test_hash.cpp + test_hfsm.cpp test_histogram.cpp test_indirect_vector.cpp test_indirect_vector_external_buffer.cpp diff --git a/test/test_fsm.cpp b/test/test_fsm.cpp index 386f585d..5e22d5a0 100644 --- a/test/test_fsm.cpp +++ b/test/test_fsm.cpp @@ -224,7 +224,7 @@ namespace etl::fsm_state_id_t on_event_unknown(const etl::imessage&) { ++get_fsm_context().unknownCount; - return STATE_ID; + return StateId::IDLE; //No_State_Change; } //*********************************** diff --git a/test/test_hfsm.cpp b/test/test_hfsm.cpp new file mode 100644 index 00000000..e081c9ca --- /dev/null +++ b/test/test_hfsm.cpp @@ -0,0 +1,790 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2021 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "etl/hfsm.h" +#include "etl/enum_type.h" +#include "etl/container.h" +#include "etl/packet.h" +#include "etl/queue.h" + +#include + +// This test implements the following state machine: +// +--------------------------------------------+ +// | | +// | O running | +// | | | +// O | v | +// | | +-----------+ +-----------+ | +// v | | |Timeout | | | +// +------+ Start | | windingUp +------->| atSpeed | | +// | idle +-------+--->| | | | | +// +------+ | +------+----+ +------+----+ | +// ^ ^ | | | | +// | | | Stop | | | +// | | | v |Stop | +// | | | +------------------+ | | +// | | Stopped | | | | | +// | +----------+--------+ windingDown |<---+ | +// | | | | | +// | EStop | +------------------+ | +// +-------------+ | +// | | +// +--------------------------------------------+ +// Created with asciiflow.com +namespace +{ + const etl::message_router_id_t MOTOR_CONTROL = 0; + + + //*************************************************************************** + // Events + struct EventId + { + enum enum_type + { + START, + STOP, + ESTOP, + STOPPED, + SET_SPEED, + RECURSIVE, + TIMEOUT, + UNSUPPORTED + }; + + ETL_DECLARE_ENUM_TYPE(EventId, etl::message_id_t) + ETL_ENUM_TYPE(START, "Start") + ETL_ENUM_TYPE(STOP, "Stop") + ETL_ENUM_TYPE(ESTOP, "E-Stop") + ETL_ENUM_TYPE(STOPPED, "Stopped") + ETL_ENUM_TYPE(SET_SPEED, "Set Speed") + ETL_ENUM_TYPE(RECURSIVE, "Recursive") + ETL_ENUM_TYPE(TIMEOUT, "Timeout") + ETL_ENUM_TYPE(UNSUPPORTED, "Unsupported") + ETL_END_ENUM_TYPE + }; + + //*********************************** + class Start : public etl::message + { + }; + + //*********************************** + class Stop : public etl::message + { + }; + + //*********************************** + class EStop : public etl::message + { + }; + + //*********************************** + class SetSpeed : public etl::message + { + public: + + SetSpeed(int speed_) : speed(speed_) {} + + const int speed; + }; + + //*********************************** + class Stopped : public etl::message + { + }; + + //*********************************** + class Recursive : public etl::message + { + }; + + //*********************************** + class Timeout : public etl::message + { + }; + + //*********************************** + class Unsupported : public etl::message + { + }; + + //*************************************************************************** + // States + struct StateId + { + enum enum_type + { + IDLE, + RUNNING, + WINDING_UP, + WINDING_DOWN, + AT_SPEED, + NUMBER_OF_STATES + }; + + ETL_DECLARE_ENUM_TYPE(StateId, etl::fsm_state_id_t) + ETL_ENUM_TYPE(IDLE, "Idle") + ETL_ENUM_TYPE(RUNNING, "Running") + ETL_ENUM_TYPE(WINDING_UP, "Winding Up") + ETL_ENUM_TYPE(WINDING_DOWN, "Winding Down") + ETL_ENUM_TYPE(AT_SPEED, "At Speed") + ETL_END_ENUM_TYPE + }; + + //*********************************** + // The motor control FSM. + //*********************************** + class MotorControl : public etl::hfsm + { + public: + + MotorControl() + : hfsm(MOTOR_CONTROL) + { + } + + //*********************************** + void Initialise(etl::ifsm_state** p_states, size_t size) + { + set_states(p_states, size); + ClearStatistics(); + } + + //*********************************** + void ClearStatistics() + { + startCount = 0; + stopCount = 0; + setSpeedCount = 0; + windUpCompleteCount = 0; + windUpStartCount = 0; + unknownCount = 0; + stoppedCount = 0; + isLampOn = false; + speed = 0; + } + + //*********************************** + void SetSpeedValue(int speed_) + { + speed = speed_; + } + + //*********************************** + void TurnRunningLampOn() + { + isLampOn = true; + } + + //*********************************** + void TurnRunningLampOff() + { + isLampOn = false; + } + + //*********************************** + template + void queue_recursive_message(const T& message) + { + messageQueue.emplace(message); + } + + typedef etl::largest Largest_t; + + typedef etl::packet Packet_t; + + etl::queue messageQueue; + + int startCount; + int stopCount; + int windUpCompleteCount; + int windUpStartCount; + int setSpeedCount; + int unknownCount; + int stoppedCount; + bool isLampOn; + int speed; + }; + + //*********************************** + // The idle state. + //*********************************** + class Idle : public etl::fsm_state + { + public: + + //*********************************** + etl::fsm_state_id_t on_event(const Start&) + { + ++get_fsm_context().startCount; + return StateId::RUNNING; + } + + //*********************************** + etl::fsm_state_id_t on_event(const Recursive&) + { + get_fsm_context().queue_recursive_message(Start()); + return StateId::IDLE; + } + + //*********************************** + etl::fsm_state_id_t on_event_unknown(const etl::imessage&) + { + ++get_fsm_context().unknownCount; + return No_State_Change; + } + + //*********************************** + etl::fsm_state_id_t on_enter_state() + { + get_fsm_context().TurnRunningLampOff(); + return No_State_Change; + } + }; + + //*********************************** + // The running state. + //*********************************** + class Running : public etl::fsm_state + { + public: + + //*********************************** + etl::fsm_state_id_t on_event(const EStop& event) + { + ++get_fsm_context().stopCount; + + return StateId::IDLE; + } + + //*********************************** + etl::fsm_state_id_t on_event_unknown(const etl::imessage&) + { + ++get_fsm_context().unknownCount; + return No_State_Change; + } + + //*********************************** + etl::fsm_state_id_t on_enter_state() + { + get_fsm_context().TurnRunningLampOn(); + + return No_State_Change; + } + }; + + //*********************************** + // The winding up state. + //*********************************** + class WindingUp : public etl::fsm_state + { + public: + + //*********************************** + etl::fsm_state_id_t on_event(const Stop&) + { + ++get_fsm_context().stopCount; + return StateId::WINDING_DOWN; + } + + //*********************************** + etl::fsm_state_id_t on_event(const Timeout&) + { + ++get_fsm_context().windUpCompleteCount; + return StateId::AT_SPEED; + } + + //*********************************** + etl::fsm_state_id_t on_event_unknown(const etl::imessage&) + { + ++get_fsm_context().unknownCount; + + return No_State_Change; + } + + etl::fsm_state_id_t on_enter_state() + { + ++get_fsm_context().windUpStartCount; + return No_State_Change; + } + }; + + //*********************************** + // The at speed state. + //*********************************** + class AtSpeed : public etl::fsm_state + { + public: + //*********************************** + etl::fsm_state_id_t on_event(const Stop&) + { + ++get_fsm_context().stopCount; + return StateId::WINDING_DOWN; + } + + //*********************************** + etl::fsm_state_id_t on_event(const SetSpeed& event) + { + ++get_fsm_context().setSpeedCount; + get_fsm_context().SetSpeedValue(event.speed); + //return No_State_Change; + return this->get_state_id(); + } + + //*********************************** + etl::fsm_state_id_t on_event_unknown(const etl::imessage&) + { + ++get_fsm_context().unknownCount; + return No_State_Change; + } + }; + + //*********************************** + // The winding down state. + //*********************************** + class WindingDown : public etl::fsm_state + { + public: + + //*********************************** + etl::fsm_state_id_t on_event(const Stopped&) + { + ++get_fsm_context().stoppedCount; + return StateId::IDLE; + } + + //*********************************** + etl::fsm_state_id_t on_event_unknown(const etl::imessage&) + { + ++get_fsm_context().unknownCount; + return No_State_Change; + } + }; + + // The states. + Idle idle; + Running running; + WindingUp windingUp; + WindingDown windingDown; + AtSpeed atSpeed; + + etl::ifsm_state* stateList[StateId::NUMBER_OF_STATES] = + { + &idle, &running, &windingUp, &windingDown, &atSpeed + }; + + etl::ifsm_state* childStates[] = + { + &windingUp, &atSpeed, &windingDown + }; + + MotorControl motorControl; + + SUITE(test_hfsm_states) + { + //************************************************************************* + TEST(test_hfsm) + { + etl::null_message_router nmr; + + CHECK(motorControl.is_producer()); + CHECK(motorControl.is_consumer()); + + running.set_child_states(childStates, etl::size(childStates)); + + motorControl.Initialise(stateList, etl::size(stateList)); + motorControl.reset(); + motorControl.ClearStatistics(); + + CHECK(!motorControl.is_started()); + + // 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())); + + 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); + + // Send unhandled events. + motorControl.receive(Stop()); + motorControl.receive(Stopped()); + motorControl.receive(SetSpeed(10)); + + CHECK_EQUAL(StateId::IDLE, motorControl.get_state_id()); + CHECK_EQUAL(StateId::IDLE, 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.stoppedCount); + CHECK_EQUAL(3, motorControl.unknownCount); + CHECK_EQUAL(0, motorControl.windUpCompleteCount); + CHECK_EQUAL(0, motorControl.windUpStartCount); + + // Send Start event. + motorControl.receive(Start()); + + // Now in WindingUp state. + + CHECK_EQUAL(StateId::WINDING_UP, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::WINDING_UP, int(motorControl.get_state().get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(3, motorControl.unknownCount); + CHECK_EQUAL(0, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + + // Send unhandled events. + motorControl.receive(Start()); + motorControl.receive(Stopped()); + + CHECK_EQUAL(StateId::WINDING_UP, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::WINDING_UP, int(motorControl.get_state().get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(5, motorControl.unknownCount); + CHECK_EQUAL(0, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + + // Send Timeout event + motorControl.receive(Timeout()); + + CHECK_EQUAL(StateId::AT_SPEED, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::AT_SPEED, int(motorControl.get_state().get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(5, motorControl.unknownCount); + CHECK_EQUAL(1, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + + // Send SetSpeed event. + motorControl.receive(SetSpeed(100)); + + // Still in at speed state. + + CHECK_EQUAL(StateId::AT_SPEED, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::AT_SPEED, int(motorControl.get_state().get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(1, motorControl.setSpeedCount); + CHECK_EQUAL(100, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(5, motorControl.unknownCount); + CHECK_EQUAL(1, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + + // 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())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(1, motorControl.setSpeedCount); + CHECK_EQUAL(100, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(1, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(5, motorControl.unknownCount); + CHECK_EQUAL(1, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + + // Send unhandled events. + motorControl.receive(Start()); + motorControl.receive(Stop()); + motorControl.receive(SetSpeed(100)); + + CHECK_EQUAL(StateId::WINDING_DOWN, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::WINDING_DOWN, int(motorControl.get_state().get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(1, motorControl.setSpeedCount); + CHECK_EQUAL(100, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(1, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(8, motorControl.unknownCount); + CHECK_EQUAL(1, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + + // Send Stopped event. + 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())); + + CHECK_EQUAL(false, motorControl.isLampOn); + CHECK_EQUAL(1, motorControl.setSpeedCount); + CHECK_EQUAL(100, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(1, motorControl.stopCount); + CHECK_EQUAL(1, motorControl.stoppedCount); + CHECK_EQUAL(8, motorControl.unknownCount); + CHECK_EQUAL(1, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + } + + //************************************************************************* + TEST(test_hfsm_emergency_stop_from_winding_up) + { + etl::null_message_router nmr; + + motorControl.Initialise(stateList, etl::size(stateList)); + motorControl.reset(); + motorControl.ClearStatistics(); + + CHECK(!motorControl.is_started()); + + // Start the FSM. + motorControl.start(false); + CHECK(motorControl.is_started()); + + // Now in Idle state. + + // Send Start event. + motorControl.receive(Start()); + + // Now in winding up state. + + CHECK_EQUAL(StateId::WINDING_UP, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::WINDING_UP, int(motorControl.get_state().get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.unknownCount); + CHECK_EQUAL(0, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + + // Send emergency Stop event. + motorControl.receive(EStop()); + + // 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(1, motorControl.startCount); + CHECK_EQUAL(1, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.unknownCount); + CHECK_EQUAL(0, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + } + + //************************************************************************* + TEST(test_hfsm_emergency_stop_from_at_speed) + { + etl::null_message_router nmr; + + motorControl.Initialise(stateList, etl::size(stateList)); + motorControl.reset(); + motorControl.ClearStatistics(); + + CHECK(!motorControl.is_started()); + + // Start the FSM. + motorControl.start(false); + CHECK(motorControl.is_started()); + + // Now in Idle state. + + // Send Start event. + motorControl.receive(Start()); + motorControl.receive(Timeout()); + + // Now in at speed state. + + CHECK_EQUAL(StateId::AT_SPEED, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::AT_SPEED, int(motorControl.get_state().get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.unknownCount); + CHECK_EQUAL(1, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + + // Send emergency Stop event. + motorControl.receive(EStop()); + + // 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(1, motorControl.startCount); + CHECK_EQUAL(1, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.unknownCount); + CHECK_EQUAL(1, motorControl.windUpCompleteCount); + CHECK_EQUAL(1, motorControl.windUpStartCount); + } + + //************************************************************************* + TEST(test_hfsm_recursive_event) + { + etl::null_message_router nmr; + + motorControl.Initialise(stateList, etl::size(stateList)); + motorControl.reset(); + motorControl.ClearStatistics(); + + motorControl.messageQueue.clear(); + + // Start the FSM. + motorControl.start(false); + + // Now in Idle state. + // Send Start event. + motorControl.receive(Recursive()); + + CHECK_EQUAL(1U, motorControl.messageQueue.size()); + + // Send the queued message. + motorControl.receive(motorControl.messageQueue.front().get()); + motorControl.messageQueue.pop(); + + // Now in winding up state. + + CHECK_EQUAL(StateId::WINDING_UP, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::WINDING_UP, int(motorControl.get_state().get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.unknownCount); + } + + //************************************************************************* + TEST(test_hfsm_supported) + { + CHECK(motorControl.accepts(EventId::SET_SPEED)); + CHECK(motorControl.accepts(EventId::START)); + CHECK(motorControl.accepts(EventId::STOP)); + CHECK(motorControl.accepts(EventId::STOPPED)); + CHECK(motorControl.accepts(EventId::UNSUPPORTED)); + + CHECK(motorControl.accepts(SetSpeed(0))); + CHECK(motorControl.accepts(Start())); + CHECK(motorControl.accepts(Stop())); + CHECK(motorControl.accepts(Stopped())); + CHECK(motorControl.accepts(Unsupported())); + } + + //************************************************************************* + TEST(test_hfsm_no_states) + { + MotorControl mc; + + // No states. + etl::ifsm_state** stateList = nullptr; + + CHECK_THROW(mc.set_states(stateList, 0U), etl::fsm_state_list_exception); + } + + //************************************************************************* + TEST(test_hfsm_null_state) + { + MotorControl mc; + + // Null state. + etl::ifsm_state* stateList[StateId::NUMBER_OF_STATES] = + { + &idle, &running, &windingUp, &windingDown, nullptr + }; + + CHECK_THROW(mc.set_states(stateList, StateId::NUMBER_OF_STATES), etl::fsm_null_state_exception); + } + + //************************************************************************* + TEST(test_hfsm_incorrect_state_order) + { + MotorControl mc; + + // Incorrect order. + etl::ifsm_state* stateList[StateId::NUMBER_OF_STATES] = + { + &idle, &running, &windingDown, &windingUp, &atSpeed + }; + + CHECK_THROW(mc.set_states(stateList, StateId::NUMBER_OF_STATES), etl::fsm_state_list_order_exception); + } + }; +} diff --git a/test/vs2019/etl.vcxproj b/test/vs2019/etl.vcxproj index 37f1980a..80c7a26d 100644 --- a/test/vs2019/etl.vcxproj +++ b/test/vs2019/etl.vcxproj @@ -1326,6 +1326,7 @@ + @@ -4481,6 +4482,7 @@ + diff --git a/test/vs2019/etl.vcxproj.filters b/test/vs2019/etl.vcxproj.filters index a2429199..8b11f5cf 100644 --- a/test/vs2019/etl.vcxproj.filters +++ b/test/vs2019/etl.vcxproj.filters @@ -1098,6 +1098,9 @@ ETL\Maths + + ETL\Frameworks + @@ -2513,6 +2516,9 @@ Source Files + + Source Files +