Skip to content

Extended Tutorial

Andrew Gresyk edited this page Apr 12, 2024 · 1 revision

Tests

Code

  1. Configure optional HFSM2 functionality using #defines (in this case we're using plans to make transition cycle more straightforward):

    #define HFSM2_ENABLE_PLANS
  2. Include HFSM2 header:

    #include <hfsm2/machine.hpp>
  3. Define interface class between the state machine and its host (also ok to use the host object itself):

    struct Context {
        bool powerOn;
    };
  4. Define type configuration:

    using Config = hfsm2::Config::ContextT<Context>;
  5. Define hfsm2::MachineT<> alias for convenience:

    using M = hfsm2::MachineT<Config>;
  6. Declare state machine structure. States need to be forward declared, e.g. with a magic macro:

    #define S(s) struct s
    
    using FSM = M::PeerRoot<
                    S(Off),                 // initial state
                    M::Composite<S(On),     // sub-machine region with a head state and and 3 substates
                        S(Red),             // initial sub-state of the region
                        S(Yellow),
                        S(Green)
                    >,
                    S(Done)
                >;
    
    #undef S
  7. While HFSM2 transitions aren't event-based, events can be used to have FSM react to external stimuli:

    struct Event {};
  8. Define states and override required state methods:

    struct Off
        : FSM::State
    {
        void entryGuard(FullControl& control) {             // called before state activation
            if (control.context().powerOn)                  // access shared data
                control.changeTo<On>();                     // initiate a transition into 'On' region
        }
    
        void exit(PlanControl& /*control*/) {}              // called on state deactivation
    };
    
    struct On
        : FSM::State
    {
        void enter(PlanControl& control) {                  // called on state activation
            auto plan = control.plan();                     // access the plan for the region
    
            plan.change<Red, Yellow>();                     // sequence plan steps, executed
            plan.change<Yellow, Green>();                   //   when the previous state succeeds
            plan.change<Green, Yellow>();
            plan.change<Yellow, Red>();
        }
    
        void exit(PlanControl& /*control*/) {}              // called on state deactivation
    
        void planSucceeded(FullControl& control) {          // called on the successful completion
            control.changeTo<Done>();                       //   of all plan steps
        }
    
        void planFailed(FullControl& /*control*/) {}        // called if any of the plan steps fails
    };
    
    struct Red
        : FSM::State
    {
        void update(FullControl& control) {                 // called on periodic state machine updates
            control.succeed();                              // notify successful completion of the plan step
        }                                                   // plan will advance to the 'Yellow' state
    };
    
    struct Yellow
        : FSM::State
    {
        void update(FullControl& control) {
            control.succeed();                              // plan will advance to the 'Green' state
                                                            //   on the first entry and 'Red' state
                                                            //   on the second one
        }
    };
    
    struct Green
        : FSM::State
    {
        void react(const Event&, FullControl& control) {    // called on external events
            control.succeed();                              // advance to the next plan step
        }
    };
    
    struct Done
        : FSM::State
    {};
  9. Write the client code to use your new state machine:

    TEST_CASE("Wiki.Tutorial") {
  10. Create context and state machine instances:

        Context context;
        context.powerOn = true;
    
        FSM::Instance fsm{context};
        assert(fsm.isActive<On>());                         // activated by Off::entryGuard()
        assert(fsm.isActive<Red>());                        // On's initial sub-state
  11. Call FSM::update() for fsm to process transitions:

        fsm.update();
        assert(fsm.isActive<Yellow>());                    // 1st setp of On's plan
    
        fsm.update();
        assert(fsm.isActive<Green>());                     // 2nd setp of On's plan
  12. Event reactions also causes transitions getting processed:

        fsm.react(Event{});
        assert(fsm.isActive<Yellow>());                    // 3rd setp of On's plan
  13. Keep updating the FSM until it's done:

        fsm.update();
        assert(fsm.isActive<Red>());                       // 4th setp of On's plan
    
        fsm.update();
        assert(fsm.isActive<Done>());                      // activated by On::planSucceeded()
    
        return 0;
    }