Skip to content

Overview Extended Tutorial

Andrew Gresyk edited this page Apr 1, 2022 · 2 revisions

Extended Tutorial

Tests

Code

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

    #define FFSM2_ENABLE_PLANS
  2. Include FFSM2 header:

    #include <ffsm2/machine_dev.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 = ffsm2::Config::ContextT<Context>;
  5. Define ffsm2::MachineT<> alias for convenience:

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

    TEST_CASE("Wiki.Tutorial") {
        // Create context and state machine instances:
        Context context;
        context.powerOn = true;
    
        FSM::Instance fsm{context};
        REQUIRE(fsm.isActive<Red>());     // On's initial sub-state
    
        fsm.update();
        REQUIRE(fsm.isActive<Yellow>());  // 1st setp of On's plan
    
        fsm.update();
        REQUIRE(fsm.isActive<Green>());   // 2nd setp of On's plan
    
        fsm.react(Event{});
        REQUIRE(fsm.isActive<Yellow>());  // 3rd setp of On's plan
    
        fsm.update();
        REQUIRE(fsm.isActive<Red>());     // 4th setp of On's plan
    
        fsm.update();
        REQUIRE(fsm.isActive<Done>());    // activated by On::planSucceeded()
    }