This is the documentation for the latest (main) development branch of Zephyr. If you are looking for the documentation of previous releases, use the drop-down menu on the left and select the desired version.

State Machine Framework

Overview

The State Machine Framework (SMF) is an application agnostic framework that provides an easy way for developers to integrate state machines into their application. The framework can be added to any project by enabling the CONFIG_SMF option.

State Creation

A state is represented by three functions, where one function implements the Entry actions, another function implements the Run actions, and the last function implements the Exit actions. The prototype for these functions is as follows: void funct(void *obj), where the obj parameter is a user defined structure that has the state machine context, smf_ctx, as its first member. For example:

struct user_object {
   struct smf_ctx ctx;
   /* All User Defined Data Follows */
};

The smf_ctx member must be first because the state machine framework’s functions casts the user defined object to the smf_ctx type with the SMF_CTX macro.

For example instead of doing this (struct smf_ctx *)&user_obj, you could use SMF_CTX(&user_obj).

By default, a state can have no ancestor states, resulting in a flat state machine. But to enable the creation of a hierarchical state machine, the CONFIG_SMF_ANCESTOR_SUPPORT option must be enabled.

By default, the hierarchical state machine does not support initial transitions to child states on entering a superstate. To enable them the CONFIG_SMF_INITIAL_TRANSITION option must be enabled.

The following macro can be used for easy state creation:

Note

The SMF_CREATE_STATE macro takes an additional parameter for the parent state when CONFIG_SMF_ANCESTOR_SUPPORT is enabled . The SMF_CREATE_STATE macro takes two additional parameters for the parent state and initial transition when the CONFIG_SMF_INITIAL_TRANSITION option is enabled.

State Machine Creation

A state machine is created by defining a table of states that’s indexed by an enum. For example, the following creates three flat states:

enum demo_state { S0, S1, S2 };

const struct smf_state demo_states[] = {
   [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit),
   [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit),
   [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit)
};

And this example creates three hierarchical states:

enum demo_state { S0, S1, S2 };

const struct smf_state demo_states[] = {
   [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit, parent_s0),
   [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit, parent_s12),
   [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit, parent_s12)
};

This example creates three hierarchical states with an initial transition from parent state S0 to child state S2:

enum demo_state { S0, S1, S2 };

/* Forward declaration of state table */
const struct smf_state demo_states[];

const struct smf_state demo_states[] = {
   [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit, NULL, demo_states[S2]),
   [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit, demo_states[S0], NULL),
   [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit, demo_states[S0], NULL)
};

To set the initial state, the smf_set_initial() function should be called. It has the following prototype: void smf_set_initial(smf_ctx *ctx, smf_state *state)

To transition from one state to another, the smf_set_state() function is used and it has the following prototype: void smf_set_state(smf_ctx *ctx, smf_state *state)

Note

If CONFIG_SMF_INITIAL_TRANSITION is not set, smf_set_initial() and smf_set_state() function should not be passed a parent state as the parent state does not know which child state to transition to. Transitioning to a parent state is OK if an initial transition to a child state is defined. A well-formed HSM will have initial transitions defined for all parent states.

Note

While the state machine is running, smf_set_state should only be called from the Entry and Run functions. Calling smf_set_state from the Exit functions doesn’t make sense and will generate a warning.

State Machine Execution

To run the state machine, the smf_run_state() function should be called in some application dependent way. An application should cease calling smf_run_state if it returns a non-zero value. The function has the following prototype: int32_t smf_run_state(smf_ctx *ctx)

Preventing Parent Run Actions

Calling smf_set_handled() prevents calling the run action of parent states. It is not required to call smf_set_handled() if the state calls smf_set_state().

State Machine Termination

To terminate the state machine, the smf_set_terminate() function should be called. It can be called from the entry, run, or exit action. The function takes a non-zero user defined value that’s returned by the smf_run_state() function. The function has the following prototype: void smf_set_terminate(smf_ctx *ctx, int32_t val)

Flat State Machine Example

This example turns the following state diagram into code using the SMF, where the initial state is S0.

digraph smf_flat { node [style=rounded]; init [shape = point]; STATE_S0 [shape = box]; STATE_S1 [shape = box]; STATE_S2 [shape = box]; init -> STATE_S0; STATE_S0 -> STATE_S1; STATE_S1 -> STATE_S2; STATE_S2 -> STATE_S0; }

Flat state machine diagram

Code:

#include <zephyr/smf.h>

/* Forward declaration of state table */
static const struct smf_state demo_states[];

/* List of demo states */
enum demo_state { S0, S1, S2 };

/* User defined object */
struct s_object {
        /* This must be first */
        struct smf_ctx ctx;

        /* Other state specific data add here */
} s_obj;

/* State S0 */
static void s0_entry(void *o)
{
        /* Do something */
}
static void s0_run(void *o)
{
        smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]);
}
static void s0_exit(void *o)
{
        /* Do something */
}

/* State S1 */
static void s1_run(void *o)
{
        smf_set_state(SMF_CTX(&s_obj), &demo_states[S2]);
}
static void s1_exit(void *o)
{
        /* Do something */
}

/* State S2 */
static void s2_entry(void *o)
{
        /* Do something */
}
static void s2_run(void *o)
{
        smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]);
}

/* Populate state table */
static const struct smf_state demo_states[] = {
        [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit),
        /* State S1 does not have an entry action */
        [S1] = SMF_CREATE_STATE(NULL, s1_run, s1_exit),
        /* State S2 does not have an exit action */
        [S2] = SMF_CREATE_STATE(s2_entry, s2_run, NULL),
};

int main(void)
{
        int32_t ret;

        /* Set initial state */
        smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]);

        /* Run the state machine */
        while(1) {
                /* State machine terminates if a non-zero value is returned */
                ret = smf_run_state(SMF_CTX(&s_obj));
                if (ret) {
                        /* handle return code and terminate state machine */
                        break;
                }
                k_msleep(1000);
        }
}

Hierarchical State Machine Example

This example turns the following state diagram into code using the SMF, where S0 and S1 share a parent state and S0 is the initial state.

digraph smf_hierarchical { node [style = rounded]; init [shape = point]; STATE_S0 [shape = box]; STATE_S1 [shape = box]; STATE_S2 [shape = box]; subgraph cluster_0 { label = "PARENT"; style = rounded; STATE_S0 -> STATE_S1; } init -> STATE_S0; STATE_S1 -> STATE_S2; STATE_S2 -> STATE_S0; }

Hierarchical state machine diagram

Code:

#include <zephyr/smf.h>

/* Forward declaration of state table */
static const struct smf_state demo_states[];

/* List of demo states */
enum demo_state { PARENT, S0, S1, S2 };

/* User defined object */
struct s_object {
        /* This must be first */
        struct smf_ctx ctx;

        /* Other state specific data add here */
} s_obj;

/* Parent State */
static void parent_entry(void *o)
{
        /* Do something */
}
static void parent_exit(void *o)
{
        /* Do something */
}

/* State S0 */
static void s0_run(void *o)
{
        smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]);
}

/* State S1 */
static void s1_run(void *o)
{
        smf_set_state(SMF_CTX(&s_obj), &demo_states[S2]);
}

/* State S2 */
static void s2_run(void *o)
{
        smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]);
}

/* Populate state table */
static const struct smf_state demo_states[] = {
        /* Parent state does not have a run action */
        [PARENT] = SMF_CREATE_STATE(parent_entry, NULL, parent_exit, NULL),
        /* Child states do not have entry or exit actions */
        [S0] = SMF_CREATE_STATE(NULL, s0_run, NULL, &demo_states[PARENT]),
        [S1] = SMF_CREATE_STATE(NULL, s1_run, NULL, &demo_states[PARENT]),
        /* State S2 do ot have entry or exit actions and no parent */
        [S2] = SMF_CREATE_STATE(NULL, s2_run, NULL, NULL),
};

