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