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 the entry and exit
22functions are as follows: ``void funct(void *obj)``, and the prototype for the
23run action is ``enum smf_state_result funct(void *obj)`` where the ``obj``
24parameter is a user defined structure that has the state machine context,
25:c:struct:`smf_ctx`, as its first member. For example::
26
27   struct user_object {
28      struct smf_ctx ctx;
29      /* All User Defined Data Follows */
30   };
31
32The :c:struct:`smf_ctx` member must be first because the state machine
33framework's functions casts the user defined object to the :c:struct:`smf_ctx`
34type with the :c:macro:`SMF_CTX` macro.
35
36For example instead of doing this ``(struct smf_ctx *)&user_obj``, you could
37use ``SMF_CTX(&user_obj)``.
38
39By default, a state can have no ancestor states, resulting in a flat state
40machine. But to enable the creation of a hierarchical state machine, the
41:kconfig:option:`CONFIG_SMF_ANCESTOR_SUPPORT` option must be enabled.
42
43The return value of the run action, :c:enum:`smf_state_result` determines if the
44state machine propagates the event to parent run actions
45(:c:enum:`SMF_EVENT_PROPAGATE`) or if the event was handled by the run action
46(:c:enum:`SMF_EVENT_HANDLED`). Flat state machines do not have parent actions,
47so the return code is ignored; returning :c:enum:`SMF_EVENT_HANDLED` is
48recommended.
49
50Calling :c:func:`smf_set_state` prevents calling parent run
51actions, even if :c:enum:`SMF_EVENT_PROPAGATE` is returned.
52
53By default, the hierarchical state machines do not support initial transitions
54to child states on entering a superstate. To enable them the
55:kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION` option must be enabled.
56
57The following macro can be used for easy state creation:
58
59* :c:macro:`SMF_CREATE_STATE` Create a state
60
61State Machine Creation
62======================
63
64A state machine is created by defining a table of states that's indexed by an
65enum. For example, the following creates three flat states::
66
67   enum demo_state { S0, S1, S2 };
68
69   const struct smf_state demo_states[] = {
70      [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit, NULL, NULL),
71      [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit, NULL, NULL),
72      [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit, NULL, NULL)
73   };
74
75And this example creates three hierarchical states::
76
77   enum demo_state { S0, S1, S2 };
78
79   const struct smf_state demo_states[] = {
80      [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit, parent_s0, NULL),
81      [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit, parent_s12, NULL),
82      [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit, parent_s12, NULL)
83   };
84
85
86This example creates three hierarchical states with an initial transition
87from parent state S0 to child state S2::
88
89   enum demo_state { S0, S1, S2 };
90
91   /* Forward declaration of state table */
92   const struct smf_state demo_states[];
93
94   const struct smf_state demo_states[] = {
95      [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit, NULL, demo_states[S2]),
96      [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit, demo_states[S0], NULL),
97      [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit, demo_states[S0], NULL)
98   };
99
100To set the initial state, the :c:func:`smf_set_initial` function should be
101called.
102
103To transition from one state to another, the :c:func:`smf_set_state`
104function is used.
105
106.. note:: If :kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION` is not set,
107   :c:func:`smf_set_initial` and :c:func:`smf_set_state` function should
108   not be passed a parent state as the parent state does not know which
109   child state to transition to. Transitioning to a parent state is OK
110   if an initial transition to a child state is defined. A well-formed
111   HSM should have initial transitions defined for all parent states.
112
113.. note:: While the state machine is running, :c:func:`smf_set_state` should
114   only be called from the Entry or Run function. Calling
115   :c:func:`smf_set_state` from Exit functions will generate a warning in the
116   log and no transition will occur.
117
118State Machine Execution
119=======================
120
121To run the state machine, the :c:func:`smf_run_state` function should be
122called in some application dependent way. An application should cease calling
123smf_run_state if it returns a non-zero value.
124
125State Machine Termination
126=========================
127
128To terminate the state machine, the :c:func:`smf_set_terminate` function
129should be called. It can be called from the entry, run, or exit actions. The
130function takes a non-zero user defined value that will be returned by the
131:c:func:`smf_run_state` function.
132
133UML State Machines
134==================
135
136SMF follows UML hierarchical state machine rules for transitions i.e., the
137entry and exit actions of the least common ancestor are not executed on
138transition, unless said transition is a transition to self.
139
140The UML Specification for StateMachines may be found in chapter 14 of the UML
141specification available here: https://www.omg.org/spec/UML/
142
143SMF breaks from UML rules in:
144
1451. Executing the actions associated with the transition within the context
146   of the source state, rather than after the exit actions are performed.
1472. Only allowing external transitions to self, not to sub-states. A transition
148   from a superstate to a child state is treated as a local transition.
1493. Prohibiting transitions using :c:func:`smf_set_state` in exit actions.
150
151SMF also does not provide any pseudostates except the Initial Pseudostate.
152Terminate pseudostates can be modelled by calling  :c:func:`smf_set_terminate`
153from the entry action of a 'terminate' state. Orthogonal regions are modelled
154by calling :c:func:`smf_run_state` for each region.
155
156State Machine Examples
157======================
158
159Flat State Machine Example
160**************************
161
162This example turns the following state diagram into code using the SMF, where
163the initial state is S0.
164
165.. graphviz::
166   :caption: Flat state machine diagram
167
168   digraph smf_flat {
169      node [style=rounded];
170      init [shape = point];
171      STATE_S0 [shape = box];
172      STATE_S1 [shape = box];
173      STATE_S2 [shape = box];
174
175      init -> STATE_S0;
176      STATE_S0 -> STATE_S1;
177      STATE_S1 -> STATE_S2;
178      STATE_S2 -> STATE_S0;
179   }
180
181Code::
182
183	#include <zephyr/smf.h>
184
185	/* Forward declaration of state table */
186	static const struct smf_state demo_states[];
187
188	/* List of demo states */
189	enum demo_state { S0, S1, S2 };
190
191	/* User defined object */
192	struct s_object {
193		/* This must be first */
194		struct smf_ctx ctx;
195
196		/* Other state specific data add here */
197	} s_obj;
198
199	/* State S0 */
200	static void s0_entry(void *o)
201	{
202		/* Do something */
203	}
204	static enum smf_state_result s0_run(void *o)
205	{
206		smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]);
207		return SMF_EVENT_HANDLED;
208	}
209	static void s0_exit(void *o)
210	{
211		/* Do something */
212	}
213
214	/* State S1 */
215	static enum smf_state_result s1_run(void *o)
216	{
217		smf_set_state(SMF_CTX(&s_obj), &demo_states[S2]);
218		return SMF_EVENT_HANDLED;
219	}
220	static void s1_exit(void *o)
221	{
222		/* Do something */
223	}
224
225	/* State S2 */
226	static void s2_entry(void *o)
227	{
228		/* Do something */
229	}
230	static enum smf_state_result s2_run(void *o)
231	{
232		smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]);
233		return SMF_EVENT_HANDLED;
234	}
235
236	/* Populate state table */
237	static const struct smf_state demo_states[] = {
238		[S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit, NULL, NULL),
239		/* State S1 does not have an entry action */
240		[S1] = SMF_CREATE_STATE(NULL, s1_run, s1_exit, NULL, NULL),
241		/* State S2 does not have an exit action */
242		[S2] = SMF_CREATE_STATE(s2_entry, s2_run, NULL, NULL, NULL),
243	};
244
245	int main(void)
246	{
247		int32_t ret;
248
249		/* Set initial state */
250		smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]);
251
252		/* Run the state machine */
253		while(1) {
254			/* State machine terminates if a non-zero value is returned */
255			ret = smf_run_state(SMF_CTX(&s_obj));
256			if (ret) {
257				/* handle return code and terminate state machine */
258				break;
259			}
260			k_msleep(1000);
261		}
262	}
263
264Hierarchical State Machine Example
265**********************************
266
267This example turns the following state diagram into code using the SMF, where
268S0 and S1 share a parent state and S0 is the initial state.
269
270
271.. graphviz::
272   :caption: Hierarchical state machine diagram
273
274   digraph smf_hierarchical {
275      node [style = rounded];
276      init [shape = point];
277      STATE_S0 [shape = box];
278      STATE_S1 [shape = box];
279      STATE_S2 [shape = box];
280
281      subgraph cluster_0 {
282         label = "PARENT";
283         style = rounded;
284         STATE_S0 -> STATE_S1;
285      }
286
287      init -> STATE_S0;
288      STATE_S1 -> STATE_S2;
289      STATE_S2 -> STATE_S0;
290   }
291
292Code::
293
294	#include <zephyr/smf.h>
295
296	/* Forward declaration of state table */
297	static const struct smf_state demo_states[];
298
299	/* List of demo states */
300	enum demo_state { PARENT, S0, S1, S2 };
301
302	/* User defined object */
303	struct s_object {
304		/* This must be first */
305		struct smf_ctx ctx;
306
307		/* Other state specific data add here */
308	} s_obj;
309
310	/* Parent State */
311	static void parent_entry(void *o)
312	{
313		/* Do something */
314	}
315	static void parent_exit(void *o)
316	{
317		/* Do something */
318	}
319
320	/* State S0 */
321	static enum smf_state_result s0_run(void *o)
322	{
323		smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]);
324		return SMF_EVENT_HANDLED;
325	}
326
327	/* State S1 */
328	static enum smf_state_result s1_run(void *o)
329	{
330		smf_set_state(SMF_CTX(&s_obj), &demo_states[S2]);
331		return SMF_EVENT_HANDLED;
332	}
333
334	/* State S2 */
335	static enum smf_state_result s2_run(void *o)
336	{
337		smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]);
338		return SMF_EVENT_HANDLED;
339	}
340
341	/* Populate state table */
342	static const struct smf_state demo_states[] = {
343		/* Parent state does not have a run action */
344		[PARENT] = SMF_CREATE_STATE(parent_entry, NULL, parent_exit, NULL, NULL),
345		/* Child states do not have entry or exit actions */
346		[S0] = SMF_CREATE_STATE(NULL, s0_run, NULL, &demo_states[PARENT], NULL),
347		[S1] = SMF_CREATE_STATE(NULL, s1_run, NULL, &demo_states[PARENT], NULL),
348		/* State S2 do not have entry or exit actions and no parent */
349		[S2] = SMF_CREATE_STATE(NULL, s2_run, NULL, NULL, NULL),
350	};
351
352	int main(void)
353	{
354		int32_t ret;
355
356		/* Set initial state */
357		smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]);
358
359		/* Run the state machine */
360		while(1) {
361			/* State machine terminates if a non-zero value is returned */
362			ret = smf_run_state(SMF_CTX(&s_obj));
363			if (ret) {
364				/* handle return code and terminate state machine */
365				break;
366			}
367			k_msleep(1000);
368		}
369	}
370
371When designing hierarchical state machines, the following should be considered:
372 - Ancestor entry actions are executed before the sibling entry actions. For
373   example, the parent_entry function is called before the s0_entry function.
374 - Transitioning from one sibling to another with a shared ancestry does not
375   re-execute the ancestor\'s entry action or execute the exit action.
376   For example, the parent_entry function is not called when transitioning
377   from S0 to S1, nor is the parent_exit function called.
378 - Ancestor exit actions are executed after the exit action of the current
379   state. For example, the s1_exit function is called before the parent_exit
380   function is called.
381 - The parent_run function only executes if the child_run function does not
382   call either :c:func:`smf_set_state` or return :c:enum:`SMF_EVENT_HANDLED`.
383
384Event Driven State Machine Example
385**********************************
386
387Events are not explicitly part of the State Machine Framework but an event driven
388state machine can be implemented using Zephyr :ref:`events`.
389
390.. graphviz::
391   :caption: Event driven state machine diagram
392
393   digraph smf_flat {
394      node [style=rounded];
395      init [shape = point];
396      STATE_S0 [shape = box];
397      STATE_S1 [shape = box];
398
399      init -> STATE_S0;
400      STATE_S0 -> STATE_S1 [label = "BTN EVENT"];
401      STATE_S1 -> STATE_S0 [label = "BTN EVENT"];
402   }
403
404Code::
405
406	#include <zephyr/kernel.h>
407	#include <zephyr/drivers/gpio.h>
408	#include <zephyr/smf.h>
409
410	#define SW0_NODE        DT_ALIAS(sw0)
411
412	/* List of events */
413	#define EVENT_BTN_PRESS BIT(0)
414
415	static const struct gpio_dt_spec button =
416		GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
417
418	static struct gpio_callback button_cb_data;
419
420	/* Forward declaration of state table */
421	static const struct smf_state demo_states[];
422
423	/* List of demo states */
424	enum demo_state { S0, S1 };
425
426	/* User defined object */
427	struct s_object {
428		/* This must be first */
429		struct smf_ctx ctx;
430
431		/* Events */
432		struct k_event smf_event;
433		int32_t events;
434
435		/* Other state specific data add here */
436	} s_obj;
437
438	/* State S0 */
439	static void s0_entry(void *o)
440	{
441		printk("STATE0\n");
442	}
443
444	static void s0_run(void *o)
445	{
446		struct s_object *s = (struct s_object *)o;
447
448		/* Change states on Button Press Event */
449		if (s->events & EVENT_BTN_PRESS) {
450			smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]);
451		}
452		return SMF_EVENT_HANDLED;
453	}
454
455	/* State S1 */
456	static void s1_entry(void *o)
457	{
458		printk("STATE1\n");
459	}
460
461	static void s1_run(void *o)
462	{
463		struct s_object *s = (struct s_object *)o;
464
465		/* Change states on Button Press Event */
466		if (s->events & EVENT_BTN_PRESS) {
467			smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]);
468		}
469		return SMF_EVENT_HANDLED;
470	}
471
472	/* Populate state table */
473	static const struct smf_state demo_states[] = {
474		[S0] = SMF_CREATE_STATE(s0_entry, s0_run, NULL, NULL, NULL),
475		[S1] = SMF_CREATE_STATE(s1_entry, s1_run, NULL, NULL, NULL),
476	};
477
478	void button_pressed(const struct device *dev,
479			struct gpio_callback *cb, uint32_t pins)
480	{
481		/* Generate Button Press Event */
482		k_event_post(&s_obj.smf_event, EVENT_BTN_PRESS);
483	}
484
485	int main(void)
486	{
487		int ret;
488
489		if (!gpio_is_ready_dt(&button)) {
490			printk("Error: button device %s is not ready\n",
491				button.port->name);
492			return;
493		}
494
495		ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
496		if (ret != 0) {
497			printk("Error %d: failed to configure %s pin %d\n",
498				ret, button.port->name, button.pin);
499			return;
500		}
501
502		ret = gpio_pin_interrupt_configure_dt(&button,
503			GPIO_INT_EDGE_TO_ACTIVE);
504		if (ret != 0) {
505			printk("Error %d: failed to configure interrupt on %s pin %d\n",
506				ret, button.port->name, button.pin);
507			return;
508		}
509
510		gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
511		gpio_add_callback(button.port, &button_cb_data);
512
513		/* Initialize the event */
514		k_event_init(&s_obj.smf_event);
515
516		/* Set initial state */
517		smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]);
518
519		/* Run the state machine */
520		while(1) {
521			/* Block until an event is detected */
522			s_obj.events = k_event_wait(&s_obj.smf_event,
523					EVENT_BTN_PRESS, true, K_FOREVER);
524
525			/* State machine terminates if a non-zero value is returned */
526			ret = smf_run_state(SMF_CTX(&s_obj));
527			if (ret) {
528				/* handle return code and terminate state machine */
529				break;
530			}
531		}
532	}
533
534State Machine Example With Initial Transitions And Transition To Self
535*********************************************************************
536
537:zephyr_file:`tests/lib/smf/src/test_lib_self_transition_smf.c` defines a state
538machine for testing the initial transitions and transitions to self in a parent
539state. The statechart for this test is below.
540
541
542.. graphviz::
543   :caption: Test state machine for UML State Transitions
544
545   digraph smf_hierarchical_initial {
546      compound=true;
547      node [style = rounded];
548      "smf_set_initial()" [shape=plaintext fontname=Courier];
549      ab_init_state [shape = point];
550      STATE_A [shape = box];
551      STATE_B [shape = box];
552      STATE_C [shape = box];
553      STATE_D [shape = box];
554      DC[shape=point height=0 width=0 label=<>]
555
556      subgraph cluster_root {
557         label = "ROOT";
558         style = rounded;
559
560         subgraph cluster_ab {
561            label = "PARENT_AB";
562            style = rounded;
563            ab_init_state -> STATE_A;
564            STATE_A -> STATE_B;
565         }
566
567         subgraph cluster_c {
568            label = "PARENT_C";
569            style = rounded;
570            STATE_B -> STATE_C [ltail=cluster_ab]
571         }
572
573         STATE_C -> DC [ltail=cluster_c, dir=none];
574         DC -> STATE_C [lhead=cluster_c];
575         STATE_C -> STATE_D
576      }
577
578      "smf_set_initial()" -> STATE_A [lhead=cluster_ab]
579   }
580
581
582API Reference
583=============
584
585.. doxygengroup:: smf
586