summaryrefslogtreecommitdiff
path: root/lib/tinyfsm/doc/40-Usage.md
blob: 457146b3270730cdce833260497ade2ac179d9ea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
Usage
=====

Refer to the [API examples](/examples/api/) provided with the TinyFSM
package for a quick overview. Recommended starting points:

 - [Elevator Project]: Documented example, two state machines with
   buttons, floor sensors and actors.
 - [Simple Switch]: A generic switch with two states (on/off).
 - [Moore Machine] and [Mealy Machine]: Basic, educational examples.

For an example in an RTOS environment, see the [stm32f103stk-demo] of
the [OpenMPTL] project. Starting points:

 - [screen.hpp](https://github.com/digint/openmptl/tree/master/projects/stm32f103stk-demo/src/screen.hpp)
   : TinyFSM declarations.
 - [kernel.cpp](https://github.com/digint/openmptl/tree/master/projects/stm32f103stk-demo/src/kernel.cpp)
   : Poll input and trigger events.

  [OpenMPTL]: https://digint.ch/openmptl/
  [stm32f103stk-demo]: https://github.com/digint/openmptl/tree/master/projects/stm32f103stk-demo


The examples in the documentation below are mainly based on the
[Elevator Project].

  [Elevator Project]: /examples/elevator/
  [Simple Switch]:    /examples/api/simple_switch.cpp
  [Moore Machine]:    /examples/api/moore_machine.cpp
  [Mealy Machine]:    /examples/api/mealy_machine.cpp


###  1. Declare Events

Declare events that your state machine will listen to. Events are
classes derived from the tinyfsm::Event class.

Example:

    struct FloorEvent : tinyfsm::Event
    {
      int floor;
    };

    struct Call        : FloorEvent { };
    struct FloorSensor : FloorEvent { };
    struct Alarm       : tinyfsm::Event { };

In the example above, we declare three events. Note that events are
regular classes, which are passed as arguments to the react() members
of a state class. In this example, we use a member variable "floor",
which is used to specify the floor number on "Call" and "FloorSensors"
events.


###  2. Declare the State Machine Class

Declare your state machine class. State machines are classes derived
from the tinyfsm::Fsm template class, where T is the type name of the
state machine itself.

You need to declare the following public members:

 - react() function for each event
 - entry() and exit() functions

Example:

    class Elevator
    : public tinyfsm::Fsm<Elevator>
    {
    public:
      /* default reaction for unhandled events */
      void react(tinyfsm::Event const &) { };
    
      virtual void react(Call        const &);
      virtual void react(FloorSensor const &);
      void         react(Alarm       const &);
    
      virtual void entry(void) { };  /* entry actions in some states */
      void         exit(void)  { };  /* no exit actions */
    };


Note that you are free to declare the functions non-virtual if you
like. This has implications on the execution speed: In the example
above, the react(Alarm) function is declared non-virtual, as all states
share the same reaction for this event. This makes code execution
faster when dispatching the "Alarm" event, since no vtable lookup is
needed.


###  3. Declare the States

Declare the states of your state machine. States are classes derived
from the state machine class.

Note that state classes are *implicitly instantiated*. If you want to
reuse states in multiple state machines, you need to declare them as
templates (see `/examples/api/multiple_switch.cpp`).

Example:

    class Panic
    : public Elevator
    {
      void entry() override;
    };
    
    class Moving
    : public Elevator
    {
      void react(FloorSensor const &) override;
    };
    
    class Idle
    : public Elevator
    {
      void entry() override;
      void react(Call const & e) override;
    };


In this example, we declare three states. Note that the "elevator"
example source code does not declare the states separately, but rather
defines the code directly in the declaration.


###  4. Implement Actions and Event Reactions

In most cases, event reactions consist of one or more of the following
steps:

 - Change some local data
 - Send events to other state machines
 - Transit to different state

**Important**:
Make sure that the `transit<>()` function call is the last command
executed within a reaction function!

**Important**:
Don't use `transit<>()` in entry/exit actions!

Example:

    void Idle::entry() {
      send_event(MotorStop());
    }
    
    void Idle::react(Call const & e) {
      dest_floor = e.floor;
    
      if(dest_floor == current_floor)
        return;
    
      /* lambda function used for transition action */
      auto action = [] { 
        if(dest_floor > current_floor)
          send_event(MotorUp());
        else if(dest_floor < current_floor)
          send_event(MotorDown());
      };
    
      transit<Moving>(action);
    };


In this example, we use a lambda function as transition action. The
`transit<>()` function does the following:

 1. Call the exit() function of the current state
 2. Call the the transition action if provided
 3. Change the current state to the new state
 4. Call the entry() function of the new state

Note that you can also pass condition functions to the `transit<>()`
function.


###  5. Define the Initial State

Use the macro `FSM_INITIAL_STATE(fsm, state)` for defining the initial
state (or "start state") of your state machine:

Example:

    FSM_INITIAL_STATE(Elevator, Idle)

This sets the current state of the "Elevator" state machine to "Idle".
More specifially, it defines a template specialization for
`Fsm<Elevator>::set_initial_state()`, setting the current state to
Idle.


###  6. Define Custom Initialization

If you need to perform custom initialization, you can override the
reset() member function in your state machine class. If you are using
state variables, you can re-instantiate your states by calling
`tinyfsm::StateList<MyStates...>::reset()`.

Example:

    class Switch : public tinyfsm::Fsm<Switch>
    {
      public: static void reset(void) {
        tinyfsm::StateList<Off, On>::reset();  // reset all states
        myvar = 0;
        ...
      }
      ...
    }

Make sure to always set the current state, or you'll end up with a
null pointer dereference.


###  7. Use FsmList for Event Dispatching

You might have noticed some calls to a send_event() function in the
example above. This is NOT a function provided with TinyFSM. Since
event dispatching can be implemented in several ways, TinyFSM leaves
this open to you. The "elevator" example implements the send_event()
function as *direct event dispatching*, without using event
queues. This has the advantage that execution is much faster, since no
RTTI is needed and the decision which function to call for an event
class is made at compile-time. On the other hand, special care has to
be taken when designing the state machines, in order to avoid loops.

Code from "fsmlist.hpp":

    typedef tinyfsm::FsmList<Motor, Elevator> fsm_list;
    
    template<typename E>
    void send_event(E const & event)
    {
      fsm_list::template dispatch<E>(event);
    }

Here, send_event() dispatches events to all state machines in the
list. It is important to understand that this approach comes with no
performance penalties at all, as long as the default reaction is
defined empty within the state machine declaration.