Lely core libraries  1.9.2
doc/co/fsm.md
1 Finite-state machines
2 =====================
3 
4 All services in the CANopen stack are asynchronous and therefore implemented as
5 event-driven [finite-state machines][] (FSMs). Each implementation follows the
6 pattern shown here. To ensure the maintainability and extensibility of these
7 state machines, the pattern was designed to have the following properties:
8 
9 - Adding a new event does not affect existing states;
10 - Adding a new state is a local change; it does not modify existing functions.
11 
12 Additionally, the implementation should support parametrized events and
13 transient states (states which transition to another state without an external
14 event). The latter greatly simplifies error handling.
15 
16 The first property excludes implementations based on a [state transition table],
17 while the second excludes `enum`-based state machines. Additionally, transient
18 states are hard to implement in either approach.
19 
20 The implementation shown here is an extension of the function-pointer-based
21 state machine and is similar to the object-oriented [state design pattern]. The
22 state machine is an object --- see the documentation of the Lely utilities
23 library ([liblely-util]) for the general object interface --- containing the
24 context and a pointer to the current state. Each state is a (constant) array of
25 pointers to transitions functions, one for each event. The transition functions
26 return a pointer to the next state, or `NULL` if no transition occurs. There are
27 two special (optional) transition functions, `on_enter()` and `on_leave()`,
28 called when a state is entered or left, respectively. A transient state is
29 simply a state whose `on_enter()` function exists and returns a pointer to
30 another state.
31 
32 Example of a public C header (lely/lib/fsm.h):
33 ~~~{.c}
34 // Check if the FSM is currently in the idle state.
35 int fsm_is_idle(const fsm_t *fsm);
36 
37 // Issue a request to the FSM. This function returns 0 on success, or -1 on
38 // error (e.g., because the FSM is not in the idle state).
39 int fsm_req(fsm_t *fsm, Args... args);
40 ~~~
41 
42 Example of the C implementation (fsm.c):
43 ~~~{.c}
44 #include <lely/libc/time.h>
45 #include <lely/util/errnum.h>
46 
47 // A state is a constant collection of function pointers.
48 struct __fsm_state;
49 typedef const struct __fsm_state fsm_state_t;
50 
51 struct __fsm {
52  ...
53  // A pointer to the current state.
54  fsm_state_t *state;
55  ...
56 };
57 
58 // A helper function for entering a new state.
59 static void fsm_enter(fsm_t *fsm, fsm_state_t *next);
60 
61 // A helper function for generating an event and invoking the corresponding
62 // transition function.
63 static inline void fsm_emit_event(fsm_t *fsm, Args... args);
64 
65 // A helper function for generating a timeout event.
66 static inline void fsm_emit_time(fsm_t *fsm, const struct timespec *tp);
67 
68 struct __fsm_state {
69  // The function called when the state is entered. If this function is
70  // specified, its return value is taken to be the next state and a
71  // transition will automatically occur.
72  fsm_state_t *(*on_enter)(fsm_t *fsm);
73  // Events can carry parameters which are passed as arguments to the
74  // transition function.
75  fsm_state_t *(*on_event)(fsm_t *fsm, Args... args);
76  // It is common for services to define a timeout event.
77  fsm_state_t *(*on_time)(fsm_t *fsm, const struct timespec *tp);
78  // The function called when a state is left. This function cannot return
79  // another state, since the next state is already specified.
80  void (*on_leave)(fsm_t *fsm);
81 };
82 
83 // A macro designed to improve the readability of state definitions.
84 #define LELY_LIB_DEFINE_STATE(name, ...) \
85  static fsm_state_t *const name = &(fsm_state_t){ __VA_ARGS__ };
86 
87 // The idle state does not contain any transition functions.
88 LELY_LIB_DEFINE_STATE(fsm_idle_state, NULL)
89 
90 // The initialization state is transient and only specifies on_enter().
91 static fsm_state_t *fsm_init_on_enter(fsm_t *fsm);
92 LELY_LIB_DEFINE_STATE(fsm_init_state,
93  .on_enter = &fsm_init_on_enter
94 )
95 
96 // The waiting state responds to events and timeouts.
97 static fsm_state_t *fsm_wait_on_event(fsm_t *fsm, Args... args);
98 static fsm_state_t *fsm_wait_on_time(fsm_t *fsm, const struct timespec *tp);
99 LELY_LIB_DEFINE_STATE(fsm_wait_state,
100  .on_event = &fsm_wait_on_event,
101  .on_time = &fsm_wait_on_time
102 )
103 
104 // Like the initialization state, the finalization state is transient.
105 static fsm_state_t *fsm_fini_on_enter(fsm_t *fsm);
106 LELY_LIB_DEFINE_STATE(fsm_fini_state,
107  .on_enter = &fsm_fini_on_enter
108 )
109 
110 #undef LELY_LIB_DEFINE_STATE
111 
112 struct __fsm *
113 __fsm_init(struct __fsm *fsm, Args... args)
114 {
115  ...
116 
117  fsm->state = NULL;
118  // Enter the idle state and wait for the user to issue a request.
119  fsm_enter(fsm, fsm_idle_state);
120 
121  ...
122 }
123 
124 int
125 fsm_is_idle(const fsm_t *fsm)
126 {
127  assert(fsm);
128 
129  // States are uniquely identified by their address and can be compared
130  // directly.
131  return fsm->state == fsm_idle_state;
132 }
133 
134 int
135 fsm_req(fsm_t *fsm, Args... args)
136 {
137  assert(fsm);
138 
139  if (!fsm_is_idle(fsm)) {
140  set_errnum(ERRNUM_INPROGRESS)
141  return -1;
142  }
143 
144  // Construct request based on args.
145  ...
146 
147  // Enter the initialization state.
148  fsm_enter(fsm, fsm_init_state);
149  return 0;
150 }
151 
152 static void
153 fsm_enter(fsm_t *fsm, fsm_state_t *next)
154 {
155  assert(fsm);
156 
157  // Use a loop to allow transient state sequences of arbitrary length.
158  while (next) {
159  // Set the next state before invoking on_leave(). It is common
160  // for services to invoke a user-defined callback function from
161  // on_leave(). By setting the next state before doing so, it
162  // becomes possible for the callback function to issue the next
163  // request without waiting for on_leave() to complete.
164  fsm_state_t *prev = fsm->state;
165  sdo->state = next;
166 
167  // Invoke the on_leave() function if specified.
168  if (prev->on_leave)
169  prev->on_leave(fsm);
170 
171  // Enter the next state and invoke its on_enter() function, if
172  // specified. If this function returns another state, enter it
173  // in the next iteration.
174  next = next->on_enter ? next->on_enter(fsm) : NULL;
175  }
176 }
177 
178 static inline void
179 fsm_emit_event(fsm_t *fsm, Args... args)
180 {
181  assert(fsm);
182  assert(fsm->state);
183  // We could choose to do nothing if no transition function is specified,
184  // but an assertion allows us to detect unexpected events.
185  assert(fsm->state->on_event);
186 
187  fsm_enter(fsm, fsm->state->on_event(fsm, args...));
188 }
189 
190 static inline void
191 fsm_emit_time(fsm_t *fsm, const struct timespec *tp)
192 {
193  assert(fsm);
194  assert(fsm->state);
195  assert(fsm->state->on_time);
196 
197  fsm_enter(fsm, fsm->state->on_time(fsm, tp));
198 }
199 
200 static fsm_state_t *
201 fsm_init_on_enter(fsm_t *fsm)
202 {
203  assert(fsm);
204 
205  // Perform initialization.
206  ...
207 
208  // This is a transient state; it transitions to the waiting state
209  // without waiting for an event.
210  return fsm_wait_state;
211 }
212 
213 static fsm_state_t *
214 fsm_wait_on_event(fsm_t *fsm, Args... args)
215 {
216  assert(fsm);
217 
218  // Process the event.
219  ...
220 
221  // If the event completes the request, enter the finalization state.
222  return fsm_fini_state;
223 }
224 
225 static fsm_state_t *
226 fsm_wait_on_time(fsm_t *fsm, const struct timespec *tp)
227 {
228  assert(fsm);
229 
230  // A timeout occurred. Abort and clean up.
231  ...
232 
233  // Abort the request by returning to the idle state.
234  return fsm_idle_state;
235 }
236 
237 static fsm_state_t *
238 fsm_fini_on_enter(fsm_t *fsm)
239 {
240  assert(fsm);
241 
242  // Perform finalization.
243  ...
244 
245  // This is a transient state; it automatically transitions to the idle
246  // state.
247  return fsm_idle_state;
248 }
249 ~~~
250 
251 [finite-state machines]: https://en.wikipedia.org/wiki/Finite-state_machine
252 [liblely-util]: https://gitlab.com/lely_industries/util
253 [state design pattern]: https://en.wikipedia.org/wiki/State_pattern
254 [state transition table]: https://en.wikipedia.org/wiki/State_transition_table
255