├── .gitignore ├── LICENSE ├── README.md ├── SConscript ├── examples ├── SConscript ├── post_state.c └── state_machine_example.c ├── inc └── state_machine.h └── src ├── SConscript └── state_machine.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | .vscode 45 | 46 | # Kernel Module Compile Results 47 | *.mod* 48 | *.cmd 49 | .tmp_versions/ 50 | modules.order 51 | Module.symvers 52 | Mkfile.old 53 | dkms.conf 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 Andreas Misje 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # state_machine 2 | 3 | ## 1、介绍 4 | 5 | state_machine是基于RT-Thread格式移植的状态机软件包。 6 | state_machine的作者是misje, github地址: https://github.com/misje/stateMachine 7 | 对该软件包进行了如下修改: 8 | 1.修复部分函数反馈,由void改为int,如果异常反馈负数; 9 | 2.修改状态儿子数的判断,如果这个状态没有儿子,还需要判断它的父亲(原作者不判断父亲,该状态会停掉); 10 | 11 | 12 | ## 使用方法 13 | 1.先申请一个状态结构,定义事件 14 | 15 | ``` 16 | enum event_type { 17 | EVENT_KEYBOARD, 18 | }; 19 | struct state_machine m; 20 | ``` 21 | 2.初始化状态对象 22 | 23 | ``` 24 | static struct state state_idle = { 25 | .state_parent = NULL, 26 | .state_entry = NULL, 27 | .transitions = (struct transition[]){ 28 | { EVENT_KEYBOARD, (void *)(intptr_t)'t', &keyboard_char_compare, NULL, &state_next },// turn next state 29 | { EVENT_KEYBOARD, (void *)(intptr_t)'d', &keyboard_char_compare, &action_fun, &state_idle },// do action 30 | }, 31 | .transition_nums = 1, 32 | .data = "idle", 33 | .action_entry = &print_msg_enter, 34 | .action_exti = &print_msg_exit, 35 | }; 36 | 37 | static struct state state_error = { 38 | .data = "Error", 39 | .action_entry = &print_msg_err 40 | }; 41 | ``` 42 | 3.初始化状态机 43 | 44 | ``` 45 | statem_init( &m, &state_idle, &state_error ); 46 | ... 47 | ``` 48 | 4.执行事件 49 | 50 | ``` 51 | statem_handle_event( &m, &(struct event){ EVENT_KEYBOARD, (void *)(intptr_t)ch } ); 52 | ``` 53 | 54 | 55 | ## 特性 56 | 57 | state_machine 使用C语言实现,基于面向对象方式设计思路,每个状态对象单独用一份数据结构管理: 58 | 59 | 60 | 61 | ## Examples 62 | 63 | 使用示例在 [examples](./examples) 下。 64 | 65 | -------------------------------------------------------------------------------- /SConscript: -------------------------------------------------------------------------------- 1 | # RT-Thread building script for bridge 2 | 3 | import os 4 | from building import * 5 | 6 | cwd = GetCurrentDir() 7 | objs = [] 8 | list = os.listdir(cwd) 9 | 10 | if GetDepend('PKG_USING_STATE_MACHINE'): 11 | for d in list: 12 | path = os.path.join(cwd, d) 13 | if os.path.isfile(os.path.join(path, 'SConscript')): 14 | objs = objs + SConscript(os.path.join(d, 'SConscript')) 15 | 16 | Return('objs') 17 | -------------------------------------------------------------------------------- /examples/SConscript: -------------------------------------------------------------------------------- 1 | from building import * 2 | 3 | cwd = GetCurrentDir() 4 | src = Glob('*.c') + Glob('*.cpp') 5 | CPPPATH = [cwd] 6 | 7 | group = DefineGroup('state_machine', src, depend = ['PKG_STATE_MACHINE_USING_EXAMPLE'], CPPPATH = CPPPATH) 8 | 9 | Return('group') 10 | -------------------------------------------------------------------------------- /examples/post_state.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, redoc 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Change Logs: 7 | * Date Author Notes 8 | * 2021-01-12 redoc the first version 9 | */ 10 | 11 | #define LOG_TAG "state.post" 12 | #define LOG_LVL LOG_LVL_DBG 13 | 14 | #include "state.h" 15 | #include 16 | 17 | /* post state graph 18 | * 19 | * 20 | * +-----------------------------------------------------------------+ 21 | * | root--------+ | 22 | * | | | 23 | * | | | 24 | * | +----------+ fail +----------+ pass +----------+ | 25 | * | | postfail | <-------- | post | --------> | postpass | | 26 | * | +----------+ +----------+ +----------+ | 27 | * | ^ \post | 28 | * | post| \break | 29 | * | break| \on | 30 | * | off| \----> +-----------+ | 31 | * | +------------- | postbreak | | 32 | * | +-----------+ | 33 | * | | 34 | * +-----------------------------------------------------------------+ 35 | * 36 | */ 37 | 38 | enum event_post_type 39 | { 40 | EVENT_POST_NULL, 41 | EVENT_POST_START, 42 | EVENT_POST_BREAKON, 43 | EVENT_POST_BREAKOFF, 44 | EVENT_POST_ANSWER, 45 | 46 | EVENT_POST_NUMS, 47 | }; 48 | 49 | static struct state state_root, state_post, state_postpass, state_postfail, 50 | state_postbreak; 51 | 52 | static void print_msg_err( void *state_data, struct event *event ); 53 | static void print_msg_enter( void *state_data, struct event *event ); 54 | static void print_msg_exit( void *state_data, struct event *event ); 55 | static void state_post_enter( void *state_data, struct event *event ); 56 | static bool guard_post_pass( void *condition, struct event *event ); 57 | static bool guard_post_fail( void *condition, struct event *event ); 58 | static void action_post_break( void *oldstate_data, struct event *event, 59 | void *state_new_data ); 60 | static void action_post_pass( void *oldstate_data, struct event *event, 61 | void *state_new_data ); 62 | static void action_post_fail( void *oldstate_data, struct event *event, 63 | void *state_new_data ); 64 | 65 | static struct state state_root = { 66 | .state_parent = NULL, 67 | .state_entry = NULL, 68 | .transitions = (struct transition[]){ 69 | /* event_type, condition, guard, action, next, state */ 70 | { EVENT_POST_START, NULL, NULL, NULL, &state_post }, 71 | }, 72 | .transition_nums = 1, 73 | .data = "ROOT", 74 | .action_entry = &print_msg_enter, 75 | .action_exti = &print_msg_exit, 76 | }; 77 | 78 | static struct state state_post = { 79 | .state_parent = NULL, 80 | .state_entry = NULL, 81 | .transitions = (struct transition[]){ 82 | { EVENT_POST_BREAKON, NULL, NULL, &action_post_break, &state_postbreak }, 83 | { EVENT_POST_ANSWER, (void*)1, &guard_post_fail, &action_post_fail, &state_postfail }, 84 | { EVENT_POST_ANSWER, (void*)2, &guard_post_pass, &action_post_pass, &state_postpass }, 85 | }, 86 | .transition_nums = 3, 87 | .data = "POST", 88 | .action_entry = &state_post_enter, 89 | .action_exti = &print_msg_exit, 90 | }; 91 | 92 | static struct state state_postpass = { 93 | .state_parent = NULL, 94 | .state_entry = NULL, 95 | .data = "POSTPASS", 96 | .action_entry = &print_msg_enter, 97 | .action_exti = &print_msg_exit, 98 | }; 99 | 100 | static struct state state_postfail = { 101 | .state_parent = NULL, 102 | .state_entry = NULL, 103 | .data = "POSTFAIL", 104 | .action_entry = &print_msg_enter, 105 | .action_exti = &print_msg_exit, 106 | }; 107 | 108 | static struct state state_postbreak = { 109 | .state_parent = NULL, 110 | .state_entry = NULL, 111 | .transitions = (struct transition[]){ 112 | { EVENT_POST_BREAKOFF, NULL, NULL, NULL, &state_post }, 113 | }, 114 | .transition_nums = 1, 115 | .data = "POSTBREAK", 116 | .action_entry = &print_msg_enter, 117 | .action_exti = &print_msg_exit, 118 | }; 119 | 120 | static struct state state_error = { 121 | .data = "ERROR", 122 | .action_entry = &print_msg_err, 123 | }; 124 | 125 | /* post process start */ 126 | static void state_post_enter( void *state_data, struct event *event ) 127 | { 128 | print_msg_enter(state_data, event); 129 | log_i("post start..."); 130 | } 131 | 132 | static void action_post_break( void *oldstate_data, struct event *event, 133 | void *state_new_data ) 134 | { 135 | log_i("post break,display break..."); 136 | } 137 | 138 | static bool guard_post_pass( void *condition, struct event *event ) 139 | { 140 | if(event->type != EVENT_POST_ANSWER) 141 | { 142 | return false; 143 | } 144 | 145 | return ((int)event->data == (int)condition); 146 | } 147 | 148 | static void action_post_pass( void *oldstate_data, struct event *event, 149 | void *state_new_data ) 150 | { 151 | log_i("post pass,display pass..."); 152 | } 153 | 154 | static bool guard_post_fail( void *condition, struct event *event ) 155 | { 156 | if(event->type != EVENT_POST_ANSWER) 157 | { 158 | return false; 159 | } 160 | 161 | return ((int)event->data == (int)condition); 162 | } 163 | 164 | static void action_post_fail( void *oldstate_data, struct event *event, 165 | void *state_new_data ) 166 | { 167 | log_i("post fail,display fail..."); 168 | } 169 | /* post process end */ 170 | 171 | static void print_msg_err( void *state_data, struct event *event ) 172 | { 173 | log_e( "entered error state!" ); 174 | } 175 | 176 | static void print_msg_enter( void *state_data, struct event *event ) 177 | { 178 | log_i( "entering %s state", (char *)state_data ); 179 | } 180 | 181 | static void print_msg_exit( void *state_data, struct event *event ) 182 | { 183 | log_i( "Eexiting %s state", (char *)state_data ); 184 | } 185 | 186 | static rt_mq_t mq_event_post; 187 | static struct state_machine m_post; 188 | 189 | int state_post_event_set(enum event_post_type event, void* data) 190 | { 191 | struct event e; 192 | 193 | RT_ASSERT(event < EVENT_POST_NUMS); 194 | 195 | e.type = event; 196 | e.data = data; 197 | 198 | return rt_mq_send(mq_event_post, &e, sizeof(struct event)); 199 | } 200 | 201 | static void state_process(void *parameter) 202 | { 203 | struct event e; 204 | statem_init( &m_post, &state_root, &state_error ); 205 | 206 | while(1) 207 | { 208 | if(RT_EOK == rt_mq_recv(mq_event_post, &e, sizeof(struct event), 20)) 209 | { 210 | statem_handle_event( &m_post, &(struct event){ e.type, e.data } ); 211 | } 212 | } 213 | } 214 | 215 | static int state_post_init(void) 216 | { 217 | rt_thread_t tid = RT_NULL; 218 | 219 | mq_event_post = rt_mq_create("event_post",sizeof(struct event), 16, RT_IPC_FLAG_FIFO); 220 | 221 | tid = rt_thread_create("state_post", state_process, RT_NULL, 1024, 10, 100); 222 | if (tid == RT_NULL) 223 | { 224 | rt_kprintf("state post initialize failed! thread create failed!\r\n"); 225 | 226 | return -RT_ENOMEM; 227 | } 228 | rt_thread_startup(tid); 229 | 230 | return RT_EOK; 231 | } 232 | INIT_APP_EXPORT(state_post_init); 233 | 234 | 235 | #ifdef FINSH_USING_MSH 236 | static void post_event_set(uint8_t argc, char **argv) 237 | { 238 | struct event e; 239 | 240 | if(argc < 2) 241 | { 242 | rt_kprintf("state post event set \n"); 243 | } 244 | else 245 | { 246 | const char *operator = argv[1]; 247 | 248 | if (argc == 3) 249 | { 250 | e.data = (void*)atoi(argv[2]); 251 | } 252 | else 253 | { 254 | e.data = RT_NULL; 255 | } 256 | 257 | if (!rt_strcmp(operator, "start")) 258 | { 259 | e.type = EVENT_POST_START; 260 | } 261 | else if (!rt_strcmp(operator, "breakon")) 262 | { 263 | e.type = EVENT_POST_BREAKON; 264 | } 265 | else if (!rt_strcmp(operator, "breakoff")) 266 | { 267 | e.type = EVENT_POST_BREAKOFF; 268 | } 269 | else if (!rt_strcmp(operator, "answer")) 270 | { 271 | e.type = EVENT_POST_ANSWER; 272 | } 273 | else 274 | { 275 | rt_kprintf("state key set:%s\n",argv[1]); 276 | return; 277 | } 278 | 279 | state_post_event_set(e.type, e.data); 280 | } 281 | } 282 | MSH_CMD_EXPORT(post_event_set, state post event set .); 283 | 284 | static void post_current_get(uint8_t argc, char **argv) 285 | { 286 | if(!m_post.state_current->data) 287 | { 288 | rt_kprintf("post current state is NULL\n"); 289 | } 290 | else 291 | { 292 | rt_kprintf("post current state is %s\n",m_post.state_current->data); 293 | } 294 | } 295 | MSH_CMD_EXPORT(post_current_get, get current state.); 296 | 297 | #endif /* FINSH_USING_MSH */ 298 | 299 | -------------------------------------------------------------------------------- /examples/state_machine_example.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redocCheng/state_machine/a0814dd9fb195616d0f1b4792f85d3dccc2d435a/examples/state_machine_example.c -------------------------------------------------------------------------------- /inc/state_machine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Andreas Misje 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /** 24 | * \mainpage %state_machine 25 | * 26 | * %state_machine is a feature-rich, yet simple finite state machine 27 | * implementation. It supports grouped states, guarded transitions, events 28 | * with payload, entry and exit actions, transition actions and access to 29 | * user-defined state data from all actions. 30 | * 31 | * The user must build the state machine by linking together states and 32 | * transitions arrays with pointers. A pointer to an initial state and an 33 | * error state is given to statem_init() to initialise a state machine object. 34 | * The state machine is run by passing events to it with the function 35 | * statem_handle_event(). The return value of statem_handle_event() will 36 | * give an indication to what has happened. 37 | * 38 | * \image html state_machine.svg "Illustrating a state_machine" 39 | */ 40 | 41 | /** 42 | * \defgroup state_machine State machine 43 | * 44 | * \author Andreas Misje 45 | * \date 27.03.13 46 | * 47 | * \brief Finite state machine 48 | * 49 | * A finite state machine implementation that supports nested states, guards 50 | * and entry/exit routines. All state machine data is stored in separate 51 | * objects, and the state machine must be built by the user. States are 52 | * connected using pointers, and all data can be stored on either the stack, 53 | * heap or both. 54 | */ 55 | 56 | /** 57 | * \addtogroup state_machine 58 | * @{ 59 | * 60 | * \file 61 | * \example state_machineExample.c Simple example of how to create a state 62 | * machine 63 | * \example nestedTest.c Simple example testing the behaviour of nested 64 | * parent states 65 | */ 66 | 67 | #ifndef __STATE_MACHINE_H 68 | #define __STATE_MACHINE_H 69 | 70 | #include 71 | #include 72 | 73 | /** 74 | * \brief Event 75 | * 76 | * Events trigger transitions from a state to another. Event types are defined 77 | * by the user. Any event may optionally contain a \ref #event::data 78 | * "payload". 79 | * 80 | * \sa state 81 | * \sa transition 82 | */ 83 | struct event 84 | { 85 | /** \brief Type of event. Defined by user. */ 86 | int type; 87 | /** 88 | * \brief Event payload. 89 | * 90 | * How this is used is entirely up to the user. This data 91 | * is always passed together with #type in order to make it possible to 92 | * always cast the data correctly. 93 | */ 94 | void *data; 95 | }; 96 | 97 | struct state; 98 | 99 | /** 100 | * \brief Transition between a state and another state 101 | * 102 | * All states that are not final must have at least one transition. The 103 | * transition may be guarded or not. Transitions are triggered by events. If 104 | * a state has more than one transition with the same type of event (and the 105 | * same condition), the first transition in the array will be run. An 106 | * unconditional transition placed last in the transition array of a state can 107 | * act as a "catch-all". A transition may optionally run an #action, which 108 | * will have the triggering event passed to it as an argument, along with the 109 | * current and new states' \ref state::data "data". 110 | * 111 | * It is perfectly valid for a transition to return to the state it belongs 112 | * to. Such a transition will not call the state's \ref state::action_entry 113 | * "entry action" or \ref state::action_exti "exit action". If there are no 114 | * transitions for the current event, the state's parent will be handed the 115 | * event. 116 | * 117 | * ### Examples ### 118 | * - An ungarded transition to a state with no action performed: 119 | * ~~~{.c} 120 | * { 121 | * .event_type = Event_timeout, 122 | * .condition = NULL, 123 | * .guard = NULL, 124 | * .action = NULL, 125 | * .state_next = &mainMenuState, 126 | * }, 127 | * ~~~ 128 | * - A guarded transition executing an action 129 | * ~~~{.c} 130 | * { 131 | * .event_type = EVENT_KEYBOARD, 132 | * .condition = NULL, 133 | * .guard = &ensureNumericInput, 134 | * .action = &addToBuffer, 135 | * .state_next = &awaitingInputState, 136 | * }, 137 | * ~~~ 138 | * - A guarded transition using a condition 139 | * ~~~{.c} 140 | * { 141 | * .event_type = Event_mouse, 142 | * .condition = boxLimits, 143 | * .guard = &coordinatesWithinLimits, 144 | * }, 145 | * ~~~ 146 | * By using \ref #condition "conditions" a more general guard function can be 147 | * used, operating on the supplied argument #condition. In this example, 148 | * `coordinatesWithinLimits` checks whether the coordinates in the mouse event 149 | * are within the limits of the "box". 150 | * 151 | * \sa event 152 | * \sa state 153 | */ 154 | struct transition 155 | { 156 | /** \brief The event that will trigger this transition. */ 157 | int event_type; 158 | /** 159 | * \brief Condition that event must fulfil 160 | * 161 | * This variable will be passed to the #guard (if #guard is non-NULL) and 162 | * may be used as a condition that the incoming event's data must fulfil in 163 | * order for the transition to be performed. By using this variable, the 164 | * number of #guard functions can be minimised by making them more general. 165 | */ 166 | void *condition; 167 | /** 168 | * \brief Check if data passed with event fulfils a condition 169 | * 170 | * A transition may be conditional. If so, this function, if non-NULL, will 171 | * be called. Its first argument will be supplied with #condition, which 172 | * can be compared against the \ref event::data "payload" in the #event. 173 | * The user may choose to use this argument or not. Only if the result is 174 | * true, the transition will take place. 175 | * 176 | * \param condition event (data) to compare the incoming event against. 177 | * \param event the event passed to the state machine. 178 | * 179 | * \returns true if the event's data fulfils the condition, otherwise false. 180 | */ 181 | bool ( *guard )( void *condition, struct event *event ); 182 | /** 183 | * \brief Function containing tasks to be performed during the transition 184 | * 185 | * The transition may optionally do some work in this function before 186 | * entering the next state. May be NULL. 187 | * 188 | * \param state_current_data the leaving state's \ref state::data "data" 189 | * \param event the event passed to the state machine. 190 | * \param state_new_data the new state's (the \ref state::state_entry 191 | * "state_entry" of any (chain of) parent states, not the parent state 192 | * itself) \ref state::data "data" 193 | */ 194 | void ( *action )( void *state_current_data, struct event *event, 195 | void *state_new_data ); 196 | /** 197 | * \brief The next state 198 | * 199 | * This must point to the next state that will be entered. It cannot be 200 | * NULL. If it is, the state machine will detect it and enter the \ref 201 | * state_machine::state_error "error state". 202 | */ 203 | struct state *state_next; 204 | }; 205 | 206 | /** 207 | * \brief State 208 | * 209 | * The current state in a state machine moves to a new state when one of the 210 | * #transitions in the current state triggers on an event. An optional \ref 211 | * #action_exti "exit action" is called when the state is left, and an \ref 212 | * #action_entry "entry action" is called when the state machine enters a new 213 | * state. If a state returns to itself, neither #action_exti nor #action_entry 214 | * will be called. An optional \ref transition::action "transition action" is 215 | * called in either case. 216 | * 217 | * States may be organised in a hierarchy by setting \ref #state_parent 218 | * "parent states". When a group/parent state is entered, the state machine is 219 | * redirected to the group state's \ref #state_entry "entry state" (if 220 | * non-NULL). If an event does not trigger a transition in a state and if the 221 | * state has a parent state, the event will be passed to the parent state. 222 | * This behaviour is repeated for all parents. Thus all children of a state 223 | * have a set of common #transitions. A parent state's #action_entry will not 224 | * be called if an event is passed on to a child state. 225 | * 226 | * The following lists the different types of states that may be created, and 227 | * how to create them: 228 | * 229 | * ### Normal state ### 230 | * ~~~{.c} 231 | * struct state normalState = { 232 | * .state_parent = &groupState, 233 | * .state_entry = NULL, 234 | * .transition = (struct transition[]){ 235 | * { EVENT_KEYBOARD, (void *)(intptr_t)'\n', &keyboard_char_compare, 236 | * NULL, &msgReceivedState }, 237 | * }, 238 | * .transition_nums = 1, 239 | * .data = normalstate_data, 240 | * .action_entry = &doSomething, 241 | * .action_exti = &cleanUp, 242 | * }; 243 | * ~~~ 244 | * In this example, `normalState` is a child of `groupState`, but the 245 | * #state_parent value may also be NULL to indicate that it is not a child of 246 | * any group state. 247 | * 248 | * ### Group/parent state ### 249 | * A state becomes a group/parent state when it is linked to by child states 250 | * by using #state_parent. No members in the group state need to be set in a 251 | * particular way. A parent state may also have a parent. 252 | * ~~~{.c} 253 | * struct state groupState = { 254 | * .state_entry = &normalState, 255 | * .action_entry = NULL, 256 | * ~~~ 257 | * If there are any transitions in the state machine that lead to a group 258 | * state, it makes sense to define an entry state in the group. This can be 259 | * done by using #state_entry, but it is not mandatory. If the #state_entry 260 | * state has children, the chain of children will be traversed until a child 261 | * with its #state_entry set to NULL is found. 262 | * 263 | * \note If #state_entry is defined for a group state, the group state's 264 | * #action_entry will not be called (the state pointed to by #state_entry (after 265 | * following the chain of children), however, will have its #action_entry 266 | * called). 267 | * 268 | * \warning The state machine cannot detect cycles in parent chains and 269 | * children chains. If such cycles are present, statem_handle_event() will 270 | * never finish due to never-ending loops. 271 | * 272 | * ### Final state ### 273 | * A final state is a state that terminates the state machine. A state is 274 | * considered as a final state if its #transition_nums is 0: 275 | * ~~~{.c} 276 | * struct state finalState = { 277 | * .transitions = NULL, 278 | * .transition_nums = 0, 279 | * ~~~ 280 | * The error state used by the state machine to indicate errors should be a 281 | * final state. Any calls to statem_handle_event() when the current state is a 282 | * final state will return #STATEM_STATE_NOCHANGE. 283 | * 284 | * \sa event 285 | * \sa transition 286 | */ 287 | struct state 288 | { 289 | /** 290 | * \brief If the state has a parent state, this pointer must be non-NULL. 291 | */ 292 | struct state *state_parent; 293 | /** 294 | * \brief If this state is a parent state, this pointer may point to a 295 | * child state that serves as an entry point. 296 | */ 297 | struct state *state_entry; 298 | /** 299 | * \brief An array of transitions for the state. 300 | */ 301 | struct transition *transitions; 302 | /** 303 | * \brief Number of transitions in the #transitions array. 304 | */ 305 | size_t transition_nums; 306 | /** 307 | * \brief Data that will be available for the state in its #action_entry and 308 | * #action_exti, and in any \ref transition::action "transition action" 309 | */ 310 | void *data; 311 | /** 312 | * \brief This function is called whenever the state is being entered. May 313 | * be NULL. 314 | * 315 | * \note If a state returns to itself through a transition (either directly 316 | * or through a parent/group sate), its #action_entry will not be called. 317 | * 318 | * \note A group/parent state with its #state_entry defined will not have 319 | * its #action_entry called. 320 | * 321 | * \param state_data the state's #data will be passed. 322 | * \param event the event that triggered the transition will be passed. 323 | */ 324 | void ( *action_entry )( void *state_data, struct event *event ); 325 | /** 326 | * \brief This function is called whenever the state is being left. May be 327 | * NULL. 328 | * 329 | * \note If a state returns to itself through a transition (either directly 330 | * or through a parent/group sate), its #action_exti will not be called. 331 | * 332 | * \param state_data the state's #data will be passed. 333 | * \param event the event that triggered a transition will be passed. 334 | */ 335 | void ( *action_exti )( void *state_data, struct event *event ); 336 | }; 337 | 338 | /** 339 | * \brief State machine 340 | * 341 | * There is no need to manipulate the members directly. 342 | */ 343 | struct state_machine 344 | { 345 | /** \brief Pointer to the current state */ 346 | struct state *state_current; 347 | /** 348 | * \brief Pointer to previous state 349 | * 350 | * The previous state is stored for convenience in case the user needs to 351 | * keep track of previous states. 352 | */ 353 | struct state *state_previous; 354 | /** 355 | * \brief Pointer to a state that will be entered whenever an error occurs 356 | * in the state machine. 357 | * 358 | * See #STATEM_ERR_STATE_RECHED for when the state machine enters the 359 | * error state. 360 | */ 361 | struct state *state_error; 362 | }; 363 | 364 | /** 365 | * \brief Initialise the state machine 366 | * 367 | * This function initialises the supplied state_machine and sets the current 368 | * state to \pn{state_init}. No actions are performed until 369 | * statem_handle_event() is called. It is safe to call this function numerous 370 | * times, for instance in order to reset/restart the state machine if a final 371 | * state has been reached. 372 | * 373 | * \note The \ref #state::action_entry "entry action" for \pn{state_init} 374 | * will not be called. 375 | * 376 | * \note If \pn{state_init} is a parent state with its \ref 377 | * state::state_entry "state_entry" defined, it will not be entered. The user 378 | * must explicitly set the initial state. 379 | * 380 | * \param state_machine the state machine to initialise. 381 | * \param state_init the initial state of the state machine. 382 | * \param state_error pointer to a state that acts a final state and notifies 383 | * the system/user that an error has occurred. 384 | */ 385 | int statem_init( struct state_machine *state_machine, 386 | struct state *state_init, struct state *state_error ); 387 | 388 | /** 389 | * \brief statem_handle_event() return values 390 | */ 391 | enum statem_handle_event_return_vals 392 | { 393 | /** \brief Erroneous arguments were passed */ 394 | STATEM_ERR_ARG = -2, 395 | /** 396 | * \brief The error state was reached 397 | * 398 | * This value is returned either when the state machine enters the error 399 | * state itself as a result of an error, or when the error state is the 400 | * next state as a result of a successful transition. 401 | * 402 | * The state machine enters the state machine if any of the following 403 | * happens: 404 | * - The current state is NULL 405 | * - A transition for the current event did not define the next state 406 | */ 407 | STATEM_ERR_STATE_RECHED, 408 | /** \brief The current state changed into a non-final state */ 409 | STATEM_STATE_CHANGED, 410 | /** 411 | * \brief The state changed back to itself 412 | * 413 | * The state can return to itself either directly or indirectly. An 414 | * indirect path may inlude a transition from a parent state and the use of 415 | * \ref state::state_entry "state_entrys". 416 | */ 417 | STATEM_STATE_LOOPSELF, 418 | /** 419 | * \brief The current state did not change on the given event 420 | * 421 | * If any event passed to the state machine should result in a state 422 | * change, this return value should be considered as an error. 423 | */ 424 | STATEM_STATE_NOCHANGE, 425 | /** \brief A final state (any but the error state) was reached */ 426 | STATEM_FINAL_STATE_RECHED, 427 | }; 428 | 429 | /** 430 | * \brief Pass an event to the state machine 431 | * 432 | * The event will be passed to the current state, and possibly to the current 433 | * state's parent states (if any). If the event triggers a transition, a new 434 | * state will be entered. If the transition has an \ref transition::action 435 | * "action" defined, it will be called. If the transition is to a state other 436 | * than the current state, the current state's \ref state::action_exti 437 | * "exit action" is called (if defined). Likewise, if the state is a new 438 | * state, the new state's \ref state::action_entry "entry action" is called (if 439 | * defined). 440 | * 441 | * The returned value is negative if an error occurs. 442 | * 443 | * \param state_machine the state machine to pass an event to. 444 | * \param event the event to be handled. 445 | * 446 | * \return #statem_handle_event_return_vals 447 | */ 448 | int statem_handle_event( struct state_machine *state_machine, 449 | struct event *event ); 450 | 451 | /** 452 | * \brief Get the current state 453 | * 454 | * \param state_machine the state machine to get the current state from. 455 | * 456 | * \retval a pointer to the current state. 457 | * \retval NULL if \pn{state_machine} is NULL. 458 | */ 459 | struct state *statem_state_current( struct state_machine *state_machine ); 460 | 461 | /** 462 | * \brief Get the previous state 463 | * 464 | * \param state_machine the state machine to get the previous state from. 465 | * 466 | * \retval the previous state. 467 | * \retval NULL if \pn{state_machine} is NULL. 468 | * \retval NULL if there has not yet been any transitions. 469 | */ 470 | struct state *statem_state_previous( struct state_machine *state_machine ); 471 | 472 | /** 473 | * \brief Check if the state machine has stopped 474 | * 475 | * \param state_machine the state machine to test. 476 | * 477 | * \retval true if the state machine has reached a final state. 478 | * \retval false if \pn{state_machine} is NULL or if the current state is not a 479 | * final state. 480 | */ 481 | int statem_stopped( struct state_machine *state_machine ); 482 | 483 | #endif // state_machine_H 484 | 485 | /** 486 | * @} 487 | */ 488 | -------------------------------------------------------------------------------- /src/SConscript: -------------------------------------------------------------------------------- 1 | from building import * 2 | 3 | cwd = GetCurrentDir() 4 | src = Glob('*.c') + Glob('*.cpp') 5 | CPPPATH = [cwd + '/../inc'] 6 | 7 | group = DefineGroup('state_machine', src, depend = ['PKG_USING_STATE_MACHINE'], CPPPATH = CPPPATH) 8 | 9 | Return('group') 10 | -------------------------------------------------------------------------------- /src/state_machine.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Andreas Misje 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | #include "state_machine.h" 24 | 25 | static void go_to_state_error( struct state_machine *state_machine, 26 | struct event *const event ); 27 | static struct transition *get_transition( struct state_machine *state_machine, 28 | struct state *state, struct event *const event ); 29 | 30 | int statem_init( struct state_machine *fsm, 31 | struct state *state_init, struct state *state_error ) 32 | { 33 | if ( !fsm ) 34 | { 35 | return -1; 36 | } 37 | 38 | fsm->state_current = state_init; 39 | fsm->state_previous = NULL; 40 | fsm->state_error = state_error; 41 | 42 | return 0; 43 | } 44 | 45 | int statem_handle_event( struct state_machine *fsm, struct event *event ) 46 | { 47 | if ( !fsm || !event ) 48 | { 49 | return STATEM_ERR_ARG; 50 | } 51 | 52 | if ( !fsm->state_current ) 53 | { 54 | go_to_state_error( fsm, event ); 55 | return STATEM_ERR_STATE_RECHED; 56 | } 57 | 58 | /* 如果这个状态没有儿子,不用转移了,那他自己是儿子吗 */ 59 | if ( (!fsm->state_current->transition_nums) && (!fsm->state_current->state_parent)) 60 | { 61 | return STATEM_STATE_NOCHANGE; 62 | } 63 | 64 | struct state *state_next = fsm->state_current; 65 | 66 | do { 67 | struct transition *transition = get_transition( fsm, state_next, event ); 68 | 69 | /* If there were no transitions for the given event for the current 70 | * state, check if there are any transitions for any of the parent 71 | * states (if any): */ 72 | /* continue并不会跳过while的条件判断 */ 73 | if ( !transition ) 74 | { 75 | state_next = state_next->state_parent; 76 | continue; 77 | } 78 | 79 | /* A transition must have a next state defined. If the user has not 80 | * defined the next state, go to error state: */ 81 | if ( !transition->state_next ) 82 | { 83 | go_to_state_error( fsm, event ); 84 | return STATEM_ERR_STATE_RECHED; 85 | } 86 | 87 | state_next = transition->state_next; 88 | 89 | /* If the new state is a parent state, enter its entry state (if it has 90 | * one). Step down through the whole family tree until a state without 91 | * an entry state is found: */ 92 | while ( state_next->state_entry ) 93 | { 94 | state_next = state_next->state_entry; 95 | } 96 | 97 | /* Run exit action only if the current state is left (only if it does 98 | * not return to itself): */ 99 | if ( state_next != fsm->state_current && fsm->state_current->action_exti ) 100 | { 101 | fsm->state_current->action_exti( fsm->state_current->data, event ); 102 | } 103 | 104 | /* Run transition action (if any): */ 105 | if ( transition->action ) 106 | { 107 | transition->action( fsm->state_current->data, event, state_next->data ); 108 | } 109 | 110 | fsm->state_previous = fsm->state_current; 111 | 112 | /* Call the new state's entry action if it has any (only if state does 113 | * not return to itself): */ 114 | if ( state_next != fsm->state_current && state_next->action_entry ) 115 | { 116 | state_next->action_entry( state_next->data, event ); 117 | } 118 | 119 | fsm->state_current = state_next; 120 | 121 | /* If the state returned to itself: */ 122 | if ( fsm->state_current == fsm->state_previous ) 123 | { 124 | return STATEM_STATE_LOOPSELF; 125 | } 126 | 127 | if ( fsm->state_current == fsm->state_error ) 128 | { 129 | return STATEM_ERR_STATE_RECHED; 130 | } 131 | 132 | /* If the new state is a final state, notify user that the state 133 | * machine has stopped: */ 134 | /* 下一个状态没有儿子,也没有父亲,你们家只有你了 */ 135 | if ((!fsm->state_current->transition_nums) && (!fsm->state_current->state_parent)) 136 | { 137 | return STATEM_FINAL_STATE_RECHED; 138 | } 139 | 140 | return STATEM_STATE_CHANGED; 141 | } while ( state_next ); 142 | 143 | return STATEM_STATE_NOCHANGE; 144 | } 145 | 146 | struct state *statem_state_current( struct state_machine *fsm ) 147 | { 148 | if ( !fsm ) 149 | { 150 | return NULL; 151 | } 152 | 153 | return fsm->state_current; 154 | } 155 | 156 | struct state *statem_state_previous( struct state_machine *fsm ) 157 | { 158 | if ( !fsm ) 159 | { 160 | return NULL; 161 | } 162 | 163 | return fsm->state_previous; 164 | } 165 | 166 | static void go_to_state_error( struct state_machine *fsm, 167 | struct event *const event ) 168 | { 169 | fsm->state_previous = fsm->state_current; 170 | fsm->state_current = fsm->state_error; 171 | 172 | /* 本地错误状态要执行进入,进入错误状态肯定是数据设置错误,不是状态机不符合逻辑 */ 173 | if ( fsm->state_current && fsm->state_current->action_entry ) 174 | { 175 | fsm->state_current->action_entry( fsm->state_current->data, event ); 176 | } 177 | } 178 | 179 | static struct transition *get_transition( struct state_machine *fsm, 180 | struct state *state, struct event *const event ) 181 | { 182 | size_t i; 183 | 184 | if( !state ) 185 | { 186 | return NULL; 187 | } 188 | 189 | for ( i = 0; i < state->transition_nums; ++i ) 190 | { 191 | struct transition *t = &state->transitions[ i ]; 192 | 193 | /* A transition for the given event has been found: */ 194 | if ( t->event_type == event->type ) 195 | { 196 | /* 轮询监视,如果没有监视,或者监视测试通过返回这个转移 197 | * 监视输入条件和事件,可以做成当前转移的条件要和事件的数据吻合 198 | * 也可以做成当前条件要符合什么,事件的数据参数要符合什么 199 | */ 200 | if ( !t->guard ) 201 | { 202 | return t; 203 | } 204 | /* If transition is guarded, ensure that the condition is held: */ 205 | else if ( t->guard( t->condition, event ) ) 206 | { 207 | return t; 208 | } 209 | } 210 | } 211 | 212 | /* No transitions found for given event for given state: */ 213 | return NULL; 214 | } 215 | 216 | int statem_stopped( struct state_machine *state_machine ) 217 | { 218 | if ( !state_machine ) 219 | { 220 | return -1; 221 | } 222 | 223 | return (state_machine->state_current->transition_nums == 0); 224 | } 225 | --------------------------------------------------------------------------------