0

I want to implement a state machine that will periodically monitor some status data (the status of my system) and react to it.

This seems to be something quite basic for a state machine (I've had this problem many times before), but I could not find a good way to do it. Here is some pseudo code to explain what I'd like to achieve:

// some data that is updated from IOs for example
MyData data;

int state = 0;

while( true ) {
    update( & data ); //read a packet from serial port 
                      //and update the data structure
    switch( state ) {
    case 0:
        if( data.field1==0 ) state = 1;
        else doSomething();
        break;
    case 1:
        if( data.field2>0 ) state = 2;
        else doSomethingElse();
        break;

    // etc.

    }
    usleep(100000); //100ms
}

Of course on top of that, I want to be able to execute some actions upon entering and exiting a state, maybe do some actions at each iteration of the state, have substates, history, etc. Which is why this simplistic approach quickly becomes impractical, hence boost statechart.

I've thought about some solutions, and I'd like to get some feedback.

1) I could list all my conditions for transitions and create an event for each one. Then I would have a loop that would monitor when each of those boolean toggles. e.g. for my first condition it could be:

if( old_data.field1!=0 && new_data.field1==0 )
    // post an event of type Event 1

but it seems that it would quickly become difficult

2) have a single event that all states react to. this event is posted whenever some new status data is available. As a result, the current state will examine the data and decide whether to initiate a transition to another state or not

3) have all states inherit from an interface that defines a do_work(const MyData & data) method that would be called externally in a loop, examine the data and decide whether to initiate a transition to another state or not

Also, I am opened to using another framework (i.e. Macho or boost MSM)

brice rebsamen
  • 664
  • 6
  • 11
  • Maybe you could take a look at the QP active object framework with hierarchical state machines at state-machine.com. You don't write which operating system/environment you want to run it on, but judging by the usleep() call it looks like POSIX, which QP has been supporting for many years. – Miro Samek Jun 23 '13 at 02:20
  • Full disclosure Miro (who commented above) is the author of QP so hes plugging his own product ;) I do highly suggest his book. – odinthenerd Jun 28 '13 at 17:26

1 Answers1

1

Having worked with boost MSM, statecharts and QP my opinion is that you are on the right track with statecharts. MSM is faster but if you don't have much experience with state machines or meta programming the error messages from MSM are hard to understand if you do something wrong. boost.statecharts is the cleanest and easiest to understand. As for QP its written in embedded style (lots of preprocessor stuff, weaker static checking) although it also works in a PC environment. I also believe its slower. It does have the advantage of working on a lot of small ARM and similar processors. Its not free for commercial use as opposed to boost solutions.

Making an event for every type of state change does not scale. I would make one type of event EvStateChanged give it a data member containing a copy or reference to the dataset (and maybe one to the old data if you need it). You can then use costume reactions to handle whatever you need from any state context. Although default transitions work quite well in a toaster oven context (which are often used to demonstrate SM functionality) most real world SMs I have seen have many custom reactions, don't be shy to use them.

I don't really understand enough about your problem to give a code example but something along the lines of:

while( true ) {
    update( & data ); //read a packet from serial port 
                      //and update the data structure
    if(data != oldData){
          sm.process_event(EvDataChanged(data,oldData));
    }
    else{
        timeout++;
        if(timeout>MAX_TIMEOUT)
            sm.process_event(EvTimeout());
    }
    usleep(100000); //100ms
}

and then handle your data changes in custome reactions depending on state along these lines:

SomeState::~SomeState(){
    DoSomethingWhenLeaving();
}
sc::result SomeState::react( const EvDataChanged & e){
    if(e.oldData.Field1 != e.newData.Field1){
        DoSomething();
        return transit<OtherState>();
    }
    if(e.oldData.Field2 != e.newData.Field2){
        return transit<ErrorState>();   //is not allowed to change in this state
    }
    if(e.oldData.Field3 == 4){
        return forward_event();  //superstate should handle this
    }
    return discard_event(); //don't care about anything else in this context
}
odinthenerd
  • 5,422
  • 1
  • 32
  • 61