Tutorial: A secure door#

In this last step of the tutorial, we’ll make our door super smart. We’re going to add a lock code that can be set when locking the door and that will be required to unlock it later.

We’ll assume that the lock code is entered suing some kind of UI interacting with the user, and which provides it to us in the form of an integer.

The new state machine can be represented by the following diagram:

Door states

Besides an additional state, we’ll introduce two new events Lock and Unlock. Now, once the door is locked, the only way to open it, is to unlock it first.

  1#include <iostream>
  2
  3#include <fsm/fsm.h>
  4
  5using asap::fsm::ByDefault;
  6using asap::fsm::Continue;
  7using asap::fsm::DoNothing;
  8using asap::fsm::Maybe;
  9using asap::fsm::On;
 10using asap::fsm::StateMachine;
 11using asap::fsm::Status;
 12using asap::fsm::TransitionTo;
 13using asap::fsm::Will;
 14
 15namespace {
 16struct OpenEvent {};
 17struct CloseEvent {};
 18
 19struct LockEvent {
 20  uint32_t newKey; // the lock code chosen by the user
 21};
 22
 23struct UnlockEvent {
 24  uint32_t key; // the lock key entered when unlocking
 25};
 26
 27struct ClosedState;
 28struct OpenState;
 29struct LockedState;
 30
 31struct ClosedState
 32    : Will<ByDefault<DoNothing>, On<LockEvent, TransitionTo<LockedState>>,
 33          On<OpenEvent, TransitionTo<OpenState>>> {
 34  using Will::Handle;
 35
 36  [[maybe_unused]] static auto OnEnter(const UnlockEvent & /*event*/)
 37      -> Status {
 38    std::cout << "   > door is closed - unlocked\n";
 39    return Continue{};
 40  }
 41
 42  template <typename Event>
 43  static auto OnEnter(const Event & /*event*/) -> Status {
 44    std::cout << "   > door is closed\n";
 45    return Continue{};
 46  }
 47
 48  [[nodiscard]] [[maybe_unused]] static auto Handle(
 49      const CloseEvent & /*event*/) -> DoNothing {
 50    std::cerr << "Error: the door is already closed!\n";
 51    return DoNothing{};
 52  }
 53};
 54
 55struct OpenState
 56    : Will<ByDefault<DoNothing>, On<CloseEvent, TransitionTo<ClosedState>>> {
 57  using Will::Handle;
 58
 59  template <typename Event>
 60  static auto OnEnter(const Event & /*event*/) -> Status {
 61    std::cout << "   > door is open\n";
 62    return Continue{};
 63  }
 64
 65  [[nodiscard]] [[maybe_unused]] static auto Handle(const OpenEvent & /*event*/)
 66      -> DoNothing {
 67    std::cerr << "Error: the door is already open!\n";
 68    return DoNothing{};
 69  }
 70};
 71
 72struct LockedState : ByDefault<DoNothing> {
 73  using ByDefault::Handle;
 74
 75  explicit LockedState(uint32_t key) : key_(key) {
 76  }
 77
 78  [[maybe_unused]] auto OnEnter(const LockEvent &event) -> Status {
 79    std::cout << "   > door is locked with new code(" << event.newKey << ")\n";
 80    key_ = event.newKey;
 81    return Continue{};
 82  }
 83
 84  [[nodiscard]] [[maybe_unused]] auto Handle(const UnlockEvent &event) const
 85      -> Maybe<TransitionTo<ClosedState>> {
 86    if (event.key == key_) {
 87      return TransitionTo<ClosedState>{};
 88    }
 89    std::cerr << "Error: wrong key (" << event.key
 90              << ") used to unlock door!\n";
 91    return DoNothing{};
 92  }
 93
 94private:
 95  uint32_t key_;
 96};
 97} // namespace
 98
 99using Door = StateMachine<ClosedState, LockedState, OpenState>;
100
101auto main() -> int {
102  try {
103    Door door{ClosedState{}, LockedState(0), OpenState{}};
104
105    constexpr int lock_code = 1234;
106    constexpr int bad_code = 2;
107
108    std::cout << "-- Starting\n";
109
110    std::cout << "-- sending open event\n";
111    door.Handle(OpenEvent{});
112
113    std::cout << "-- sending close event\n";
114    door.Handle(CloseEvent{});
115
116    std::cout << "-- sending lock event (" << lock_code << ")\n";
117    door.Handle(LockEvent{lock_code});
118
119    std::cout << "-- sending unlock event (" << bad_code << ")\n";
120    door.Handle(UnlockEvent{bad_code});
121
122    std::cout << "-- sending unlock event (" << lock_code << ")\n";
123    door.Handle(UnlockEvent{lock_code});
124  } catch (const std::exception &err) {
125    std::cerr << "An exception was thrown: " << err.what() << std::endl;
126  } catch (...) {
127    std::cerr << "An unknown exception was thrown" << std::endl;
128  }
129}

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

-- Starting
-- sending open event
  > door is open
-- sending close event
  > door is closed
-- sending lock event (1234)
  > door is locked with new code(1234)
-- sending unlock event (2)
Error: wrong key (2) used to unlock door!
-- sending unlock event (1234)
  > door is closed - unlocked

The new implementation demonstrates many advanced features offered by the asap::fsm library:

  • using templates SFINAE to define custom handlers for specific events,

  • passing data in events,

  • storing data in states

Additionally, the API allows for passing data during transitions from one state to another. Check the documentation of TransitionTo for additional details on that technique.

Several sophisticated examples of using the asap::fsm library in real world projects can be found at: