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