HSM  0.1
Heirarchical State Machine
 All Classes Files Functions Variables Enumerations Enumerator Macros Pages
HSM user's API documentation

This is the manual for the Heirarchical State Machine API.Interesting classes:

First you should declare an enum for all your event types. Start at HSM_USER_START because values below that are reserved.

enum MyHSMEVENTTYPE {
CONFIG,
CONNECT,
DISCON,
AUTH,
DUMMY
};

Next, declare structs and struct bodys for these events. There are two ways to do it. Examples of both are presented below.

struct myKickoffEvt : HSM::HSMEventT<myKickoffEvt,KICKOFF>
{
const std::string evtName(void) const { return "myKickoffEvt"; }
int value3;
int value4;
};
HSM_EVENT_DECLARE(myConfigEvt,CONFIG,
int value1;
int value2;
);

Note that each class defined (in this case myConfigEvt and myKickoffEvt) are given a memory pool (supplied by the ThreadSlinger library) and a static HSM::HSMEventT::alloc method to alloc new instances. Each event is also given a ref and deref method (see ThreadSlinger::thread_slinger_message::ref). On the last deref, the event buffer is automatically released back to the pool.

Next, declare and implement the state machine. There are two types of state machines available.

A simple HSM::HSM state machine is an object with methods. The HSM::HSM::dispatch method must be invoked on the object to pass an event into that state machine. You manage the memory on the event objects and there is no threading. The dispatch method simply returns once your state transition function has completed.

An example of declaring and using a simple HSM::HSM state machine follows:

class myStateMachine1 : public HSM<myStateMachine1>
{
myStateMachine1(void) : HSM(true) { // true enables debug
// some init code
}
~myStateMachine1(void) {
// some cleanup code
}
Action initial(void) { // virtual from base class
// some init code
return TRANS(&myStateMachine1::state1);
}
Action top(HSMEvent const * ev) {
switch (ev->type)
{
case HSM_ENTRY:
// do some state entry stuff
return HANDLED();
case HSM_EXIT:
// do some state exit stuff
return HANDLED();
case CONFIG:
doSomething(dynamic_cast<myConfigEvt*>(ev)->value1);
return TRANS(&myStateMachine2::state2);
// etc
}
return TOP();
}
Action state1(HSMEvent const * ev) {
switch (ev->type)
{
case HSM_ENTRY:
// do some state entry stuff
return HANDLED();
case HSM_EXIT:
// do some state exit stuff
return HANDLED();
// etc
}
return SUPER(&myStateMachine1::top, "state1");
}
Action state2(HSMEvent const * ev) {
// etc
return SUPER(&myStateMachine1::top, "state2");
}
};
void somefunc(void) {
myStateMachine1 m;
m.HSMInit();
myConfigEvt cfg;
cfg.value1 = something;
cfg.value2 = something;
m.dispatch(&cfg);
//etc
}

Every HSM must implement the "initial" method. The "initial" method must return TRANS to indicate the initial state.

A state must return SUPER or TOP if it does not understand a message. SUPER indicates the parent state may support the message. TOP indicates it is the top-most state in the heirarchy.

The HSM system uses SUPER also to determine state names. (It is assumed the TOP state is named "top".) That is why SUPER has a mandatory string argument.

States receive an HSM_ENTRY event when a TRANS causes that state to be entered. When a heirarchical state is entered, all states which have just been entered receive HSM_ENTRY, in order from top-most to bottom-most state. When a TRANS causes a set of states in the heirarchy to be exited, they all receive HSM_EXIT events, in reverse order (bottom-most to top-most). If a TRANS causes a subset of the states to be the same in the before-set and after-set, they receive no ENTRY or EXIT events.

In the above example, "top" is the top-most state, and its two substates are state1 and state2. (Note that the parent-relationship is established via the SUPER return values.) When HSMInit is called, the "initial" virtual method is called. "initial" puts it in state1. This causes an ENTRY event to "top" state, then and ENTRY to "state1". We are now in the "top.state1" heirarchical state. When the "CONFIG" event is injected, state1 function is invoked. In this case, state1 does not have a handler for CONFIG, so it falls to the SUPER. The config event is then passed to the SUPER state, which is "top". Top has a handler for that event which causes a TRANS to state2. Since state2 also has a SUPER to "top", this means "top" is not exited. So an EXIT is sent to state1, and ENTRY is sent to state2. We are now in the "top.state2" state.

An HSM::ActiveHSM is more complex. It is a thread plus a subscription- management system. Multiple ActiveHSMs may be created, and each may subscribe to a set of HSM::HSMEvent::Type types it is interested in receiving. An HSM::HSMScheduler object must be created, and the ActiveHSMs are registered with it. HSMScheduler performs subscription management and reference-counting on the events.

The following example shows how to use an ActiveHSM, and also demonstrates both ways to declare an ActiveHSM.

