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