Tutorial: A basic door#

In this tutorial we will use the asap::fsm toolkit to implement a state machine simulating a door. We’ll start with two states (Closed and Open) and two events (Open and Close). The state machine can be represented by the following diagram:

Door states
 1#include <exception>
 2#include <iostream>
 3
 4#include <fsm/fsm.h>
 5
 6using asap::fsm::ByDefault;
 7using asap::fsm::DoNothing;
 8using asap::fsm::On;
 9using asap::fsm::StateMachine;
10using asap::fsm::TransitionTo;
11using asap::fsm::Will;
12
13namespace {
14struct OpenEvent {};
15struct CloseEvent {};
16
17struct ClosedState;
18struct OpenState;
19
20struct ClosedState :
21    // Using the state helpers to completely implement the state
22    // in a declarative way
23    Will<
24        // Default action is to do nothing
25        ByDefault<DoNothing>,
26        // Specific action on a specific event
27        On<OpenEvent, TransitionTo<OpenState>>> {};
28
29struct OpenState
30    : Will<ByDefault<DoNothing>, On<CloseEvent, TransitionTo<ClosedState>>> {};
31
32using Door = StateMachine<ClosedState, OpenState>;
33
34void PrintDoorState(const Door &door) {
35  std::cout << "   > door is " << (door.IsIn<OpenState>() ? "open" : "closed")
36            << "\n";
37}
38} // namespace
39
40auto main() -> int {
41  try {
42    Door door{ClosedState{}, OpenState{}};
43    std::cout << "-- Starting\n";
44    PrintDoorState(door);
45
46    std::cout << "-- sending close event\n";
47    door.Handle(CloseEvent{});
48    PrintDoorState(door);
49
50    std::cout << "-- sending open event\n";
51    door.Handle(OpenEvent{});
52    PrintDoorState(door);
53
54    std::cout << "-- sending close event\n";
55    door.Handle(CloseEvent{});
56    PrintDoorState(door);
57  } catch (const std::exception &err) {
58    std::cerr << "An exception was thrown: " << err.what() << std::endl;
59  } catch (...) {
60    std::cerr << "An unknown exception was thrown" << std::endl;
61  }
62}

The implementation is pretty straightforward for such a simple state machine:

  1. Define the events. Event types don’t need to be complex types. In this case, they are simple type tags. In other more complicated cases, events can carry data as well.

  2. Define the states.

    We can completely implement states with simple transition logic, such as in this example, declaratively. In lines 18-25, we use the state helpers Will, On and ByDefault to implement the logic of the ClosedState.

  3. Declare the State Machine. It’s always a good practice to use an alias for the State Machine type so that you don’t have to repeat the often very long type declaration.

From there on, using the state machine is a matter of injecting events into it via calls to its Handle() method.

If we run that example, this is what we get:

-- Starting
  > door is closed
-- sending close event
  > door is closed
-- sending open event
  > door is open
-- sending close event
  > door is closed

Notice how this implementation of the state machine is very basic to the point where we can close a closed door or open and open one with no error. We’d like to make it smarter so it can detect such cases and report the error to the user.

Head over to the next step to see how we can do that.