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:

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: