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:
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.
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.
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.
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
32 Example of a public C header (lely/lib/fsm.h):
34 // Check if the FSM is currently in the idle state.
35 int fsm_is_idle(const fsm_t *fsm);
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);
42 Example of the C implementation (fsm.c):
44 #include <lely/libc/time.h>
45 #include <lely/util/errnum.h>
47 // A state is a constant collection of function pointers.
49 typedef const struct __fsm_state fsm_state_t;
53 // A pointer to the current state.
58 // A helper function for entering a new state.
59 static void fsm_enter(fsm_t *fsm, fsm_state_t *next);
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);
65 // A helper function for generating a timeout event.
66 static inline void fsm_emit_time(fsm_t *fsm, const struct timespec *tp);
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);
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__ };
87 // The idle state does not contain any transition functions.
88 LELY_LIB_DEFINE_STATE(fsm_idle_state, NULL)
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
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
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
110 #undef LELY_LIB_DEFINE_STATE
113 __fsm_init(struct __fsm *fsm, Args... args)
118 // Enter the idle state and wait for the user to issue a request.
119 fsm_enter(fsm, fsm_idle_state);
125 fsm_is_idle(const fsm_t *fsm)
129 // States are uniquely identified by their address and can be compared
131 return fsm->state == fsm_idle_state;
135 fsm_req(fsm_t *fsm, Args... args)
139 if (!fsm_is_idle(fsm)) {
140 set_errnum(ERRNUM_INPROGRESS)
144 // Construct request based on args.
147 // Enter the initialization state.
148 fsm_enter(fsm, fsm_init_state);
153 fsm_enter(fsm_t *fsm, fsm_state_t *next)
157 // Use a loop to allow transient state sequences of arbitrary length.
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;
167 // Invoke the on_leave() function if specified.
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;
179 fsm_emit_event(fsm_t *fsm, Args... args)
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);
187 fsm_enter(fsm, fsm->state->on_event(fsm, args...));
191 fsm_emit_time(fsm_t *fsm, const struct timespec *tp)
195 assert(fsm->state->on_time);
197 fsm_enter(fsm, fsm->state->on_time(fsm, tp));
201 fsm_init_on_enter(fsm_t *fsm)
205 // Perform initialization.
208 // This is a transient state; it transitions to the waiting state
209 // without waiting for an event.
210 return fsm_wait_state;
214 fsm_wait_on_event(fsm_t *fsm, Args... args)
218 // Process the event.
221 // If the event completes the request, enter the finalization state.
222 return fsm_fini_state;
226 fsm_wait_on_time(fsm_t *fsm, const struct timespec *tp)
230 // A timeout occurred. Abort and clean up.
233 // Abort the request by returning to the idle state.
234 return fsm_idle_state;
238 fsm_fini_on_enter(fsm_t *fsm)
242 // Perform finalization.
245 // This is a transient state; it automatically transitions to the idle
247 return fsm_idle_state;
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