int main(void)
{
        int32_t ret;

        /* Set initial state */
        smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]);

        /* Run the state machine */
        while(1) {
                /* State machine terminates if a non-zero value is returned */
                ret = smf_run_state(SMF_CTX(&s_obj));
                if (ret) {
                        /* handle return code and terminate state machine */
                        break;
                }
                k_msleep(1000);
        }
}
When designing hierarchical state machines, the following should be considered:
  • Ancestor entry actions are executed before the sibling entry actions. For example, the parent_entry function is called before the s0_entry function.

  • Transitioning from one sibling to another with a shared ancestry does not re-execute the ancestor's entry action or execute the exit action. For example, the parent_entry function is not called when transitioning from S0 to S1, nor is the parent_exit function called.

  • Ancestor exit actions are executed after the sibling exit actions. For example, the s1_exit function is called before the parent_exit function is called.

  • The parent_run function only executes if the child_run function does not call either smf_set_state() or smf_set_handled().

  • Transitions to self in super-states containing sub-states are not supported. Transitions to self from the most-nested child state are supported and will call the exit and entry function of the child state correctly.

Event Driven State Machine Example

Events are not explicitly part of the State Machine Framework but an event driven state machine can be implemented using Zephyr Events.

digraph smf_flat { node [style=rounded]; init [shape = point]; STATE_S0 [shape = box]; STATE_S1 [shape = box]; init -> STATE_S0; STATE_S0 -> STATE_S1 [label = "BTN EVENT"]; STATE_S1 -> STATE_S0 [label = "BTN EVENT"]; }

Event driven state machine diagram

Code:

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/smf.h>

#define SW0_NODE        DT_ALIAS(sw0)

/* List of events */
#define EVENT_BTN_PRESS BIT(0)

static const struct gpio_dt_spec button =
        GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});

static struct gpio_callback button_cb_data;

/* Forward declaration of state table */
static const struct smf_state demo_states[];

/* List of demo states */
enum demo_state { S0, S1 };

/* User defined object */
struct s_object {
        /* This must be first */
        struct smf_ctx ctx;

        /* Events */
        struct k_event smf_event;
        int32_t events;

        /* Other state specific data add here */
} s_obj;

/* State S0 */
static void s0_entry(void *o)
{
        printk("STATE0\n");
}

static void s0_run(void *o)
{
        struct s_object *s = (struct s_object *)o;

        /* Change states on Button Press Event */
        if (s->events & EVENT_BTN_PRESS) {
                smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]);
        }
}

/* State S1 */
static void s1_entry(void *o)
{
        printk("STATE1\n");
}

static void s1_run(void *o)
{
        struct s_object *s = (struct s_object *)o;

        /* Change states on Button Press Event */
        if (s->events & EVENT_BTN_PRESS) {
                smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]);
        }
}

/* Populate state table */
static const struct smf_state demo_states[] = {
        [S0] = SMF_CREATE_STATE(s0_entry, s0_run, NULL),
        [S1] = SMF_CREATE_STATE(s1_entry, s1_run, NULL),
};

void button_pressed(const struct device *dev,
                struct gpio_callback *cb, uint32_t pins)
{
        /* Generate Button Press Event */
        k_event_post(&s_obj.smf_event, EVENT_BTN_PRESS);
}

int main(void)
{
        int ret;

        if (!gpio_is_ready_dt(&button)) {
                printk("Error: button device %s is not ready\n",
                        button.port->name);
                return;
        }

        ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
        if (ret != 0) {
                printk("Error %d: failed to configure %s pin %d\n",
                        ret, button.port->name, button.pin);
                return;
        }

        ret = gpio_pin_interrupt_configure_dt(&button,
                GPIO_INT_EDGE_TO_ACTIVE);
        if (ret != 0) {
                printk("Error %d: failed to configure interrupt on %s pin %d\n",
                        ret, button.port->name, button.pin);
                return;
        }

        gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
        gpio_add_callback(button.port, &button_cb_data);

        /* Initialize the event */
        k_event_init(&s_obj.smf_event);

        /* Set initial state */
        smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]);

        /* Run the state machine */
        while(1) {
                /* Block until an event is detected */
                s_obj.events = k_event_wait(&s_obj.smf_event,
                                EVENT_BTN_PRESS, true, K_FOREVER);

                /* State machine terminates if a non-zero value is returned */
                ret = smf_run_state(SMF_CTX(&s_obj));
                if (ret) {
                        /* handle return code and terminate state machine */
                        break;
                }
        }
}