class myStateMachine1 : public ActiveHSM<myStateMachine1>
{
myStateMachine1(HSMScheduler * _sched, bool __debug = false)
: ActiveHSM<myStateMachine1>(_sched, "machine1", __debug)
{
// init code
}
~myStateMachine1(void)
{
// cleanup code
}
Action initial(void) // virtual in base
{
subscribe(CONFIG);
subscribe(CONNECT);
subscribe(DISCON);
subscribe(AUTH);
subscribe(DUMMY);
return TRANS(&myStateMachine1::unconfigured);
}
// here is the state heirarchy:
// top
// unconfigured
// configured
// init
// connected
// auth
Action top(HSMEvent const * ev)
{
switch (ev->type)
{
case HSM_ENTRY:
printf("ENTER 1: top\n");
return HANDLED();
case HSM_EXIT:
printf("EXIT 1: top\n");
return HANDLED();
}
return TOP();
}
Action unconfigured(HSMEvent const * ev)
{
switch (ev->type)
{
case HSM_ENTRY:
printf("ENTER 1: top.unconfigured\n");
return HANDLED();
case HSM_EXIT:
printf("EXIT 1: top.unconfigured\n");
return HANDLED();
case CONFIG:
printf("got CONFIG in top.unconfigured\n");
return TRANS(&myStateMachine1::init);
}
return SUPER(&myStateMachine1::top, "unconfigured");
}
Action configured(HSMEvent const * ev)
{
switch (ev->type)
{
case HSM_ENTRY:
printf("ENTER 1: top.configured\n");
return HANDLED();
case HSM_EXIT:
printf("EXIT 1: top.configured\n");
return HANDLED();
case CONNECT:
printf("got CONNECT in top.configured.init\n");
return TRANS(&myStateMachine1::connected);
case DISCON:
printf("got DISCON in top.configured\n");
return TRANS(&myStateMachine1::unconfigured);
}
return SUPER(&myStateMachine1::top, "configured");
}
Action init(HSMEvent const * ev)
{
switch (ev->type)
{
case HSM_ENTRY:
printf("ENTER 1: top.configured.init\n");
return HANDLED();
case HSM_EXIT:
printf("EXIT 1: top.configured.init\n");
return HANDLED();
}
return SUPER(&myStateMachine1::configured, "init");
}
Action connected(HSMEvent const * ev)
{
switch (ev->type)
{
case HSM_ENTRY:
printf("ENTER 1: top.configured.connected\n");
return HANDLED();
case HSM_EXIT:
printf("EXIT 1: top.configured.connected\n");
return HANDLED();
case AUTH:
printf("got AUTH in top.configured.connected\n");
return TRANS(&myStateMachine1::auth);
}
return SUPER(&myStateMachine1::configured, "connected");
}
Action auth(HSMEvent const * ev)
{
switch (ev->type)
{
case HSM_ENTRY:
printf("ENTER 1: top.configured.connected.auth\n");
return HANDLED();
case HSM_EXIT:
printf("EXIT 1: top.configured.connected.auth\n");
return HANDLED();
}
return SUPER(&myStateMachine1::connected, "auth");
}
};
ACTIVE_HSM_DECLARE(myStateMachine2)
{
public:
myStateMachine2(HSMScheduler * _sched, bool __debug = false)
: ACTIVE_HSM_BASE(myStateMachine2)(_sched, "machine2", __debug)
{
}
virtual ~myStateMachine2(void)
{
}
Action initial(void) // virtual in base
{
subscribe(KICKOFF);
return TRANS(&myStateMachine2::top);
}
Action top(HSMEvent const * ev)
{
switch (ev->type)
{
case HSM_ENTRY:
printf("ENTER 2: top\n");
return HANDLED();
case HSM_EXIT:
printf("EXIT 2: top\n");
return HANDLED();
case KICKOFF:
printf("sm2 got KICKOFF in top\n");
printf("sm2 sleeping\n");
sleep(1);
printf("sm2 sending config\n");
publish(myConfigEvt::alloc());
printf("sm2 sleeping\n");
sleep(1);
printf("sm2 sending connect\n");
publish(myConnectEvt::alloc());
printf("sm2 sleeping\n");
sleep(1);
printf("sm2 sending auth\n");
publish(myAuthEvt::alloc());
printf("sm2 sleeping\n");
sleep(1);
printf("sm2 sending discon\n");
publish(myDisconEvt::alloc());
printf("sm2 sleeping\n");
sleep(1);
printf("sm2 sending dummy\n");
publish(myDummyEvt::alloc());
printf("sm2 sleeping\n");
sleep(1);
done = true;
return HANDLED();
}
return TOP();
}
};
void somefunc(void)
{
HSMScheduler sched;
myStateMachine1 myHsm1(&sched, true);
myStateMachine2 myHsm2(&sched, true);
sched.start();
sched.publish(MyTestApp::myKickoffEvt::alloc());
while (done == false)
sleep(1);
sched.stop();
ThreadSlinger::poolReportList_t report;
ThreadSlinger::thread_slinger_pools::report_pools(report);
std::cout << "pool report:\n";
for (size_t ind = 0; ind < report.size(); ind++)
{
const ThreadSlinger::poolReport &r = report[ind];
std::cout << " pool "
<< r.name
<< " : "
<< r.usedCount
<< " used "
<< r.freeCount
<< " free\n";
}
}