diff --git a/src/fsm.h b/src/fsm.h new file mode 100644 index 00000000..4a4236ed --- /dev/null +++ b/src/fsm.h @@ -0,0 +1,1024 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +http://www.etlcpp.com + +Copyright(c) 2017 jwellbelove + +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_FSM__ +#define __ETL_FSM__ + +#include + +#include "array.h" +#include "nullptr.h" +#include "error_handler.h" +#include "exception.h" +#include "user_type.h" +#include "message_router.h" + +#undef ETL_FILE +#define ETL_FILE "34" + +namespace etl +{ + /// Allow alternative type for state id. +#if !defined(ETL_FSM_STATE_ID_TYPE) + typedef uint_least8_t fsm_state_id_t; +#else + typedef ETL_FSM_STATE_ID_TYPE fsm_state_id_t; +#endif + + typedef etl::imessage::id_t fsm_event_id_t; + + //*************************************************************************** + /// Base exception class for FSM. + //*************************************************************************** + class fsm_exception : public etl::exception + { + public: + + fsm_exception(string_type what, string_type file_name, numeric_type line_number) + : etl::exception(what, file_name, line_number) + { + } + }; + + //*************************************************************************** + /// Exception for null state pointer. + //*************************************************************************** + class fsm_null_state_exception : public etl::fsm_exception + { + public: + + fsm_null_state_exception(string_type file_name, numeric_type line_number) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:null state", ETL_FILE"A"), file_name, line_number) + { + } + }; + + //*************************************************************************** + /// Exception for invalid state id. + //*************************************************************************** + class fsm_state_id_exception : public etl::fsm_exception + { + public: + + fsm_state_id_exception(string_type file_name, numeric_type line_number) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:state id", ETL_FILE"B"), file_name, line_number) + { + } + }; + + //*************************************************************************** + /// Exception for incompatible state list. + //*************************************************************************** + class fsm_state_list_exception : public etl::fsm_exception + { + public: + + fsm_state_list_exception(string_type file_name, numeric_type line_number) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:state list", ETL_FILE"C"), file_name, line_number) + { + } + }; + + //*************************************************************************** + /// Interface class for FSM states. + //*************************************************************************** + class ifsm_state + { + public: + + friend class fsm; + + //******************************************* + /// Gets the id for this state. + //******************************************* + etl::fsm_state_id_t get_state_id() const + { + return state_id; + } + + protected: + + virtual fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) = 0; + + //******************************************* + /// Constructor. + //******************************************* + ifsm_state(etl::fsm_state_id_t state_id_) + : state_id(state_id_) + { + } + + virtual void on_enter_state() {}; // By default, do nothing. + virtual void on_exit_state() {}; // By default, do nothing. + + private: + + // The state id. + const etl::fsm_state_id_t state_id; + + // Disabled. + ifsm_state(const ifsm_state&); + ifsm_state& operator =(const ifsm_state&); + }; + + //*************************************************************************** + class fsm : public etl::imessage_router + { + public: + + //******************************************* + /// Constructor. + //******************************************* + fsm() + : p_state(nullptr) + { + } + + //******************************************* + /// Set the states for the FSM + //******************************************* + template + void set_states(etl::ifsm_state** p_states, TSize size) + { + state_list = p_states; + number_of_states = etl::fsm_state_id_t(size); + + for (etl::fsm_state_id_t i = 0; i < size; ++i) + { + ETL_ASSERT((state_list[i] != nullptr), ETL_ERROR(etl::fsm_null_state_exception)); + } + + bool ok = (number_of_states > 0) && + etl::is_sorted(state_list, state_list + number_of_states, fsm::CompareStateId()); + + ETL_ASSERT(ok, ETL_ERROR(etl::fsm_state_list_exception)); + } + + //******************************************* + /// Starts the FSM. + /// Can only be called once. + /// Subsequent calls will do nothing. + //******************************************* + void start() + { + // Can only be started once. + if (p_state == nullptr) + { + p_state = state_list[0]; + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + + p_state->on_enter_state(); + } + } + + //******************************************* + /// Top level message handlers for the FSM. + //******************************************* + void receive(const etl::imessage& message) + { + receive(etl::null_message_router(), message); + } + + void receive(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t next_state_id = p_state->process_event(source, message); + + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + + // Have we changed state? + if (next_state_id != p_state->get_state_id()) + { + p_state->on_exit_state(); + + p_state = state_list[next_state_id]; + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + + p_state->on_enter_state(); + } + } + + //******************************************* + /// Gets the current state id. + //******************************************* + etl::fsm_state_id_t get_state_id() const + { + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + return p_state->get_state_id(); + } + + //******************************************* + /// Gets a reference to the current state interface. + //******************************************* + ifsm_state& get_state() + { + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + return *p_state; + } + + //******************************************* + /// Gets a const reference to the current state interface. + //******************************************* + const ifsm_state& get_state() const + { + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + return *p_state; + } + + //******************************************* + /// Checks if the FSM has been started. + //******************************************* + bool is_started() const + { + return p_state != nullptr; + } + + //******************************************* + /// Reset the FSM to pre-started state. + //******************************************* + void reset() + { + p_state = nullptr; + } + + private: + + struct CompareStateId + { + bool operator()(etl::ifsm_state* lhs, etl::ifsm_state* rhs) + { + return lhs->get_state_id() < rhs->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. + }; + + //*************************************************************************** + // To generate to header file, run this at the command line. + // Note: You will need Python and COG installed. + // + // python -m cogapp -d -e -ofsm.h -DHandlers= fsm_generator.h + // Where is the number of messages to support. + // + // e.g. + // To generate handlers for up to 16 events... + // python -m cogapp -d -e -ofsm.h -DHandlers=16 fsm_generator.h + // + // See CreateFSM.bat + //*************************************************************************** + + //*************************************************************************** + // The code below has been auto generated. Do not manually edit. + //*************************************************************************** + + //*************************************************************************** + // The definition for all 16 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + + friend class fsm; + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T8::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T9::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T10::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T11::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T12::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T13::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T14::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T15::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T16::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 15 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T8::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T9::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T10::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T11::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T12::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T13::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T14::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T15::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 14 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T8::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T9::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T10::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T11::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T12::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T13::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T14::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 13 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T8::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T9::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T10::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T11::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T12::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T13::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 12 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T8::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T9::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T10::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T11::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T12::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 11 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T8::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T9::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T10::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T11::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 10 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T8::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T9::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T10::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 9 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T8::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T9::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 8 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T8::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 7 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T7::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 6 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T6::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 5 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T5::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 4 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T4::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 3 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T3::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 2 message types. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + case T2::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; + + //*************************************************************************** + // Specialisation for 1 message type. + //*************************************************************************** + template + class fsm_state : public ifsm_state + { + public: + friend class fsm; + + + enum + { + STATE_ID = STATE_ID_ + }; + + fsm_state() + : ifsm_state(STATE_ID) + { + } + + etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t new_state_id; + etl::fsm_event_id_t event_id = message.get_message_id(); + + switch (event_id) + { + case T1::ID: new_state_id = static_cast(this)->on_event(source, static_cast(message)); break; + default: new_state_id = static_cast(this)->on_event_unknown(source, message); break; + } + + return new_state_id; + } + }; +} + +#undef ETL_FILE + +#endif \ No newline at end of file diff --git a/src/fsm_generator.h b/src/fsm_generator.h new file mode 100644 index 00000000..624bc856 --- /dev/null +++ b/src/fsm_generator.h @@ -0,0 +1,428 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +http://www.etlcpp.com + +Copyright(c) 2017 jwellbelove + +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_FSM__ +#define __ETL_FSM__ + +#include + +#include "array.h" +#include "nullptr.h" +#include "error_handler.h" +#include "exception.h" +#include "user_type.h" +#include "message_router.h" + +#undef ETL_FILE +#define ETL_FILE "34" + +namespace etl +{ + /// Allow alternative type for state id. +#if !defined(ETL_FSM_STATE_ID_TYPE) + typedef uint_least8_t fsm_state_id_t; +#else + typedef ETL_FSM_STATE_ID_TYPE fsm_state_id_t; +#endif + + typedef etl::imessage::id_t fsm_event_id_t; + + //*************************************************************************** + /// Base exception class for FSM. + //*************************************************************************** + class fsm_exception : public etl::exception + { + public: + + fsm_exception(string_type what, string_type file_name, numeric_type line_number) + : etl::exception(what, file_name, line_number) + { + } + }; + + //*************************************************************************** + /// Exception for null state pointer. + //*************************************************************************** + class fsm_null_state_exception : public etl::fsm_exception + { + public: + + fsm_null_state_exception(string_type file_name, numeric_type line_number) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:null state", ETL_FILE"A"), file_name, line_number) + { + } + }; + + //*************************************************************************** + /// Exception for invalid state id. + //*************************************************************************** + class fsm_state_id_exception : public etl::fsm_exception + { + public: + + fsm_state_id_exception(string_type file_name, numeric_type line_number) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:state id", ETL_FILE"B"), file_name, line_number) + { + } + }; + + //*************************************************************************** + /// Exception for incompatible state list. + //*************************************************************************** + class fsm_state_list_exception : public etl::fsm_exception + { + public: + + fsm_state_list_exception(string_type file_name, numeric_type line_number) + : etl::fsm_exception(ETL_ERROR_TEXT("fsm:state list", ETL_FILE"C"), file_name, line_number) + { + } + }; + + //*************************************************************************** + /// Interface class for FSM states. + //*************************************************************************** + class ifsm_state + { + public: + + friend class fsm; + + //******************************************* + /// Gets the id for this state. + //******************************************* + etl::fsm_state_id_t get_state_id() const + { + return state_id; + } + + protected: + + virtual fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message) = 0; + + //******************************************* + /// Constructor. + //******************************************* + ifsm_state(etl::fsm_state_id_t state_id_) + : state_id(state_id_) + { + } + + virtual void on_enter_state() {}; // By default, do nothing. + virtual void on_exit_state() {}; // By default, do nothing. + + private: + + // The state id. + const etl::fsm_state_id_t state_id; + + // Disabled. + ifsm_state(const ifsm_state&); + ifsm_state& operator =(const ifsm_state&); + }; + + //*************************************************************************** + class fsm : public etl::imessage_router + { + public: + + //******************************************* + /// Constructor. + //******************************************* + fsm() + : p_state(nullptr) + { + } + + //******************************************* + /// Set the states for the FSM + //******************************************* + template + void set_states(etl::ifsm_state** p_states, TSize size) + { + state_list = p_states; + number_of_states = etl::fsm_state_id_t(size); + + for (etl::fsm_state_id_t i = 0; i < size; ++i) + { + ETL_ASSERT((state_list[i] != nullptr), ETL_ERROR(etl::fsm_null_state_exception)); + } + + bool ok = (number_of_states > 0) && + etl::is_sorted(state_list, state_list + number_of_states, fsm::CompareStateId()); + + ETL_ASSERT(ok, ETL_ERROR(etl::fsm_state_list_exception)); + } + + //******************************************* + /// Starts the FSM. + /// Can only be called once. + /// Subsequent calls will do nothing. + //******************************************* + void start() + { + // Can only be started once. + if (p_state == nullptr) + { + p_state = state_list[0]; + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + + p_state->on_enter_state(); + } + } + + //******************************************* + /// Top level message handlers for the FSM. + //******************************************* + void receive(const etl::imessage& message) + { + receive(etl::null_message_router(), message); + } + + void receive(etl::imessage_router& source, const etl::imessage& message) + { + etl::fsm_state_id_t next_state_id = p_state->process_event(source, message); + + ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception)); + + // Have we changed state? + if (next_state_id != p_state->get_state_id()) + { + p_state->on_exit_state(); + + p_state = state_list[next_state_id]; + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + + p_state->on_enter_state(); + } + } + + //******************************************* + /// Gets the current state id. + //******************************************* + etl::fsm_state_id_t get_state_id() const + { + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + return p_state->get_state_id(); + } + + //******************************************* + /// Gets a reference to the current state interface. + //******************************************* + ifsm_state& get_state() + { + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + return *p_state; + } + + //******************************************* + /// Gets a const reference to the current state interface. + //******************************************* + const ifsm_state& get_state() const + { + ETL_ASSERT(p_state != nullptr, ETL_ERROR(etl::fsm_null_state_exception)); + return *p_state; + } + + //******************************************* + /// Checks if the FSM has been started. + //******************************************* + bool is_started() const + { + return p_state != nullptr; + } + + //******************************************* + /// Reset the FSM to pre-started state. + //******************************************* + void reset() + { + p_state = nullptr; + } + + private: + + struct CompareStateId + { + bool operator()(etl::ifsm_state* lhs, etl::ifsm_state* rhs) + { + return lhs->get_state_id() < rhs->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. + }; + + //*************************************************************************** + // To generate to header file, run this at the command line. + // Note: You will need Python and COG installed. + // + // python -m cogapp -d -e -ofsm.h -DHandlers= fsm_generator.h + // Where is the number of messages to support. + // + // e.g. + // To generate handlers for up to 16 events... + // python -m cogapp -d -e -ofsm.h -DHandlers=16 fsm_generator.h + // + // See CreateFSM.bat + //*************************************************************************** + + /*[[[cog + import cog + cog.outl("//***************************************************************************") + cog.outl("// The code below has been auto generated. Do not manually edit.") + cog.outl("//***************************************************************************") + cog.outl("") + ################################################ + # The first definition for all of the events. + ################################################ + cog.outl("//***************************************************************************") + cog.outl("// The definition for all %s message types." % Handlers) + cog.outl("//***************************************************************************") + cog.outl("template " % Handlers) + cog.outl("class fsm_state : public ifsm_state") + cog.outl("{") + cog.outl("public:") + cog.outl("") + + cog.outl(" friend class fsm;") + cog.outl("") + + cog.outl(" enum") + cog.outl(" {") + cog.outl(" STATE_ID = STATE_ID_") + cog.outl(" };") + cog.outl("") + cog.outl(" fsm_state()") + cog.outl(" : ifsm_state(STATE_ID)") + cog.outl(" {") + cog.outl(" }") + cog.outl("") + cog.outl(" etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message)") + cog.outl(" {") + cog.outl(" etl::fsm_state_id_t new_state_id;") + cog.outl(" etl::fsm_event_id_t event_id = message.get_message_id();") + cog.outl("") + cog.outl(" switch (event_id)") + cog.outl(" {") + for n in range(1, int(Handlers) + 1): + cog.out(" case T%d::ID:" % n) + cog.out(" new_state_id = static_cast(this)->on_event(source, static_cast(message));" % n) + cog.outl(" break;") + cog.out(" default:") + cog.out(" new_state_id = static_cast(this)->on_event_unknown(source, message);") + cog.outl(" break;") + cog.outl(" }") + cog.outl("") + cog.outl(" return new_state_id;") + cog.outl(" }") + cog.outl("};") + + #################################### + # All of the other specialisations. + #################################### + for n in range(int(Handlers) - 1, 0, -1): + cog.outl("") + cog.outl("//***************************************************************************") + if n == 1: + cog.outl("// Specialisation for %d message type." % n) + else: + cog.outl("// Specialisation for %d message types." % n) + cog.outl("//***************************************************************************") + cog.outl("template " % n) + cog.out("class fsm_state : public ifsm_state") + cog.outl("{") + cog.outl("public:") + + cog.outl(" friend class fsm;") + cog.outl("") + + cog.outl("") + cog.outl(" enum") + cog.outl(" {") + cog.outl(" STATE_ID = STATE_ID_") + cog.outl(" };") + cog.outl("") + cog.outl(" fsm_state()") + cog.outl(" : ifsm_state(STATE_ID)") + cog.outl(" {") + cog.outl(" }") + cog.outl("") + cog.outl(" etl::fsm_state_id_t process_event(etl::imessage_router& source, const etl::imessage& message)") + cog.outl(" {") + cog.outl(" etl::fsm_state_id_t new_state_id;") + cog.outl(" etl::fsm_event_id_t event_id = message.get_message_id();") + cog.outl("") + cog.outl(" switch (event_id)") + cog.outl(" {") + for n in range(1, n + 1): + cog.out(" case T%d::ID:" % n) + cog.out(" new_state_id = static_cast(this)->on_event(source, static_cast(message));" % n) + cog.outl(" break;") + cog.out(" default:") + cog.out(" new_state_id = static_cast(this)->on_event_unknown(source, message);") + cog.outl(" break;") + cog.outl(" }") + cog.outl("") + cog.outl(" return new_state_id;") + cog.outl(" }") + cog.outl("};") + ]]]*/ + /*[[[end]]]*/ +} + +#undef ETL_FILE + +#endif \ No newline at end of file diff --git a/src/message_router.h b/src/message_router.h new file mode 100644 index 00000000..057d7efb --- /dev/null +++ b/src/message_router.h @@ -0,0 +1,715 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +http://www.etlcpp.com + +Copyright(c) 2017 jwellbelove + +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_MESSAGE_ROUTER__ +#define __ETL_MESSAGE_ROUTER__ + +#include + +namespace etl +{ + //*************************************************************************** + class imessage + { + public: + + /// Allow alternative type for message id. +#if !defined(ETL_MESSAGE_ID_TYPE) + typedef uint_least8_t id_t; +#else + typedef ETL_MESSAGE_ID_TYPE id_t; +#endif + + virtual ~imessage() {} + virtual id_t get_message_id() const = 0; + }; + + //*************************************************************************** + template + class message : public imessage + { + public: + + enum + { + ID = ID_ + }; + + id_t get_message_id() const + { + return id_t(ID); + } + }; + + //*************************************************************************** + class imessage_router + { + public: + virtual ~imessage_router() {} + virtual void receive(const imessage& message) = 0; + virtual void receive(imessage_router& source, const imessage& message) = 0; + + void send_message(imessage_router& destination, + const imessage& message) + { + destination.receive(*this, message); + } + }; + + //*************************************************************************** + /// This router can be used either as a sink for messages + /// or as a producer-only of messages such an interrupt routine. + //*************************************************************************** + class null_message_router : public imessage_router + { + public: + + void receive(const imessage& message) + { + } + + void receive(imessage_router& source, const imessage& message) + { + } + }; + + //*************************************************************************** + /// Send a message to a router. + /// Sets the 'sender' to etl::null_message_router type. + //*************************************************************************** + inline static void send_message(imessage_router& destination, + const imessage& message) + { + destination.receive(message); + } + + //*************************************************************************** + /// Send a message to a router. + //*************************************************************************** + inline static void send_message(imessage_router& source, + imessage_router& destination, + const imessage& message) + { + destination.receive(source, message); + } + + //*************************************************************************** + // To generate to header file, run this at the command line. + // Note: You will need Python and COG installed. + // + // python -m cogapp -d -e -omessage_router.h -DHandlers= message_router_generator.h + // Where is the number of messages to support. + // + // e.g. + // To generate handlers for up to 16 messages... + // python -m cogapp -d -e -omessage_router.h -DHandlers=16 message_router_generator.h + // + // See CreateMessageProcessor.bat + //*************************************************************************** + + //*************************************************************************** + // The code below has been auto generated. Do not manually edit. + //*************************************************************************** + + //*************************************************************************** + // The definition for all 16 message types. + //*************************************************************************** + template + class message_router : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const id_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T8::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T9::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T10::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T11::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T12::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T13::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T14::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T15::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T16::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 15 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T8::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T9::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T10::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T11::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T12::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T13::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T14::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T15::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 14 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T8::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T9::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T10::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T11::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T12::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T13::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T14::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 13 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T8::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T9::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T10::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T11::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T12::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T13::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 12 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T8::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T9::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T10::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T11::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T12::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 11 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T8::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T9::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T10::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T11::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 10 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T8::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T9::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T10::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 9 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T8::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T9::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 8 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T8::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 7 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T7::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 6 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T6::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 5 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T5::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 4 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T4::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 3 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T3::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 2 message types. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + case T2::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; + + //*************************************************************************** + // Specialisation for 1 message type. + //*************************************************************************** + template + class message_router + : public imessage_router + { + public: + + void receive(const imessage& msg) + { + receive(etl::null_message_router(), msg); + } + + void receive(imessage_router& source, const imessage& msg) + { + const size_t id = msg.get_message_id(); + + switch (id) + { + case T1::ID: static_cast(this)->on_receive(source, static_cast(msg)); break; + default: static_cast(this)->on_receive_unknown(source, msg); break; + } + } + }; +} + +#endif diff --git a/src/message_router_generator.h b/src/message_router_generator.h new file mode 100644 index 00000000..85fa2021 --- /dev/null +++ b/src/message_router_generator.h @@ -0,0 +1,243 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +http://www.etlcpp.com + +Copyright(c) 2017 jwellbelove + +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_MESSAGE_ROUTER__ +#define __ETL_MESSAGE_ROUTER__ + +#include + +namespace etl +{ + //*************************************************************************** + class imessage + { + public: + + /// Allow alternative type for message id. +#if !defined(ETL_MESSAGE_ID_TYPE) + typedef uint_least8_t id_t; +#else + typedef ETL_MESSAGE_ID_TYPE id_t; +#endif + + virtual ~imessage() {} + virtual id_t get_message_id() const = 0; + }; + + //*************************************************************************** + template + class message : public imessage + { + public: + + enum + { + ID = ID_ + }; + + id_t get_message_id() const + { + return id_t(ID); + } + }; + + //*************************************************************************** + class imessage_router + { + public: + virtual ~imessage_router() {} + virtual void receive(const imessage& message) = 0; + virtual void receive(imessage_router& source, const imessage& message) = 0; + + void send_message(imessage_router& destination, + const imessage& message) + { + destination.receive(*this, message); + } + }; + + //*************************************************************************** + /// This router can be used either as a sink for messages + /// or as a producer-only of messages such an interrupt routine. + //*************************************************************************** + class null_message_router : public imessage_router + { + public: + + void receive(const imessage& message) + { + } + + void receive(imessage_router& source, const imessage& message) + { + } + }; + + //*************************************************************************** + /// Send a message to a router. + /// Sets the 'sender' to etl::null_message_router type. + //*************************************************************************** + inline static void send_message(imessage_router& destination, + const imessage& message) + { + destination.receive(message); + } + + //*************************************************************************** + /// Send a message to a router. + //*************************************************************************** + inline static void send_message(imessage_router& source, + imessage_router& destination, + const imessage& message) + { + destination.receive(source, message); + } + + //*************************************************************************** + // To generate to header file, run this at the command line. + // Note: You will need Python and COG installed. + // + // python -m cogapp -d -e -omessage_router.h -DHandlers= message_router_generator.h + // Where is the number of messages to support. + // + // e.g. + // To generate handlers for up to 16 messages... + // python -m cogapp -d -e -omessage_router.h -DHandlers=16 message_router_generator.h + // + // See CreateMessageProcessor.bat + //*************************************************************************** + + /*[[[cog + import cog + cog.outl("//***************************************************************************") + cog.outl("// The code below has been auto generated. Do not manually edit.") + cog.outl("//***************************************************************************") + cog.outl("") + ################################################ + # The first definition for all of the messages. + ################################################ + cog.outl("//***************************************************************************") + cog.outl("// The definition for all %s message types." % Handlers) + cog.outl("//***************************************************************************") + cog.outl("template " % Handlers) + cog.out("class message_router") + cog.outl(" : public imessage_router") + cog.outl("{") + cog.outl("public:") + cog.outl("") + cog.outl(" void receive(const imessage& msg)") + cog.outl(" {") + cog.outl(" receive(etl::null_message_router(), msg);") + cog.outl(" }") + cog.outl("") + cog.outl(" void receive(imessage_router& source, const imessage& msg)") + cog.outl(" {") + cog.outl(" const id_t id = msg.get_message_id();") + cog.outl("") + cog.outl(" switch (id)") + cog.outl(" {") + for n in range(1, int(Handlers) + 1): + cog.out(" case T%d::ID:" % n) + cog.out(" static_cast(this)->on_receive(source, static_cast(msg));" % n) + cog.outl(" break;") + cog.out(" default:") + cog.out(" static_cast(this)->on_receive_unknown(source, msg);") + cog.outl(" break;") + cog.outl(" }") + cog.outl(" }") + cog.outl("};") + + #################################### + # All of the other specialisations. + #################################### + for n in range(int(Handlers) - 1, 0, -1): + cog.outl("") + cog.outl("//***************************************************************************") + if n == 1: + cog.outl("// Specialisation for %d message type." % n) + else: + cog.outl("// Specialisation for %d message types." % n) + cog.outl("//***************************************************************************") + cog.outl("template " % n) + cog.out("class message_router") + cog.outl(" : public imessage_router") + cog.outl("{") + cog.outl("public:") + cog.outl("") + cog.outl(" void receive(const imessage& msg)") + cog.outl(" {") + cog.outl(" receive(etl::null_message_router(), msg);") + cog.outl(" }") + cog.outl("") + cog.outl(" void receive(imessage_router& source, const imessage& msg)") + cog.outl(" {") + cog.outl(" const size_t id = msg.get_message_id();") + cog.outl("") + cog.outl(" switch (id)") + cog.outl(" {") + for t in range(1, n + 1): + cog.out(" case T%d::ID:" % t) + cog.out(" static_cast(this)->on_receive(source, static_cast(msg));" % t) + cog.outl(" break;") + cog.out(" default:") + cog.out(" static_cast(this)->on_receive_unknown(source, msg);") + cog.outl(" break;") + cog.outl(" }") + cog.outl(" }") + cog.outl("};") + ]]]*/ + /*[[[end]]]*/ +} + +#endif diff --git a/test/test_fsm.cpp b/test/test_fsm.cpp new file mode 100644 index 00000000..2c2420b6 --- /dev/null +++ b/test/test_fsm.cpp @@ -0,0 +1,498 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2017 jwellbelove + +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 "UnitTest++.h" + +#include "fsm.h" +#include "enum_type.h" +#include "container.h" + +#include + +namespace +{ + //*************************************************************************** + // Events + struct EventId + { + enum enum_type + { + START, + STOP, + STOPPED, + SET_SPEED + }; + + ETL_DECLARE_ENUM_TYPE(EventId, etl::fsm_event_id_t) + ETL_ENUM_TYPE(START, "Start") + ETL_ENUM_TYPE(STOP, "Stop") + ETL_ENUM_TYPE(STOPPED, "Stopped") + ETL_ENUM_TYPE(SET_SPEED, "Set Speed") + ETL_END_ENUM_TYPE + }; + + //*********************************** + class Start : public etl::message + { + }; + + //*********************************** + class Stop : public etl::message + { + public: + + Stop() : isEmergencyStop(false) {} + Stop(bool emergency) : isEmergencyStop(emergency) {} + + const bool isEmergencyStop; + }; + + //*********************************** + class SetSpeed : public etl::message + { + public: + + SetSpeed(int speed) : speed(speed) {} + + const int speed; + }; + + //*********************************** + class Stopped : public etl::message + { + }; + + //*************************************************************************** + // States + struct StateId + { + enum enum_type + { + IDLE, + RUNNING, + WINDING_DOWN, + 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_DOWN, "Winding Down") + ETL_END_ENUM_TYPE + }; + + class MotorControl; + + //*********************************** + // Common functionality + //*********************************** + class Common + { + public: + + //*********************************** + Common() + { + ClearStatistics(); + } + + //*********************************** + void ClearStatistics() + { + startCount = 0; + stopCount = 0; + setSpeedCount = 0; + unknownCount = 0; + stoppedCount = 0; + isLampOn = false; + speed = 0; + } + + //*********************************** + void SetSpeed(int speed_) + { + speed = speed_; + } + + //*********************************** + void TurnRunningLampOn() + { + isLampOn = true; + } + + //*********************************** + void TurnRunningLampOff() + { + isLampOn = false; + } + + int startCount; + int stopCount; + int setSpeedCount; + int unknownCount; + int stoppedCount; + bool isLampOn; + int speed; + }; + + //*********************************** + // The idle state. + //*********************************** + class Idle : public etl::fsm_state + { + public: + + //*********************************** + Idle(Common& common) + : common(common) + { + } + + //*********************************** + etl::fsm_state_id_t on_event(etl::imessage_router& sender, const Start& event) + { + ++common.startCount; + return StateId::RUNNING; + } + + //*********************************** + etl::fsm_state_id_t on_event_unknown(etl::imessage_router& sender, const etl::imessage& event) + { + ++common.unknownCount; + return STATE_ID; + } + + //*********************************** + void on_enter_state() + { + common.TurnRunningLampOff(); + } + + Common& common; + }; + + //*********************************** + // The running state. + //*********************************** + class Running : public etl::fsm_state + { + public: + + //*********************************** + Running(Common& common) + : common(common) + { + } + + //*********************************** + etl::fsm_state_id_t on_event(etl::imessage_router& sender, const Stop& event) + { + ++common.stopCount; + + if (event.isEmergencyStop) + { + return StateId::IDLE; + } + else + { + return StateId::WINDING_DOWN; + } + } + + //*********************************** + etl::fsm_state_id_t on_event(etl::imessage_router& sender, const SetSpeed& event) + { + ++common.setSpeedCount; + common.SetSpeed(event.speed); + return STATE_ID; + } + + //*********************************** + etl::fsm_state_id_t on_event_unknown(etl::imessage_router& sender, const etl::imessage& event) + { + ++common.unknownCount; + return STATE_ID; + } + + void on_enter_state() + { + common.TurnRunningLampOn(); + } + + Common& common; + }; + + //*********************************** + // The winding down state. + //*********************************** + class WindingDown : public etl::fsm_state + { + public: + + //*********************************** + WindingDown(Common& common) + : common(common) + { + } + + //*********************************** + etl::fsm_state_id_t on_event(etl::imessage_router& source, const Stopped& event) + { + ++common.stoppedCount; + return StateId::IDLE; + } + + //*********************************** + etl::fsm_state_id_t on_event_unknown(etl::imessage_router& source, const etl::imessage& event) + { + ++common.unknownCount; + return STATE_ID; + } + + Common& common; + }; + + //*********************************** + // The motor control FSM. + //*********************************** + class MotorControl : public etl::fsm + { + public: + + MotorControl() + : idle(common), + running(common), + windingDown(common) + { + set_states(stateList, etl::size(stateList)); + } + + Common common; + + private: + + // The states. + Idle idle; + Running running; + WindingDown windingDown; + + etl::ifsm_state* stateList[StateId::NUMBER_OF_STATES] = + { + &idle, &running, &windingDown + }; + }; + + MotorControl motorControl; + + SUITE(test_map) + { + //************************************************************************* + TEST(test_fsm) + { + motorControl.reset(); + motorControl.common.ClearStatistics(); + + CHECK(!motorControl.is_started()); + + // Start the FSM. + motorControl.start(); + 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.common.isLampOn); + CHECK_EQUAL(0, motorControl.common.setSpeedCount); + CHECK_EQUAL(0, motorControl.common.speed); + CHECK_EQUAL(0, motorControl.common.startCount); + CHECK_EQUAL(0, motorControl.common.stopCount); + CHECK_EQUAL(0, motorControl.common.stoppedCount); + CHECK_EQUAL(0, motorControl.common.unknownCount); + + // Send unhandled events. + motorControl.receive(etl::null_message_router(), Stop()); + motorControl.receive(etl::null_message_router(), Stopped()); + motorControl.receive(etl::null_message_router(), SetSpeed(10)); + + CHECK_EQUAL(StateId::IDLE, motorControl.get_state_id()); + CHECK_EQUAL(StateId::IDLE, motorControl.get_state().get_state_id()); + + CHECK_EQUAL(false, motorControl.common.isLampOn); + CHECK_EQUAL(0, motorControl.common.setSpeedCount); + CHECK_EQUAL(0, motorControl.common.speed); + CHECK_EQUAL(0, motorControl.common.startCount); + CHECK_EQUAL(0, motorControl.common.stopCount); + CHECK_EQUAL(0, motorControl.common.stoppedCount); + CHECK_EQUAL(3, motorControl.common.unknownCount); + + // Send Start event. + motorControl.receive(etl::null_message_router(), 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())); + + CHECK_EQUAL(true, motorControl.common.isLampOn); + CHECK_EQUAL(0, motorControl.common.setSpeedCount); + CHECK_EQUAL(0, motorControl.common.speed); + CHECK_EQUAL(1, motorControl.common.startCount); + CHECK_EQUAL(0, motorControl.common.stopCount); + CHECK_EQUAL(0, motorControl.common.stoppedCount); + CHECK_EQUAL(3, motorControl.common.unknownCount); + + // Send unhandled events. + motorControl.receive(etl::null_message_router(), Start()); + motorControl.receive(etl::null_message_router(), Stopped()); + + CHECK_EQUAL(StateId::RUNNING, int(motorControl.get_state_id())); + CHECK_EQUAL(StateId::RUNNING, int(motorControl.get_state().get_state_id())); + + CHECK_EQUAL(true, motorControl.common.isLampOn); + CHECK_EQUAL(0, motorControl.common.setSpeedCount); + CHECK_EQUAL(0, motorControl.common.speed); + CHECK_EQUAL(1, motorControl.common.startCount); + CHECK_EQUAL(0, motorControl.common.stopCount); + CHECK_EQUAL(0, motorControl.common.stoppedCount); + CHECK_EQUAL(5, motorControl.common.unknownCount); + + // Send SetSpeed event. + motorControl.receive(etl::null_message_router(), 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())); + + CHECK_EQUAL(true, motorControl.common.isLampOn); + CHECK_EQUAL(1, motorControl.common.setSpeedCount); + CHECK_EQUAL(100, motorControl.common.speed); + CHECK_EQUAL(1, motorControl.common.startCount); + CHECK_EQUAL(0, motorControl.common.stopCount); + CHECK_EQUAL(0, motorControl.common.stoppedCount); + CHECK_EQUAL(5, motorControl.common.unknownCount); + + // Send Stop event. + motorControl.receive(etl::null_message_router(), 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.common.isLampOn); + CHECK_EQUAL(1, motorControl.common.setSpeedCount); + CHECK_EQUAL(100, motorControl.common.speed); + CHECK_EQUAL(1, motorControl.common.startCount); + CHECK_EQUAL(1, motorControl.common.stopCount); + CHECK_EQUAL(0, motorControl.common.stoppedCount); + CHECK_EQUAL(5, motorControl.common.unknownCount); + + // Send unhandled events. + motorControl.receive(etl::null_message_router(), Start()); + motorControl.receive(etl::null_message_router(), Stop()); + motorControl.receive(etl::null_message_router(), 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.common.isLampOn); + CHECK_EQUAL(1, motorControl.common.setSpeedCount); + CHECK_EQUAL(100, motorControl.common.speed); + CHECK_EQUAL(1, motorControl.common.startCount); + CHECK_EQUAL(1, motorControl.common.stopCount); + CHECK_EQUAL(0, motorControl.common.stoppedCount); + CHECK_EQUAL(8, motorControl.common.unknownCount); + + // Send Stopped event. + motorControl.receive(etl::null_message_router(), 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.common.isLampOn); + CHECK_EQUAL(1, motorControl.common.setSpeedCount); + CHECK_EQUAL(100, motorControl.common.speed); + CHECK_EQUAL(1, motorControl.common.startCount); + CHECK_EQUAL(1, motorControl.common.stopCount); + CHECK_EQUAL(1, motorControl.common.stoppedCount); + CHECK_EQUAL(8, motorControl.common.unknownCount); + } + + //************************************************************************* + TEST(test_fsm_emergency_stop) + { + motorControl.reset(); + motorControl.common.ClearStatistics(); + + CHECK(!motorControl.is_started()); + + // Start the FSM. + motorControl.start(); + CHECK(motorControl.is_started()); + + // Now in Idle state. + + // Send Start event. + motorControl.receive(etl::null_message_router(), 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())); + + CHECK_EQUAL(true, motorControl.common.isLampOn); + CHECK_EQUAL(0, motorControl.common.setSpeedCount); + CHECK_EQUAL(0, motorControl.common.speed); + CHECK_EQUAL(1, motorControl.common.startCount); + CHECK_EQUAL(0, motorControl.common.stopCount); + CHECK_EQUAL(0, motorControl.common.stoppedCount); + CHECK_EQUAL(0, motorControl.common.unknownCount); + + // Send emergency Stop event. + motorControl.receive(etl::null_message_router(), Stop(true)); + + // 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.common.isLampOn); + CHECK_EQUAL(0, motorControl.common.setSpeedCount); + CHECK_EQUAL(0, motorControl.common.speed); + CHECK_EQUAL(1, motorControl.common.startCount); + CHECK_EQUAL(1, motorControl.common.stopCount); + CHECK_EQUAL(0, motorControl.common.stoppedCount); + CHECK_EQUAL(0, motorControl.common.unknownCount); + } + }; +} diff --git a/test/test_message_router.cpp b/test/test_message_router.cpp new file mode 100644 index 00000000..21d36330 --- /dev/null +++ b/test/test_message_router.cpp @@ -0,0 +1,325 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +http://www.etlcpp.com + +Copyright(c) 2017 jwellbelove + +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 "UnitTest++.h" +#include "ExtraCheckMacros.h" + +#include "message_router.h" + +//*************************************************************************** +// The set of messages. +//*************************************************************************** +namespace +{ + enum + { + MESSAGE1, + MESSAGE2, + MESSAGE3, + MESSAGE4, + MESSAGE5 + }; + + struct Message1 : public etl::message + { + + }; + + struct Message2 : public etl::message + { + + }; + + struct Message3 : public etl::message + { + + }; + + struct Message4 : public etl::message + { + + }; + + struct Message5 : public etl::message + { + + }; + + Message1 message1; + Message2 message2; + Message3 message3; + Message4 message4; + Message5 message5; + + //*************************************************************************** + // Router that handles messages 1, 2, 3, 4 and 5 and returns nothing. + //*************************************************************************** + class Router1 : public etl::message_router + { + public: + + Router1() + : message1_count(0), + message2_count(0), + message3_count(0), + message4_count(0), + message_unknown_count(0), + callback_count(0) + { + + } + + void on_receive(etl::imessage_router& sender, const Message1& msg) + { + ++message1_count; + etl::send_message(sender, message5); + } + + void on_receive(etl::imessage_router& sender, const Message2& msg) + { + ++message2_count; + etl::send_message(sender, message5); + } + + void on_receive(etl::imessage_router& sender, const Message3& msg) + { + ++message3_count; + etl::send_message(sender, message5); + } + + void on_receive(etl::imessage_router& sender, const Message4& msg) + { + ++message4_count; + etl::send_message(sender, message5); + } + + void on_receive(etl::imessage_router& sender, const Message5& msg) + { + ++callback_count; + } + + void on_receive_unknown(etl::imessage_router& sender, const etl::imessage& msg) + { + ++message_unknown_count; + } + + int message1_count; + int message2_count; + int message3_count; + int message4_count; + int message_unknown_count; + int callback_count; + }; + + //*************************************************************************** + // Router that handles messages 1, 2, 4 and 5 and returns nothing. + //*************************************************************************** + class Router2 : public etl::message_router + { + public: + + Router2() + : message1_count(0), + message2_count(0), + message4_count(0), + message_unknown_count(0), + callback_count(0) + { + + } + + void on_receive(etl::imessage_router& sender, const Message1& msg) + { + ++message1_count; + etl::send_message(sender, message5); + } + + void on_receive(etl::imessage_router& sender, const Message2& msg) + { + ++message2_count; + etl::send_message(sender, message5); + } + + void on_receive(etl::imessage_router& sender, const Message4& msg) + { + ++message4_count; + etl::send_message(sender, message5); + } + + void on_receive(etl::imessage_router& sender, const Message5& msg) + { + ++callback_count; + } + + void on_receive_unknown(etl::imessage_router& sender, const etl::imessage& msg) + { + ++message_unknown_count; + etl::send_message(sender, message5); + } + + int message1_count; + int message2_count; + int message4_count; + int message_unknown_count; + int callback_count; + }; + + + etl::imessage_router* p_router; + + SUITE(test_message_router) + { + //========================================================================= + TEST(message_router) + { + Router1 r1; + Router2 r2; + + p_router = &r1; + + p_router->receive(r2, message1); + CHECK_EQUAL(1, r1.message1_count); + CHECK_EQUAL(0, r1.message2_count); + CHECK_EQUAL(0, r1.message3_count); + CHECK_EQUAL(0, r1.message4_count); + CHECK_EQUAL(0, r1.message_unknown_count); + CHECK_EQUAL(1, r2.callback_count); + + p_router->receive(r2, message2); + CHECK_EQUAL(1, r1.message1_count); + CHECK_EQUAL(1, r1.message2_count); + CHECK_EQUAL(0, r1.message3_count); + CHECK_EQUAL(0, r1.message4_count); + CHECK_EQUAL(0, r1.message_unknown_count); + CHECK_EQUAL(2, r2.callback_count); + + p_router->receive(r2, message3); + CHECK_EQUAL(1, r1.message1_count); + CHECK_EQUAL(1, r1.message2_count); + CHECK_EQUAL(1, r1.message3_count); + CHECK_EQUAL(0, r1.message4_count); + CHECK_EQUAL(0, r1.message_unknown_count); + CHECK_EQUAL(3, r2.callback_count); + + p_router->receive(r2, message4); + CHECK_EQUAL(1, r1.message1_count); + CHECK_EQUAL(1, r1.message2_count); + CHECK_EQUAL(1, r1.message3_count); + CHECK_EQUAL(1, r1.message4_count); + CHECK_EQUAL(0, r1.message_unknown_count); + CHECK_EQUAL(4, r2.callback_count); + + p_router = &r2; + + p_router->receive(r1, message1); + CHECK_EQUAL(1, r2.message1_count); + CHECK_EQUAL(0, r2.message2_count); + CHECK_EQUAL(0, r2.message4_count); + CHECK_EQUAL(0, r2.message_unknown_count); + CHECK_EQUAL(1, r1.callback_count); + + p_router->receive(r1, message2); + CHECK_EQUAL(1, r2.message1_count); + CHECK_EQUAL(1, r2.message2_count); + CHECK_EQUAL(0, r2.message4_count); + CHECK_EQUAL(0, r2.message_unknown_count); + CHECK_EQUAL(2, r1.callback_count); + + p_router->receive(r1, message3); + CHECK_EQUAL(1, r2.message1_count); + CHECK_EQUAL(1, r2.message2_count); + CHECK_EQUAL(0, r2.message4_count); + CHECK_EQUAL(1, r2.message_unknown_count); + CHECK_EQUAL(3, r1.callback_count); + + p_router->receive(r1, message4); + CHECK_EQUAL(1, r2.message1_count); + CHECK_EQUAL(1, r2.message2_count); + CHECK_EQUAL(1, r2.message4_count); + CHECK_EQUAL(1, r2.message_unknown_count); + CHECK_EQUAL(4, r1.callback_count); + } + + //========================================================================= + TEST(message_null_router) + { + Router2 router; + etl::null_message_router null_router; + + // Send from the null router. + etl::send_message(router, message1); + CHECK_EQUAL(1, router.message1_count); + CHECK_EQUAL(0, router.message2_count); + CHECK_EQUAL(0, router.message4_count); + CHECK_EQUAL(0, router.message_unknown_count); + + etl::send_message(router, message2); + CHECK_EQUAL(1, router.message1_count); + CHECK_EQUAL(1, router.message2_count); + CHECK_EQUAL(0, router.message4_count); + CHECK_EQUAL(0, router.message_unknown_count); + + etl::send_message(router, message3); + CHECK_EQUAL(1, router.message1_count); + CHECK_EQUAL(1, router.message2_count); + CHECK_EQUAL(0, router.message4_count); + CHECK_EQUAL(1, router.message_unknown_count); + + etl::send_message(router, message4); + CHECK_EQUAL(1, router.message1_count); + CHECK_EQUAL(1, router.message2_count); + CHECK_EQUAL(1, router.message4_count); + CHECK_EQUAL(1, router.message_unknown_count); + + // Send to the null router. + etl::send_message(null_router, message1); + CHECK_EQUAL(1, router.message1_count); + CHECK_EQUAL(1, router.message2_count); + CHECK_EQUAL(1, router.message4_count); + CHECK_EQUAL(1, router.message_unknown_count); + + etl::send_message(null_router, message2); + CHECK_EQUAL(1, router.message1_count); + CHECK_EQUAL(1, router.message2_count); + CHECK_EQUAL(1, router.message4_count); + CHECK_EQUAL(1, router.message_unknown_count); + + etl::send_message(null_router, message3); + CHECK_EQUAL(1, router.message1_count); + CHECK_EQUAL(1, router.message2_count); + CHECK_EQUAL(1, router.message4_count); + CHECK_EQUAL(1, router.message_unknown_count); + + etl::send_message(null_router, message4); + CHECK_EQUAL(1, router.message1_count); + CHECK_EQUAL(1, router.message2_count); + CHECK_EQUAL(1, router.message4_count); + CHECK_EQUAL(1, router.message_unknown_count); + } + }; +} diff --git a/test/vs2017/etl.vcxproj b/test/vs2017/etl.vcxproj index 72989d79..736afc28 100644 --- a/test/vs2017/etl.vcxproj +++ b/test/vs2017/etl.vcxproj @@ -216,7 +216,6 @@ - @@ -240,7 +239,6 @@ - diff --git a/test/vs2017/etl.vcxproj.filters b/test/vs2017/etl.vcxproj.filters index 815eb6b3..cd16db12 100644 --- a/test/vs2017/etl.vcxproj.filters +++ b/test/vs2017/etl.vcxproj.filters @@ -38,7 +38,7 @@ {7028012c-30c4-4993-b2d9-3b1521a610ae} - {5de50c3d-4679-4eb3-9b76-e43e1aad6a66} + {6be3bc76-e17c-4be0-8b0b-d1053e1a1761} @@ -261,12 +261,6 @@ ETL\Containers - - ETL\Containers - - - ETL\Containers - ETL\Containers @@ -439,16 +433,16 @@ Header Files - ETL\Patterns + ETL\Frameworks - ETL\Patterns + ETL\Frameworks - ETL\Patterns + ETL\Frameworks - ETL\Patterns + ETL\Frameworks @@ -788,10 +782,10 @@ Resource Files - ETL\Patterns + ETL\Frameworks - ETL\Patterns + ETL\Frameworks