# c_state_machine **Repository Path**: dingslord/c_state_machine ## Basic Information - **Project Name**: c_state_machine - **Description**: A simple state machine written in C. - **Primary Language**: C - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-06-18 - **Last Updated**: 2025-06-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # c_state_machine A simple state machine written in C. The main idea is to separate the state map and the context, this will enable multiple context sharing same map. The map is constant defined by macros. The users should define the states, arcs, and the state machine itself. ### 1. define the states and events enums As the sample shown in smtest.c, the users define the states enum and the events enum. ```c enum States { ST_IDLE, ST_COMPLETED, ST_FAILED, ST_START_TEST, ST_ACCELERATION, ST_WAIT_FOR_ACCELERATION, ST_DECELERATION, ST_WAIT_FOR_DECELERATION, ST_MAX_STATES }; enum Events { EV_DIRECT, EV_START, EV_POLL, EV_CANCEL, EV_DONE, EV_MAX_EVENTS }; ``` Note that the enums above should start at 0, increase by 1, and DO NOT jump over values by setting the enumerator values directly. The first state, value 0, represents the initial state, while the value 0 of events enum represents the direct unconditional transition. ### 2. define the state map The state map is defined by CSM_BEGIN_STATE_MAP and CSM_END_STATE_MAP, with state entries inside. Here's the example in smtest.c. ```C CSM_BEGIN_STATE_MAP(CentrifugeTest) CSM_STATE_MAP_ENTRY_ALL(ST_IDLE, on_idle_state, on_idle_entered, on_idle_exited) CSM_STATE_MAP_ENTRY_ALL(ST_COMPLETED, on_completed_state, on_completed_entered, on_completed_exited) CSM_STATE_MAP_ENTRY_ALL(ST_FAILED, on_failed_state, on_failed_entered, on_failed_exited) CSM_STATE_MAP_ENTRY_ALL(ST_START_TEST, on_start_state, on_start_entered, on_start_exited) CSM_STATE_MAP_ENTRY_ALL(ST_ACCELERATION, on_acc_state, on_acc_entered, on_acc_exited) CSM_STATE_MAP_ENTRY_ALL(ST_WAIT_FOR_ACCELERATION, on_wait4acc_state, on_wait4acc_entered, on_wait4acc_exited) CSM_STATE_MAP_ENTRY_ALL(ST_DECELERATION, on_dec_state, on_dec_entered, on_dec_exited) CSM_STATE_MAP_ENTRY_ALL(ST_WAIT_FOR_DECELERATION, on_wait4dec_state, on_wait4dec_entered, on_wait4dec_exited) CSM_END_STATE_MAP ``` The user can define a state entry either by CSM_STATE_MAP_ENTRY or CSM_STATE_MAP_ENTRY_ALL. The only difference is the CSM_STATE_MAP_ENTRY only defines the state callback function, while CSM_STATE_MAP_ENTRY_ALL has entry and exit functions. ```C #define CSM_STATE_MAP_ENTRY(id, state_func) \ { id, (csm_state_func_t)state_func, NULL, NULL }, #define CSM_STATE_MAP_ENTRY_ALL(id, state_func, entry_func, exit_func) \ { id, (csm_state_func_t)state_func, (csm_enter_func_t)entry_func, (csm_exit_func_t)exit_func }, ``` The callback functions have similar signatures. ```C typedef void (*csm_state_func_t)(struct _csm_state_ctx *ctx, void *event_data); typedef void (*csm_enter_func_t)(struct _csm_state_ctx *ctx, void *event_data); typedef void (*csm_exit_func_t)(struct _csm_state_ctx *ctx, void *event_data); ``` The users can manually written it, like in smtest.c, or directly use predefined macros: - CSM_STATE_FUNC_DECLARE - CSM_STATE_FUNC_DEFINE - CSM_ENTRY_FUNC_DECLARE - CSM_ENTRY_FUNC_DEFINE - CSM_EXIT_FUNC_DECLARE - CSM_EXIT_FUNC_DEFINE Using these macros will define a callback function like: `on_xxx_state`, `on_xxx_entered`, `on_xxx_exited`. ### 3. Define the arc map The arc is the transition from on state to another. Therefore, it has three fields, source state, dest state, and the transition event. Define the arc map is similar to state map, using CSM_BEGIN_ARC_MAP and CSM_END_ARC_MAP, with arc entry defined by CSM_ARC_MAP_ENTRY. Here's the example in smtest.c. ```C CSM_BEGIN_ARC_MAP(CentrifugeTest) CSM_ARC_MAP_ENTRY(ST_IDLE, ST_START_TEST, EV_START) CSM_ARC_MAP_ENTRY(ST_START_TEST, ST_ACCELERATION, EV_DIRECT) CSM_ARC_MAP_ENTRY(ST_ACCELERATION, ST_WAIT_FOR_ACCELERATION, EV_POLL) CSM_ARC_MAP_ENTRY(ST_WAIT_FOR_ACCELERATION, ST_DECELERATION, EV_DONE) CSM_ARC_MAP_ENTRY(ST_WAIT_FOR_ACCELERATION, ST_WAIT_FOR_ACCELERATION, EV_POLL) CSM_ARC_MAP_ENTRY(ST_DECELERATION, ST_WAIT_FOR_DECELERATION, EV_POLL) CSM_ARC_MAP_ENTRY(ST_WAIT_FOR_DECELERATION, ST_WAIT_FOR_DECELERATION, EV_POLL) CSM_ARC_MAP_ENTRY(ST_WAIT_FOR_DECELERATION, ST_COMPLETED, EV_DONE) CSM_ARC_MAP_ENTRY(ST_COMPLETED, ST_IDLE, EV_DIRECT) CSM_ARC_MAP_ENTRY(ST_FAILED, ST_IDLE, EV_DIRECT) CSM_ARC_MAP_ENTRY(ST_START_TEST, ST_FAILED, EV_CANCEL) CSM_ARC_MAP_ENTRY(ST_ACCELERATION, ST_FAILED, EV_CANCEL) CSM_ARC_MAP_ENTRY(ST_WAIT_FOR_ACCELERATION, ST_FAILED, EV_CANCEL) CSM_ARC_MAP_ENTRY(ST_DECELERATION, ST_FAILED, EV_CANCEL) CSM_ARC_MAP_ENTRY(ST_WAIT_FOR_DECELERATION, ST_FAILED, EV_CANCEL) CSM_END_ARC_MAP ``` Note the EV_DIRECT means unconditional direct transition, with no trigger needed. Other events should have a manual trigger, by `csm_trigger`, either in user context or the state/entry/exit callback functions. ### 4. Define the state machine The state machine is defined by CSM_SM_DEFINE. ```c CSM_SM_DEFINE(CentrifugeTest) ``` Note the argument passed to CSM_BEGIN_STATE_MAP, CSM_BEGIN_ARC_MAP, and CSM_SM_DEFINE, must be the same one. ### 5. Using the state machine After finished the steps above, the user can now use the state machine. First, initialize the state machine by calling `csm_init_state_machine`. It will check the state map and the arc map. Especially, when CSM_ENABLE_CYCLE_CHECK is defined, it will check if there has cycle with EV_DIRECT in the arc map, which may cause infinite loop. Then, allocate a context and initilize it. In smtest.c, ```C csm_state_ctx_t ctx1 = {0}; csm_init_state_ctx(&ctx1, &CentrifugeTest); ``` Then, trigger events for the context. ```C csm_trigger(&ctx1, EV_START, NULL); ``` User defined data can be passed with the event. ```C size_t acc_value = 1; csm_trigger(&ctx1, EV_POLL, (void *)acc_value); ``` The data will passed to the state/entry/exit callback functions. ```c static void on_wait4acc_state(csm_state_ctx_t *ctx, void* event_data) { printf("%s\n", __func__); uintptr_t acc_value = (uintptr_t)event_data; ctx->current_data += acc_value; if ((uintptr_t)ctx->current_data >= 5) csm_trigger(ctx, EV_DONE, NULL); } ``` The context itself also has a user defined data field. The users can use it for their business. ### 6. Notes This state machine support multiple contexts. If used in multi-thread/multi-core environment, the users should pay more attention to the possible concurrency issues. To keep simple, complex states, e.g. nested state or parallel state, are not supported. Consider the init time, the CSM_ENABLE_CYCLE_CHECK can be switched OFF. The user must guarantee no cycle with EV_DIRECT in arc map.