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