In hierarchical state machines states are organized in a hierarchy.
This means that states can be themselves other state machines.
Implementation
In skyward-boardcore implementation, the HSM class extends the EventHandler class, as for FSMs.
HSM is also a template class, such as FSM.
The main difference is that we can specify also super-states and perform transitions to them.
Moreover, state methods should return a value of type State
:
enum State
{
HANDLED = 0,
IGNORED = 1,
TRAN = 2,
SUPER = 3,
UNHANDLED = 4
};
For example:
State state_myState(const Event& ev)
{
...
switch(ev.sig)
{
case EV_1:
...
case EV_2:
...
default:
...
}
return HANDLED;
}
The HSM class also defines an Hsm_top
state, which indicates the top level state in the hierarchy. We can say that states that do not belong to any other super-state, are sub-states of Hsm_top
.
Methods
Available public methods are:
- State transition(State (T::*nextState)(const Event&)): perform a transition from the current state to the state that is specified as a parameter.
- State tran_super(State (T::`*superState)(const Event&)): perform a transition from the current state to the super-state that is specified as a parameter.
- bool testState(State (T::*test_state)(const Event&)): test is the state machines is in a specific state (that is passed as a parameter).
Standard Events
As for FSMs, EV_ENTRY
and EV_EXIT
are automatically posted when entering or exiting a state. Moreover, by handling EV_INIT
in a super-state we can perform the transition to its initial sub-state.
You can find the complete implementation of the HSM class in src/shared/events/HSM.h
.
Example
This example implements the following very simple hierarchical state machine behavior:
Whenever the state machine moves to state S2
(super-state), it will automatically transition to its first sub-state, that is S3
(S2
is a state machine itself and S3
is its initial state).
We can notice that state S3
only has a self-loop trasition that is triggered by event EV_2
. However, if the HSM receives an event EV_1
while being in state S3
, the event is handled by state S2
, which is a super-state of S3
.
No action is specified for the involved events (EV_ENTRY
, EV_EXIT
, EV_1
, EV_2
). They simply trigger the transitions.
MyHSM.h
As always when dealing with events and topics, after we include the required dependencies, we have to define the set of possible events and topics.
#pragma once
#include "events/HSM.h"
#include "events/EventBroker.h"
enum ExampleEvents : uint8_t
{
EV_1 = EV_FIRST_CUSTOM,
EV_2
};
enum ExampleTopics : uint8_t
{
TOPIC_1
};
Then we can define the MyHSM class and its states, which are identified by the two methods state_S1
, state_S2
(which is a super-state) and state_S3
(which is a sub-state of state_S2
).
#pragma once
#include "events/HSM.h"
#include "events/EventBroker.h"
#define sEventBroker Singleton<EventBroker>::getInstance()
namespace Boardcore {
enum ExampleEvents : uint8_t
{
EV_1 = EV_FIRST_CUSTOM,
EV_2
};
enum ExampleTopics : uint8_t
{
TOPIC_1
};
class MyHSM : public HSM<MyHSM>
{
public:
MyHSM()
// set HSM initial state to state_initialization
: HSM(&MyHSM::state_initialization),
last_event(0)
{
// make this object to subscribe to TOPIC_1
sEventBroker.subscribe(this, TOPIC_1);
}
~MyHSM()
{
// unsubscribe from all the topics this object was subscribed to
sEventBroker.unsubscribe(this);
}
State state_initialization(const Event& ev)
{ // this is just a ficticious state
// that automatically triggers a transition
// to the HSM initial state
// Common.h defined UNUSED(x) for (void)(x),
// but Common.h was replaced by miosix.h
(void)(ev);
return transition(&MyHSM::state_S1);
}
State state_S1(const Event& ev)
{
State ret_state = HANDLED;
switch (ev)
{
case EV_INIT:
case EV_ENTRY:
case EV_EXIT:
{
break;
}
case EV_1:
{
TRACE("S1: EV_1 \n");
// self-loop on state_S1
ret_state = transition(&MyHSM::state_S1);
break;
}
case EV_2:
{
TRACE("S1: EV_2 ---> S2 \n");
ret_state = transition(&MyHSM::state_S2);
break;
}
default:
{
ret_state = tranSuper(&MyHSM::state_top); // no parent state
break;
}
}
last_event = ev;
return ret_state;
}
State state_S2(const Event& ev)
{
State ret_state = HANDLED;
switch (ev)
{
case EV_INIT:
{
// this is a super-state
// move to the first sub-state
TRACE("S2: start from S3 \n");
ret_state = transition(&MyHSM::state_S3);
}
case EV_ENTRY:
case EV_EXIT:
{
break;
}
case EV_1:
{
TRACE("S2: EV_1 ---> S1 \n");
ret_state = transition(&MyHSM::state_S1);
break;
}
default:
{
ret_state = tranSuper(&MyHSM::state_top); // no parent state
break;
}
}
last_event = ev;
return ret_state;
}
State state_S3(const Event& ev)
{
State ret_state = HANDLED;
switch (ev)
{
case EV_INIT:
case EV_ENTRY:
case EV_EXIT:
{
break;
}
case EV_2:
{
TRACE("S3: EV_2 \n");
// self-loop on state_S3
ret_state = transition(&MyHSM::state_S3);
break;
}
default:
{
ret_state = tranSuper(&MyHSM::state_S2); // try with parent state S2
break;
}
}
last_event = ev;
return ret_state;
}
private:
uint8_t last_event;
};
} //namespace Boardcore
test-hsm.cpp
#include <miosix.h>
#include "events/MyHSM.h"
#define broker Singleton<EventBroker>::getInstance()
using namespace miosix;
using namespace Boardcore;
int main()
{
broker.start();
MyHSM hsm;
if (hsm.start())
{
// trigger self-loop on state S1
broker.post(Event{EV_1}, TOPIC_1);
Thread::sleep(100);
// trigger transition to state S2
// which in turn will automatically
// start from state S3 (the first sub-state)
broker.post(Event{EV_2}, TOPIC_1);
Thread::sleep(100);
// trigger self-loop on state S3
broker.post(Event{EV_2}, TOPIC_1);
Thread::sleep(100);
// the parent state S2 handles the event
// and triggers transition to state S1
broker.post(Event{EV_1}, TOPIC_1);
Thread::sleep(100);
hsm.stop();
broker.stop();
}
else {
TRACE("Failed to start MyHSM\n");
}
return 0;
}
Output
0.12> S1: EV_1
0.22> S1: EV_2 ---> S2
0.23> S2: start from S3
0.32> S3: EV_2
0.42> S2: EV_1 ---> S1