一、项目简介

state_machine 是一个轻量级、可移植、支持层级状态的 C 语言状态机框架,适用于嵌入式和 Linux 应用。其核心特点有:数据驱动、父子状态(层级嵌套)、入口/出口动作、守卫条件、外/内部转换、未处理事件钩子等,通过“状态/转换表+生命周期钩子+层级状态”三大机制,让 开发者能直观的搭建事件驱动系统。

二、状态机核心结构与字段详解

状态机的核心是通过结构体和表格将所有状态、事件、转换关系以数据形式描述。以 posix_app.c 典型片段为例:

static const SM_State sm_state_power_on = {
    .parent = NULL,
    .entryAction = sm_entry_print,
    .exitAction = sm_exit_print,
    .transitions = sm_trans_power_on,
    .numTransitions = sizeof(sm_trans_power_on) / sizeof(sm_trans_power_on[0]),
    .name = "PowerOn"
};

字段说明与关系

  • .parent
    指定父状态,用于实现层级嵌套。例如某些状态为另一个状态的子状态,可以复用父状态的行为和转换。如果没有父状态则为 NULL

  • .entryAction
    进入该状态时自动调用的回调函数。用于初始化、打印、资源分配等。例如 sm_entry_print 会输出当前进入的状态。

  • .exitAction
    离开该状态时自动调用的回调函数。常用于资源释放、日志打印、清理现场等。例如 sm_exit_print 输出离开状态。

  • .transitions
    指向一个“转换表”(数组),每个元素描述在特定事件下如何转换到新状态,以及是否有守卫条件和动作。
    例如:

    static const SM_Transition sm_trans_power_on[] = {
        {SM_EVENT_POST_STEP_OK, &sm_state_post_step, NULL, NULL, SM_TRANSITION_EXTERNAL},
        {SM_EVENT_POST_STEP_FAIL, &sm_state_post_fail, NULL, NULL, SM_TRANSITION_EXTERNAL},
        {SM_EVENT_POST_DONE, &sm_state_post_pass, NULL, NULL, SM_TRANSITION_EXTERNAL}
    };
    

    每一行都表示“在收到某事件时,是否满足守卫条件,切换到哪一状态,是否有动作”。

  • .numTransitions
    当前状态的转换表的元素数量。通常用 sizeof(transition数组)/sizeof(transition数组[0]) 得到。用于遍历转换表查找匹配的事件。

  • .name
    状态名,便于调试、日志输出和可视化。

字段之间的关系

  • 每个状态(SM_State)单独描述入口/出口动作与转换表。
  • 每个转换(SM_Transition)描述“事件-条件-动作-目标状态”。
  • 多个状态可指向同一个父状态,实现层级和复用。
  • 转换表决定了状态间的流转路径,不同状态有各自的转换表,保证逻辑清晰。
  • 入口和出口动作保证每次状态切换时业务动作的一致性与原子性。

为什么需要这么多表和回调函数?

  • 多表设计:每个状态都有自己的转换表,使得复杂系统的状态流转能被拆分、解耦、模块化。避免了“大 switch-case”带来的臃肿和难以维护。
  • 入口/出口动作:让每个状态拥有独立的生命周期钩子,便于扩展和测试。比如进入"PowerOn"时自动初始化,离开"Run"时自动清理。
  • 守卫条件:转换表中的守卫条件(回调函数)可根据业务数据决定是否允许状态转换,提高灵活性和健壮性。

三、如何使用?以 examples/posix_app.c 为例

1. 定义事件、状态、转换表和动作

  • 事件:用枚举定义系统所有关心的事件(如 SM_EVENT_POWER_ON)。
  • 状态:每个状态用 SM_State 结构体描述,指定入口/出口动作、父状态、转换表、名字。
  • 转换表:每个状态对应一个转换表(数组),描述收到各种事件时如何跳转。
  • 动作函数:如 sm_entry_printsm_exit_printsm_entry_post,用于打印、初始化、计数等。

2. 状态机初始化与事件分发

  • 初始化状态机:指定初始状态、缓冲区、用户数据、未处理事件钩子等。
  • 事件分发:可在主线程、或通过消息队列/线程异步投递事件(如 sm_post_event),状态机会自动查表,匹配事件,执行相应的动作和切换。

3. 典型状态机流程(以 PowerOn 为例)

  1. 启动,进入 Off 状态,执行 entryAction(打印日志)。
  2. 收到 poweron 事件,根据 sm_trans_off 转换表跳转到 PowerOn,执行 exitAction 离开 Off,再执行 entryAction 进入 PowerOn
  3. PowerOnentryAction 可自动触发 POST 流程(如自动分发 SM_EVENT_POST_STEP_OK)。
  4. 根据事件和转换表不断流转,直至运行、维护、升级等状态。

4. 状态流转可视化

poweron

stepok

stepfail

done

stepfail [guard]

retry

run

runerr

recover [guard]

maint

exitmaint

upgrade

upgradedone

reset

Off

PowerOn

PostStep

PostFail

PostPass

PostRetry

Run

RunError

Maint

Upgrade

UpgradeDone

四、实用代码示例与输出说明

POSIX 示例详解

examples/posix_app.c 展示了如何用 POSIX 线程和消息队列驱动状态机。比如用命令行参数投递事件,状态机线程自动处理,输出如下:

==> Enter Off
Complex State machine initialized. Initial State: Off

--- Event received: 1, dispatching to state machine ---
<== Exit Off
==> Enter PowerOn
POST: Start self-check sequence.
...
Current State: Run

每一行分别代表:进入状态、收到事件、离开状态、进入下一个状态、执行入口动作、当前状态。

五、设计优势与应用场景

  • 结构清晰:每个状态、每种转换、每个生命周期动作都用数据与函数分离,易于管理和扩展。
  • 层级复用:通过 parent 字段实现父子状态逻辑复用,复杂系统无需重复代码。
  • 高可读性与可调试性:每个状态和事件流转都能在日志中清晰体现。
  • 易于集成:既能在嵌入式 RTOS、也能在 Linux/多线程环境下部署。
  • 适用范围广:如设备自检、电源管理、协议解析、复杂流程控制等。

参考项目与文档

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