Hierarchical State Machine Example With Initial Transitions

tests/lib/smf/src/test_lib_initial_transitions_smf.c defines a state machine for testing initial transitions and smf_set_handled(). The statechart for this test is below.

digraph smf_hierarchical_initial { compound=true; node [style = rounded]; smf_set_initial [shape=plaintext]; ab_init_state [shape = point]; STATE_A [shape = box]; STATE_B [shape = box]; STATE_C [shape = box]; STATE_D [shape = box]; subgraph cluster_ab { label = "PARENT_AB"; style = rounded; ab_init_state -> STATE_A; STATE_A -> STATE_B; } subgraph cluster_c { label = "PARENT_C"; style = rounded; STATE_C -> STATE_C } smf_set_initial -> STATE_A [lhead=cluster_ab] STATE_B -> STATE_C STATE_C -> STATE_D }

Test state machine for initial trnasitions and smf_set_handled

API Reference

group smf

State Machine Framework API.

Defines

SMF_CREATE_STATE(_entry, _run, _exit)

Macro to create a flat state.

Parameters:
  • _entry – State entry function

  • _run – State run function

  • _exit – State exit function

SMF_CTX(o)

Macro to cast user defined object to state machine context.

Parameters:
  • o – A pointer to the user defined object

Typedefs

typedef void (*state_execution)(void *obj)

Function pointer that implements a portion of a state.

Param obj:

pointer user defined object

Functions

void smf_set_initial(struct smf_ctx *ctx, const struct smf_state *init_state)

Initializes the state machine and sets its initial state.

Parameters:
  • ctx – State machine context

  • init_state – Initial state the state machine starts in.

void smf_set_state(struct smf_ctx *ctx, const struct smf_state *new_state)

Changes a state machines state.

This handles exiting the previous state and entering the target state. A common parent state will not exited nor be re-entered.

Parameters:
  • ctx – State machine context

  • new_state – State to transition to (NULL is valid and exits all states)

void smf_set_terminate(struct smf_ctx *ctx, int32_t val)

Terminate a state machine.

Parameters:
  • ctx – State machine context

  • val – Non-Zero termination value that’s returned by the smf_run_state function.

void smf_set_handled(struct smf_ctx *ctx)

Tell the SMF to stop propagating the event to ancestors.

This allows HSMs to implement ‘programming by difference’ where substates can handle events on their own or propagate up to a common handler.

Parameters:
  • ctx – State machine context

int32_t smf_run_state(struct smf_ctx *ctx)

Runs one iteration of a state machine (including any parent states)

Parameters:
  • ctx – State machine context

Returns:

A non-zero value should terminate the state machine. This non-zero value could represent a terminal state being reached or the detection of an error that should result in the termination of the state machine.

struct smf_state
#include <smf.h>

General state that can be used in multiple state machines.

Public Members

const state_execution entry

Optional method that will be run when this state is entered.

const state_execution run

Optional method that will be run repeatedly during state machine loop.

const state_execution exit

Optional method that will be run when this state exists.

const struct smf_state *parent

Optional parent state that contains common entry/run/exit implementation among various child states.

entry: Parent function executes BEFORE child function. run: Parent function executes AFTER child function. exit: Parent function executes AFTER child function.

Note: When transitioning between two child states with a shared parent, that parent’s exit and entry functions do not execute.

struct smf_ctx
#include <smf.h>

Defines the current context of the state machine.

Public Members

const struct smf_state *current

Current state the state machine is executing.

const struct smf_state *previous

Previous state the state machine executed.

int32_t terminate_val

This value is set by the set_terminate function and should terminate the state machine when its set to a value other than zero when it’s returned by the run_state function.

uint32_t internal

The state machine casts this to a “struct internal_ctx” and it’s used to track state machine context.