diff --git a/include/etl/platform.h b/include/etl/platform.h index 0034c1cc..cab27d25 100644 --- a/include/etl/platform.h +++ b/include/etl/platform.h @@ -69,7 +69,11 @@ SOFTWARE. #endif #if defined(ETL_COMPILER_GCC) - #define GCC_VERSION ((__GNUC__ * 10000) + (__GNUC_MINOR__ * 100) + __GNUC_PATCHLEVEL__) + #define ETL_COMPILER_VERSION __GNUC__ + #define ETL_COMPILER_FULL_VERSION ((__GNUC__ * 10000) + (__GNUC_MINOR__ * 100) + __GNUC_PATCHLEVEL__) +#elif defined ETL_COMPILER_MICROSOFT + #define ETL_COMPILER_VERSION _MSC_VER + #define ETL_COMPILER_FULL_VERSION _MSC_FULL_VER #endif #if ETL_CPP11_SUPPORTED @@ -84,4 +88,10 @@ SOFTWARE. #define ETL_IF_CONSTEXPR #endif +#if ETL_CPP11_SUPPORTED + #define ETL_DELETE = delete +#else + #define ETL_DELETE +#endif + #endif diff --git a/include/etl/state_chart.h b/include/etl/state_chart.h new file mode 100644 index 00000000..692c6322 --- /dev/null +++ b/include/etl/state_chart.h @@ -0,0 +1,289 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2018 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_STATE_CHART_INCLUDED +#define ETL_STATE_CHART_INCLUDED + +#include + +#include "etl/platform.h" +#include "etl/nullptr.h" +#include "etl/array.h" +#include "etl/array_view.h" + +namespace etl +{ + //*************************************************************************** + /// Simple Finite State Machine Interface + //*************************************************************************** + class istate_chart + { + public: + + typedef uint32_t state_id_t; + typedef uint32_t event_id_t; + + virtual state_id_t get_state_id() const = 0; + virtual void process_event(const event_id_t event_id) = 0; + + virtual ~istate_chart() + { + } + }; + + //*************************************************************************** + /// Simple Finite State Machine + //*************************************************************************** + template + class state_chart : public istate_chart + { + public: + + //************************************************************************* + /// Transition definition + //************************************************************************* + struct transition + { + transition(const event_id_t event_id_, + const state_id_t current_state_id_, + const state_id_t next_state_id_, + void (TObject::* const action_)() = nullptr, + bool (TObject::* const guard_)() = nullptr) + : event_id(event_id_), + current_state_id(current_state_id_), + next_state_id(next_state_id_), + action(action_), + guard(guard_) + { + } + + const event_id_t event_id; + const state_id_t current_state_id; + const state_id_t next_state_id; + void (TObject::* const action)(); + bool (TObject::* const guard)(); + }; + + //************************************************************************* + /// State definition + //************************************************************************* + struct state + { + state(const state_id_t state_id_, + void (TObject::* const on_entry_)() = nullptr, + void (TObject::* const on_exit_)() = nullptr) + : state_id(state_id_), + on_entry(on_entry_), + on_exit(on_exit_) + { + } + + state_id_t state_id; + void (TObject::* const on_entry)(); + void (TObject::* const on_exit)(); + }; + + //************************************************************************* + /// Constructor. + /// \tparam TRANSITION_TABLE_SIZE The transition table size. + /// \param object_ A reference to the implementation object. + /// \param transition_table_ The table of transitions. + /// \param state_id_ The initial state id. + //************************************************************************* + template + state_chart(TObject& object_, + const etl::array& transition_table_, + const state_id_t state_id_) + : object(object_), + current_state_id(state_id_), + transition_table(transition_table_.begin(), transition_table_.end()) + { + } + + //************************************************************************* + /// Sets the state table. + /// \tparam STATE_TABLE_SIZE The state table size. + /// \param state_table_ A reference to the state table. + //************************************************************************* + template + void set_state_table(const etl::array& state_table_) + { + state_table.assign(state_table_.begin(), state_table_.end()); + } + + //************************************************************************* + /// Gets a reference to the implementation object. + /// \return Reference to the implementation object. + //************************************************************************* + TObject& get_object() + { + return object; + } + + //************************************************************************* + /// Gets a const reference to the implementation object. + /// \return Const reference to the implementation object. + //************************************************************************* + const TObject& get_object() const + { + return object; + } + + //************************************************************************* + /// Gets the current state id. + /// \return The current state id. + //************************************************************************* + state_id_t get_state_id() const + { + return current_state_id; + } + + //************************************************************************* + /// Gets the current state id. + /// \return The current state id. + //************************************************************************* + const state* find_state(state_id_t state_id) + { + if (state_table.empty()) + { + return state_table.end(); + } + else + { + return std::find_if(state_table.begin(), + state_table.end(), + is_state(state_id)); + } + } + + //************************************************************************* + /// Processes the specified event. + /// The state machine will action the first item in the transition table + /// that satisfies the conditions for executing the action. + /// \param event_id The id of the event to process. + //************************************************************************* + void process_event(const event_id_t event_id) + { + // Scan the transition table. + const transition* t = std::find_if(transition_table.begin(), + transition_table.end(), + is_transition(event_id, current_state_id)); + + // Found an entry? + if (t != transition_table.end()) + { + // Shall we execute the transition? + if ((t->guard == nullptr) || ((object.*t->guard)())) + { + const bool to_new_state = (current_state_id != t->next_state_id); + + // Execute the state exit if necessary. + if (to_new_state) + { + // See if we have a state item for the current state. + const state* s = find_state(current_state_id); + + // If the current state has an 'on_exit' then call it. + if ((s != state_table.end()) && (s->on_exit != nullptr)) + { + (object.*(s->on_exit))(); + } + } + + // Shall we execute the action? + if (t->action != nullptr) + { + (object.*t->action)(); + } + + // Execute the state entry if necessary. + if (to_new_state) + { + // See if we have a state item for the next state. + const state* s = find_state(t->next_state_id); + + // If the new state has an 'on_entry' then call it. + if ((s != state_table.end()) && (s->on_entry != nullptr)) + { + (object.*(s->on_entry))(); + } + } + + current_state_id = t->next_state_id; + } + } + } + + private: + + //************************************************************************* + struct is_transition + { + is_transition(event_id_t event_id_, state_id_t state_id_) + : event_id(event_id_), + state_id(state_id_) + { + } + + bool operator()(const transition& t) const + { + return (t.event_id == event_id) && (t.current_state_id == state_id); + } + + const event_id_t event_id; + const state_id_t state_id; + }; + + //************************************************************************* + struct is_state + { + is_state(state_id_t state_id_) + : state_id(state_id_) + { + } + + bool operator()(const state& s) const + { + return (s.state_id == state_id); + } + + const state_id_t state_id; + }; + + // Disabled + state_chart(const state_chart&) ETL_DELETE; + state_chart& operator =(const state_chart&) ETL_DELETE; + + TObject& object; ///< The object that supplies guard and action member functions. + state_id_t current_state_id; ///< The current state id. + const etl::array_view transition_table; ///< The table of transitions. + etl::array_view state_table; ///< The table of states. + }; +} + +#endif diff --git a/include/etl/version.h b/include/etl/version.h index 32094197..c5444289 100644 --- a/include/etl/version.h +++ b/include/etl/version.h @@ -37,13 +37,13 @@ SOFTWARE. /// Definitions of the ETL version ///\ingroup utilities -#define ETL_VERSION "11.16.5" -#define ETL_VERSION_W L"11.16.5" -#define ETL_VERSION_U16 u"11.16.5" -#define ETL_VERSION_U32 U"11.16.5" +#define ETL_VERSION "11.17.0" +#define ETL_VERSION_W L"11.17.0" +#define ETL_VERSION_U16 u"11.17.0" +#define ETL_VERSION_U32 U"11.17.0" #define ETL_VERSION_MAJOR 11 -#define ETL_VERSION_MINOR 16 -#define ETL_VERSION_PATCH 5 +#define ETL_VERSION_MINOR 17 +#define ETL_VERSION_PATCH 0 #define ETL_VERSION_VALUE ((ETL_VERSION_MAJOR * 10000) + (ETL_VERSION_MINOR * 100) + ETL_VERSION_PATCH) #endif diff --git a/support/Release notes.txt b/support/Release notes.txt index d3293ecf..69a38502 100644 --- a/support/Release notes.txt +++ b/support/Release notes.txt @@ -1,3 +1,11 @@ +=============================================================================== +11.17.0 +Added etl::state_chart + +=============================================================================== +11.16.6 +Fixed implementations of key_comp and value_comp for maps and sets + =============================================================================== 11.16.5 Added 'ull' suffix to 64bit literals diff --git a/test/codeblocks/ETL.cbp b/test/codeblocks/ETL.cbp index e66e4acd..b4a78c2f 100644 --- a/test/codeblocks/ETL.cbp +++ b/test/codeblocks/ETL.cbp @@ -279,6 +279,7 @@ + @@ -423,6 +424,7 @@ + diff --git a/test/test_state_chart.cpp b/test/test_state_chart.cpp new file mode 100644 index 00000000..a7730ce8 --- /dev/null +++ b/test/test_state_chart.cpp @@ -0,0 +1,387 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2018 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 "etl/state_chart.h" +#include "etl/enum_type.h" +#include "etl/queue.h" +#include "etl/array.h" + +#include + +namespace +{ + //*************************************************************************** + // Events + struct EventId + { + enum enum_type + { + START, + STOP, + EMERGENCY_STOP, + STOPPED, + SET_SPEED + }; + + ETL_DECLARE_ENUM_TYPE(EventId, etl::istate_chart::event_id_t) + ETL_ENUM_TYPE(START, "Start") + ETL_ENUM_TYPE(STOP, "Stop") + ETL_ENUM_TYPE(EMERGENCY_STOP, "Emergency Stop") + ETL_ENUM_TYPE(STOPPED, "Stopped") + ETL_ENUM_TYPE(SET_SPEED, "Set Speed") + ETL_END_ENUM_TYPE + }; + + //*************************************************************************** + // States + struct StateId + { + enum enum_type + { + IDLE, + RUNNING, + WINDING_DOWN, + NUMBER_OF_STATES + }; + + ETL_DECLARE_ENUM_TYPE(StateId, etl::istate_chart::state_id_t) + ETL_ENUM_TYPE(IDLE, "Idle") + ETL_ENUM_TYPE(RUNNING, "Running") + ETL_ENUM_TYPE(WINDING_DOWN, "Winding Down") + ETL_END_ENUM_TYPE + }; + + //*********************************** + // The motor control FSM. + //*********************************** + class MotorControl : public etl::state_chart + { + public: + + MotorControl() + : state_chart(*this, transitionTable, StateId::IDLE) + { + this->set_state_table(stateTable); + ClearStatistics(); + } + + //*********************************** + void ClearStatistics() + { + startCount = 0; + stopCount = 0; + setSpeedCount = 0; + stoppedCount = 0; + isLampOn = false; + speed = 0; + windingDown = 0; + } + + //*********************************** + void OnStart() + { + ++startCount; + } + + //*********************************** + void OnStop() + { + ++stopCount; + } + + //*********************************** + void OnStopped() + { + ++stoppedCount; + } + + //*********************************** + void OnSetSpeed() + { + ++setSpeedCount; + SetSpeedValue(100); + } + + //*********************************** + void OnEnterIdle() + { + TurnRunningLampOff(); + } + + //*********************************** + void OnEnterRunning() + { + TurnRunningLampOn(); + } + + //*********************************** + void OnEnterWindingDown() + { + ++windingDown; + } + + //*********************************** + void OnExitWindingDown() + { + --windingDown; + } + + //*********************************** + void SetSpeedValue(int speed_) + { + speed = speed_; + } + + //*********************************** + bool Guard() + { + return guard; + } + + //*********************************** + void TurnRunningLampOn() + { + isLampOn = true; + } + + //*********************************** + void TurnRunningLampOff() + { + isLampOn = false; + } + + int startCount; + int stopCount; + int setSpeedCount; + int stoppedCount; + bool isLampOn; + int speed; + int windingDown; + + bool guard; + + static const etl::array transitionTable; + static const etl::array stateTable; + }; + + //*************************************************************************** + const etl::array MotorControl::transitionTable = + { + MotorControl::transition(EventId::START, StateId::IDLE, StateId::RUNNING, &MotorControl::OnStart, &MotorControl::Guard), + MotorControl::transition(EventId::STOP, StateId::RUNNING, StateId::WINDING_DOWN, &MotorControl::OnStop), + MotorControl::transition(EventId::STOPPED, StateId::WINDING_DOWN, StateId::IDLE, &MotorControl::OnStopped), + MotorControl::transition(EventId::EMERGENCY_STOP, StateId::RUNNING, StateId::IDLE, &MotorControl::OnStop), + MotorControl::transition(EventId::SET_SPEED, StateId::RUNNING, StateId::RUNNING, &MotorControl::OnSetSpeed) + }; + + //*************************************************************************** + const etl::array MotorControl::stateTable = + { + MotorControl::state(StateId::IDLE, &MotorControl::OnEnterIdle, nullptr), + MotorControl::state(StateId::RUNNING, &MotorControl::OnEnterRunning, nullptr), + MotorControl::state(StateId::WINDING_DOWN, &MotorControl::OnEnterWindingDown, &MotorControl::OnExitWindingDown) + }; + + MotorControl motorControl; + + SUITE(test_state_chart_class) + { + //************************************************************************* + TEST(test_state_chart) + { + motorControl.ClearStatistics(); + + // Now in Idle state. + CHECK_EQUAL(StateId::IDLE, int(motorControl.get_state_id())); + + CHECK_EQUAL(false, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(0, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.windingDown); + + // Send unhandled events. + motorControl.process_event(EventId::STOP); + motorControl.process_event(EventId::STOPPED); + + CHECK_EQUAL(StateId::IDLE, motorControl.get_state_id()); + + CHECK_EQUAL(false, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(0, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + + // Send Start event. + motorControl.guard = false; + motorControl.process_event(EventId::START); + + // Still in Idle state. + + CHECK_EQUAL(StateId::IDLE, int(motorControl.get_state_id())); + + CHECK_EQUAL(false, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(0, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.windingDown); + + // Send Start event. + motorControl.guard = true; + motorControl.process_event(EventId::START); + + // Now in Running state. + + CHECK_EQUAL(StateId::RUNNING, int(motorControl.get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.windingDown); + + // Send unhandled events. + motorControl.process_event(EventId::START); + motorControl.process_event(EventId::STOPPED); + + CHECK_EQUAL(StateId::RUNNING, int(motorControl.get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.windingDown); + + // Send SetSpeed event. + motorControl.process_event(EventId::SET_SPEED); + + // Still in Running state. + + CHECK_EQUAL(StateId::RUNNING, int(motorControl.get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(1, motorControl.setSpeedCount); + CHECK_EQUAL(100, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.windingDown); + + // Send Stop event. + motorControl.process_event(EventId::STOP); + + // Now in WindingDown state. + + CHECK_EQUAL(StateId::WINDING_DOWN, int(motorControl.get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(1, motorControl.setSpeedCount); + CHECK_EQUAL(100, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(1, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(1, motorControl.windingDown); + + // Send unhandled events. + motorControl.process_event(EventId::START); + motorControl.process_event(EventId::STOP); + + CHECK_EQUAL(StateId::WINDING_DOWN, int(motorControl.get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(1, motorControl.setSpeedCount); + CHECK_EQUAL(100, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(1, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(1, motorControl.windingDown); + + // Send Stopped event. + motorControl.process_event(EventId::STOPPED); + + // Now in Idle state. + CHECK_EQUAL(StateId::IDLE, int(motorControl.get_state_id())); + + CHECK_EQUAL(false, motorControl.isLampOn); + CHECK_EQUAL(1, motorControl.setSpeedCount); + CHECK_EQUAL(100, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(1, motorControl.stopCount); + CHECK_EQUAL(1, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.windingDown); + } + + //************************************************************************* + TEST(test_fsm_emergency_stop) + { + motorControl.ClearStatistics(); + + // Now in Idle state. + + // Send Start event. + motorControl.process_event(EventId::START); + + // Now in Running state. + + CHECK_EQUAL(StateId::RUNNING, int(motorControl.get_state_id())); + + CHECK_EQUAL(true, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(0, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.windingDown); + + // Send emergency Stop event. + motorControl.process_event(EventId::EMERGENCY_STOP); + + // Now in Idle state. + CHECK_EQUAL(StateId::IDLE, int(motorControl.get_state_id())); + + CHECK_EQUAL(false, motorControl.isLampOn); + CHECK_EQUAL(0, motorControl.setSpeedCount); + CHECK_EQUAL(0, motorControl.speed); + CHECK_EQUAL(1, motorControl.startCount); + CHECK_EQUAL(1, motorControl.stopCount); + CHECK_EQUAL(0, motorControl.stoppedCount); + CHECK_EQUAL(0, motorControl.windingDown); + } + }; +} diff --git a/test/vs2017/cpp.hint b/test/vs2017/cpp.hint new file mode 100644 index 00000000..82067cca --- /dev/null +++ b/test/vs2017/cpp.hint @@ -0,0 +1,8 @@ +// Hint files help the Visual Studio IDE interpret Visual C++ identifiers +// such as names of functions and macros. +// For more information see https://go.microsoft.com/fwlink/?linkid=865984 +#define ETL_CONSTEXPR +#define ETL_CONSTEXPR_IF +#define ETL_DELETE + + diff --git a/test/vs2017/etl.vcxproj b/test/vs2017/etl.vcxproj index 7c97660e..fd081b11 100644 --- a/test/vs2017/etl.vcxproj +++ b/test/vs2017/etl.vcxproj @@ -371,6 +371,7 @@ + @@ -728,6 +729,7 @@ true true + @@ -768,6 +770,7 @@ + diff --git a/test/vs2017/etl.vcxproj.filters b/test/vs2017/etl.vcxproj.filters index b4225aba..32db94a6 100644 --- a/test/vs2017/etl.vcxproj.filters +++ b/test/vs2017/etl.vcxproj.filters @@ -693,6 +693,9 @@ ETL\Utilities\Atomic + + ETL\Frameworks + @@ -1136,6 +1139,9 @@ Source Files + + Source Files + @@ -1174,6 +1180,7 @@ Resource Files\Generators +