Search code examples
c++boost-msm

Handle an error from every state in Boost MSM


I am using Boost MSM to model the behavior of a robot. There are multiple states, as "ManualMove", "AutoMove", "Work", "Idle", etc... However I need to be able to force stop the robot from any state, putting it in state where it cannot move and cannot receive new commands, so I created an "EmergencyStopped" state. When the robot is asked to rearm itself, the robot should go back to the "Idle" state.

However, Boost does not recommend to create a transition from all state to a single one, and prefer to use orthogonal states. So I could for example do "AllOk" and an interrupt_state "EmergencyStopped" orthogonal states.

The problem is, while I can easily put the robot in "EmergencyStopped", I cannot quit it and put the robot into the "Idle" state from the state it was before. For example, if the robot do:

  • [Work, AllOk] -> stop
  • [Work,EmergencyStopped] -> recover

The robot will be in the state [Work, AllOk], while I want it to go in the state [Idle, AllOk].

So my questions are:

  • Can and should I use orthogonal states for this workflow? If yes, how to force the state to "Idle" when I qui the orthogonal state "EmergencyStopped"?
  • Or should I make "EmergencyStopped" non-orthogonal and declare a transition from all states to it?
  • Or is there another solution?

Solution

  • There is another solution. In your case, composite state is better choice. See the diagram.

    StateMachineDiagram

    How to avoid writing many transitions to "EmergencyStopped".

    Em-place all states that need to transition to "EmergencyStopped" if "stop" event is happend into "Normal" state. And put the transition that is from "Normal" to "EmergencyStopped". Its trigger event is "stop". This approach can avoid writing many transitions to "EmergencyStopped". Even if you would add other child state of "Normal", you don't need to add the transition for the added state. That is one of benefit of the composite state approach.

    How to transition to "Idle" state if "recover" event is happened.

    Set "Idle" state to initial_state. It reflect to Initial puseudo state in UML state-machine diagram.

    typedef mpl::vector<Idle> initial_state;
    

    If transition target is parent state "Normal", then transition target state is "Idle" state because it is marked as initial_state.

    Those two technique solve your question.

    Here is the complete code:

    #include <iostream>
    #include <boost/msm/back/state_machine.hpp>
    
    #include <boost/msm/front/state_machine_def.hpp>
    #include <boost/msm/front/functor_row.hpp>
    #include <boost/static_assert.hpp>
    
    namespace msm = boost::msm;
    namespace msmf = boost::msm::front;
    namespace mpl = boost::mpl;
    
    // ----- Events
    struct ev1 {};
    struct ev2 {};
    struct ev3 {};
    struct stop {};
    struct recover {};
    
    // ----- State machine
    struct YourSystem_:msmf::state_machine_def<YourSystem_>
    {
        struct Normal_:msmf::state_machine_def<Normal_>
        {
            template <class Event,class Fsm>
            void on_entry(Event const&, Fsm&) const {
                std::cout << "Normal::on_entry()" << std::endl;
            }
            template <class Event,class Fsm>
            void on_exit(Event const&, Fsm&) const {
                std::cout << "Normal::on_exit()" << std::endl;
            }
    
            struct Idle:msmf::state<> {
                template <class Event,class Fsm>
                void on_entry(Event const&, Fsm&) const {
                    std::cout << "Idle::on_entry()" << std::endl;
                }
                template <class Event,class Fsm>
                void on_exit(Event const&, Fsm&) const {
                    std::cout << "Idle::on_exit()" << std::endl;
                }
            };
    
            struct Work:msmf::state<> {
                template <class Event,class Fsm>
                void on_entry(Event const&, Fsm&) const {
                    std::cout << "Work::on_entry()" << std::endl;
                }
                template <class Event,class Fsm>
                void on_exit(Event const&, Fsm&) const {
                    std::cout << "Work::on_exit()" << std::endl;
                }
            };
    
            struct AllOk:msmf::state<> {
                template <class Event,class Fsm>
                void on_entry(Event const&, Fsm&) const {
                    std::cout << "AllOk::on_entry()" << std::endl;
                }
                template <class Event,class Fsm>
                void on_exit(Event const&, Fsm&) const {
                    std::cout << "AllOk::on_exit()" << std::endl;
                }
            };
    
            // Set initial state
            typedef mpl::vector<Idle> initial_state;
            // Transition table
            struct transition_table:mpl::vector<
                //          Start      Event   Next       Action      Guard
                msmf::Row < Idle,      ev1,    Work,      msmf::none, msmf::none >,
                msmf::Row < Work,      ev2,    AllOk,     msmf::none, msmf::none >,
                msmf::Row < AllOk,     ev3,    Idle,      msmf::none, msmf::none >
                > {};
        };
        struct EmergencyStopped:msmf::state<>
        {
            template <class Event,class Fsm>
            void on_entry(Event const&, Fsm&) const {
                std::cout << "EmergencyStopped::on_entry()" << std::endl;
            }
            template <class Event,class Fsm>
            void on_exit(Event const&, Fsm&) const {
                std::cout << "EmergencyStopped::on_exit()" << std::endl;
            }
        };
    
        typedef msm::back::state_machine<Normal_> Normal;
    
        // Set initial state
        typedef Normal initial_state;
        // Transition table
        struct transition_table:mpl::vector<
            //          Start             Event     Next               Action      Guard
            msmf::Row < Normal,           stop,     EmergencyStopped,  msmf::none, msmf::none >,
            msmf::Row < EmergencyStopped, recover,  Normal,            msmf::none, msmf::none >
        > {};
    };
    
    // Pick a back-end
    typedef msm::back::state_machine<YourSystem_> Ys;
    
    int main()
    {
        Ys ys;
        ys.start();
    
        std::cout << "> Send ev1()" << std::endl;
        ys.process_event(ev1());
        std::cout << "> Send ev2()" << std::endl;
        ys.process_event(ev2());
    
        std::cout << "> Send stop()" << std::endl;
        ys.process_event(stop());
        std::cout << "> Send recover()" << std::endl;
        ys.process_event(recover());
    }
    

    and running demo https://wandbox.org/permlink/uBm6jTvG0YL3gSgl