1.. _smf:
2
3State Machine Framework
4#######################
5
6.. highlight:: c
7
8Overview
9========
10
11The State Machine Framework (SMF) is an application agnostic framework that
12provides an easy way for developers to integrate state machines into their
13application. The framework can be added to any project by enabling the
14:kconfig:option:`CONFIG_SMF` option.
15
16State Creation
17==============
18
19A state is represented by three functions, where one function implements the
20Entry actions, another function implements the Run actions, and the last
21function implements the Exit actions. The prototype for these functions is as
22follows: ``void funct(void *obj)``, where the ``obj`` parameter is a user
23defined structure that has the state machine context, ``struct smf_ctx``, as
24its first member. For example::
25
26   struct user_object {
27      struct smf_ctx ctx;
28      /* All User Defined Data Follows */
29   };
30
31The ``struct smf_ctx`` member must be first because the state machine
32framework's functions casts the user defined object to the ``struct smf_ctx``
33type with the following macro: ``SMF_CTX(o)``
34
35For example instead of doing this ``(struct smf_ctx *)&user_obj``, you could
36use ``SMF_CTX(&user_obj)``.
37
38By default, a state can have no ancestor states, resulting in a flat state
39machine. But to enable the creation of a hierarchical state machine, the
40:kconfig:option:`CONFIG_SMF_ANCESTOR_SUPPORT` option must be enabled.
41
42The following macro can be used for easy state creation:
43
44* :c:macro:`SMF_CREATE_STATE` Create a state
45
46**NOTE:** The :c:macro:`SMF_CREATE_STATE` macro takes an additional parameter
47when :kconfig:option:`CONFIG_SMF_ANCESTOR_SUPPORT` is enabled.
48
49State Machine Creation
50======================
51
52A state machine is created by defining a table of states that's indexed by an
53enum. For example, the following creates three flat states::
54
55   enum demo_state { S0, S1, S2 };
56
57   const struct smf_state demo_states[] = {
58      [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit),
59      [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit),
60      [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit)
61   };
62
63And this example creates three hierarchical states::
64
65   enum demo_state { S0, S1, S2 };
66
67   const struct smf_state demo_states[] = {
68      [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit, parent_s0),
69      [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit, parent_s12),
70      [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit, parent_s12)
71   };
72
73
74To set the initial state, the ``smf_set_initial`` function should be
75called. It has the following prototype:
76``void smf_set_initial(smf_ctx *ctx, smf_state *state)``
77
78To transition from one state to another, the ``smf_set_state`` function is
79used and it has the following prototype:
80``void smf_set_state(smf_ctx *ctx, smf_state *state)``
81
82**NOTE:** While the state machine is running, smf_set_state should only be
83called from the Entry and Run functions. Calling smf_set_state from the Exit
84functions doesn't make sense and will generate a warning.
85
86State Machine Execution
87=======================
88
89To run the state machine, the ``smf_run_state`` function should be called in
90some application dependent way. An application should cease calling
91smf_run_state if it returns a non-zero value. The function has the following
92prototype: ``int32_t smf_run_state(smf_ctx *ctx)``
93
94State Machine Termination
95=========================
96
97To terminate the state machine, the ``smf_terminate`` function should be
98called. It can be called from the entry, run, or exit action. The function
99takes a non-zero user defined value that's returned by the ``smf_run_state``
100function. The function has the following prototype:
101``void smf_terminate(smf_ctx *ctx, int32_t val)``
102
103Flat State Machine Example
104==========================
105
106This example turns the following state diagram into code using the SMF, where
107the initial state is S0.
108
109.. graphviz::
110   :caption: Flat state machine diagram
111
112   digraph smf_flat {
113      node [style=rounded];
114      init [shape = point];
115      STATE_S0 [shape = box];
116      STATE_S1 [shape = box];
117      STATE_S2 [shape = box];
118
119      init -> STATE_S0;
120      STATE_S0 -> STATE_S1;
121      STATE_S1 -> STATE_S2;
122      STATE_S2 -> STATE_S0;
123   }
124
125Code::
126
127	#include <zephyr/smf.h>
128
129	/* Forward declaration of state table */
130	static const struct smf_state demo_states[];
131
132	/* List of demo states */
133	enum demo_state { S0, S1, S2 };
134
135	/* User defined object */
136	struct s_object {
137		/* This must be first */
138		struct smf_ctx ctx;
139
140		/* Other state specific data add here */
141	} s_obj;
142
143	/* State S0 */
144	static void s0_entry(void *o)
145	{
146		/* Do something */
147	}
148	static void s0_run(void *o)
149	{
150		smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]);
151	}
152	static void s0_exit(void *o)
153	{
154		/* Do something */
155	}
156
157	/* State S1 */
158	static void s1_run(void *o)
159	{
160		smf_set_state(SMF_CTX(&s_obj), &demo_states[S2]);
161	}
162	static void s1_exit(void *o)
163	{
164		/* Do something */
165	}
166
167	/* State S2 */
168	static void s2_entry(void *o)
169	{
170		/* Do something */
171	}
172	static void s2_run(void *o)
173	{
174		smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]);
175	}
176
177	/* Populate state table */
178	static const struct smf_state demo_states[] = {
179		[S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit),
180		/* State S1 does not have an entry action */
181		[S1] = SMF_CREATE_STATE(NULL, s1_run, s1_exit),
182		/* State S2 does not have an exit action */
183		[S2] = SMF_CREATE_STATE(s2_entry, s2_run, NULL),
184	};
185
186	int main(void)
187	{
188		int32_t ret;
189
190		/* Set initial state */
191		smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]);
192
193		/* Run the state machine */
194		while(1) {
195			/* State machine terminates if a non-zero value is returned */
196			ret = smf_run_state(SMF_CTX(&s_obj));
197			if (ret) {
198				/* handle return code and terminate state machine */
199				break;
200			}
201			k_msleep(1000);
202		}
203	}
204
205Hierarchical State Machine Example
206==================================
207
208This example turns the following state diagram into code using the SMF, where
209S0 and S1 share a parent state and S0 is the initial state.
210
211
212.. graphviz::
213   :caption: Hierarchical state machine diagram
214
215   digraph smf_hierarchical {
216      node [style = rounded];
217      init [shape = point];
218      STATE_S0 [shape = box];
219      STATE_S1 [shape = box];
220      STATE_S2 [shape = box];
221
222      subgraph cluster_0 {
223         label = "PARENT";
224         style = rounded;
225         STATE_S0 -> STATE_S1;
226      }
227
228      init -> STATE_S0;
229      STATE_S1 -> STATE_S2;
230      STATE_S2 -> STATE_S0;
231   }
232
233Code::
234
235	#include <zephyr/smf.h>
236
237	/* Forward declaration of state table */
238	static const struct smf_state demo_states[];
239
240	/* List of demo states */
241	enum demo_state { PARENT, S0, S1, S2 };
242
243	/* User defined object */
244	struct s_object {
245		/* This must be first */
246		struct smf_ctx ctx;
247
248		/* Other state specific data add here */
249	} s_obj;
250
251	/* Parent State */
252	static void parent_entry(void *o)
253	{
254		/* Do something */
255	}
256	static void parent_exit(void *o)
257	{
258		/* Do something */
259	}
260
261	/* State S0 */
262	static void s0_run(void *o)
263	{
264		smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]);
265	}
266
267	/* State S1 */
268	static void s1_run(void *o)
269	{
270		smf_set_state(SMF_CTX(&s_obj), &demo_states[S2]);
271	}
272
273	/* State S2 */
274	static void s2_run(void *o)
275	{
276		smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]);
277	}
278
279	/* Populate state table */
280	static const struct smf_state demo_states[] = {
281		/* Parent state does not have a run action */
282		[PARENT] = SMF_CREATE_STATE(parent_entry, NULL, parent_exit, NULL),
283		/* Child states do not have entry or exit actions */
284		[S0] = SMF_CREATE_STATE(NULL, s0_run, NULL, &demo_states[PARENT]),
285		[S1] = SMF_CREATE_STATE(NULL, s1_run, NULL, &demo_states[PARENT]),
286		/* State S2 do ot have entry or exit actions and no parent */
287		[S2] = SMF_CREATE_STATE(NULL, s2_run, NULL, NULL),
288	};
289
290	int main(void)
291	{
292		int32_t ret;
293
294		/* Set initial state */
295		smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]);
296
297		/* Run the state machine */
298		while(1) {
299			/* State machine terminates if a non-zero value is returned */
300			ret = smf_run_state(SMF_CTX(&s_obj));
301			if (ret) {
302				/* handle return code and terminate state machine */
303				break;
304			}
305			k_msleep(1000);
306		}
307	}
308
309When designing hierarchical state machines, the following should be considered:
310 - Ancestor entry actions are executed before the sibling entry actions. For
311   example, the parent_entry function is called before the s0_entry function.
312 - Transitioning from one sibling to another with a shared ancestry does not
313   re-execute the ancestor\'s entry action or execute the exit action.
314   For example, the parent_entry function is not called when transitioning
315   from S0 to S1, nor is the parent_exit function called.
316 - Ancestor exit actions are executed after the sibling exit actions. For
317   example, the s1_exit function is called before the parent_exit function
318   is called.
319 - The parent_run function only executes if the child_run function returns
320   without transitioning to another state, ie. calling smf_set_state.
321
322Event Driven State Machine Example
323==================================
324
325Events are not explicitly part of the State Machine Framework but an event driven
326state machine can be implemented using Zephyr :ref:`events`.
327
328.. graphviz::
329   :caption: Event driven state machine diagram
330
331   digraph smf_flat {
332      node [style=rounded];
333      init [shape = point];
334      STATE_S0 [shape = box];
335      STATE_S1 [shape = box];
336
337      init -> STATE_S0;
338      STATE_S0 -> STATE_S1 [label = "BTN EVENT"];
339      STATE_S1 -> STATE_S0 [label = "BTN EVENT"];
340   }
341
342Code::
343
344	#include <zephyr/kernel.h>
345	#include <zephyr/drivers/gpio.h>
346	#include <zephyr/smf.h>
347
348	#define SW0_NODE        DT_ALIAS(sw0)
349
350	/* List of events */
351	#define EVENT_BTN_PRESS BIT(0)
352
353	static const struct gpio_dt_spec button =
354		GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
355
356	static struct gpio_callback button_cb_data;
357
358	/* Forward declaration of state table */
359	static const struct smf_state demo_states[];
360
361	/* List of demo states */
362	enum demo_state { S0, S1 };
363
364	/* User defined object */
365	struct s_object {
366		/* This must be first */
367		struct smf_ctx ctx;
368
369		/* Events */
370		struct k_event smf_event;
371		int32_t events;
372
373		/* Other state specific data add here */
374	} s_obj;
375
376	/* State S0 */
377	static void s0_entry(void *o)
378	{
379		printk("STATE0\n");
380	}
381
382	static void s0_run(void *o)
383	{
384		struct s_object *s = (struct s_object *)o;
385
386		/* Change states on Button Press Event */
387		if (s->events & EVENT_BTN_PRESS) {
388			smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]);
389		}
390	}
391
392	/* State S1 */
393	static void s1_entry(void *o)
394	{
395		printk("STATE1\n");
396	}
397
398	static void s1_run(void *o)
399	{
400		struct s_object *s = (struct s_object *)o;
401
402		/* Change states on Button Press Event */
403		if (s->events & EVENT_BTN_PRESS) {
404			smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]);
405		}
406	}
407
408	/* Populate state table */
409	static const struct smf_state demo_states[] = {
410		[S0] = SMF_CREATE_STATE(s0_entry, s0_run, NULL),
411		[S1] = SMF_CREATE_STATE(s1_entry, s1_run, NULL),
412	};
413
414	void button_pressed(const struct device *dev,
415			struct gpio_callback *cb, uint32_t pins)
416	{
417		/* Generate Button Press Event */
418		k_event_post(&s_obj.smf_event, EVENT_BTN_PRESS);
419	}
420
421	int main(void)
422	{
423		int ret;
424
425		if (!gpio_is_ready_dt(&button)) {
426			printk("Error: button device %s is not ready\n",
427				button.port->name);
428			return;
429		}
430
431		ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
432		if (ret != 0) {
433			printk("Error %d: failed to configure %s pin %d\n",
434				ret, button.port->name, button.pin);
435			return;
436		}
437
438		ret = gpio_pin_interrupt_configure_dt(&button,
439			GPIO_INT_EDGE_TO_ACTIVE);
440		if (ret != 0) {
441			printk("Error %d: failed to configure interrupt on %s pin %d\n",
442				ret, button.port->name, button.pin);
443			return;
444		}
445
446		gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
447		gpio_add_callback(button.port, &button_cb_data);
448
449		/* Initialize the event */
450		k_event_init(&s_obj.smf_event);
451
452		/* Set initial state */
453		smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]);
454
455		/* Run the state machine */
456		while(1) {
457			/* Block until an event is detected */
458			s_obj.events = k_event_wait(&s_obj.smf_event,
459					EVENT_BTN_PRESS, true, K_FOREVER);
460
461			/* State machine terminates if a non-zero value is returned */
462			ret = smf_run_state(SMF_CTX(&s_obj));
463			if (ret) {
464				/* handle return code and terminate state machine */
465				break;
466			}
467		}
468	}
469