02_state

嵌入式 C 设计模式 · 状态模式:干掉你的巨型 switch-case

系列专栏:嵌入式 C 设计模式实战

本篇:状态模式(State Pattern)


🔍 问题:蓝牙耳机的连接状态机

先看一段真实代码。一个蓝牙耳机的连接管理:

c 复制代码
void bt_event_handler(bt_event_t evt) {
    switch (bt_state) {
    case BT_POWER_OFF:
        if (evt == EVT_POWER_ON) {
            hci_init();
            start_advertising();
            bt_state = BT_ADVERTISING;
        }
        break;

    case BT_ADVERTISING:
        if (evt == EVT_CONNECT_REQ) {
            hci_connect_accept();
            start_sco_link();
            bt_state = BT_CONNECTED;
        } else if (evt == EVT_TIMEOUT) {
            enter_sleep();
            bt_state = BT_SLEEP;
        }
        break;

    case BT_CONNECTED:
        if (evt == EVT_DISCONNECT) {
            start_advertising();
            bt_state = BT_ADVERTISING;
        } else if (evt == EVT_AUDIO_START) {
            start_a2dp_stream();
            /* 注意:这里没有状态转换?bug! */
        }
        break;
    // ... 还有更多的 case ...
    }
}

这个代码有什么问题?

  1. 一个函数越来越长 --- 每加一个状态,switch 多一个 case;每加一个事件,每个 case 多一个 if
  2. 转换逻辑散落各处 --- 你永远不知道从 A 到 B 的转换是不是在别的地方也有一份
  3. 容易漏掉处理 --- 比如上面 EVT_AUDIO_STARTBT_CONNECTED 状态里该转换到 BT_STREAMING,但忘了写
  4. 非法状态转换无法拦截 --- 如果有人在 BT_POWER_OFF 状态收到了 EVT_AUDIO_START,会发生什么?
  5. 多人协作灾难 --- 两个人同时改这个函数,merge conflict 是必然的

核心问题:状态和行为耦合在一起,还塞在同一个函数里。


💡 模式:状态模式

一句话

每个状态单独写一个文件/结构体,状态之间的转换由状态自己决定。

核心思想

复制代码
┌─────────────────┐
│   状态上下文      │  持有当前状态指针
│  (Context)       │
│  ┌─────────┐    │
│  │ 状态 A  │    │  每个状态是一个独立的对象
│  │ 状态 B  │    │  状态之间互相知道怎么转换
│  │ 状态 C  │    │
│  └─────────┘    │
└─────────────────┘

上电
EVT_POWER_ON
EVT_CONNECT_REQ
EVT_TIMEOUT
EVT_WAKE
EVT_DISCONNECT
EVT_POWER_OFF
EVT_AUDIO_START
EVT_AUDIO_STOP
POWER_OFF
ADVERTISING
CONNECTED
SLEEP
STREAMING

  • 把每个状态的处理逻辑封装到独立的"状态对象"中
  • 状态之间自己决定"下个状态是谁"
  • 上下文只需要调用当前状态的接口,不用管具体逻辑

在 C 语言中的实现方式

C 语言没有虚函数,我们用 struct + 函数指针 来实现多态:

c 复制代码
/* 每个状态提供一个接口表 */
typedef struct {
    void (*enter)(void);          /* 进入该状态时调用 */
    void (*exit)(void);           /* 离开该状态时调用 */
    void (*handle_event)(int);    /* 处理事件 */
    const char *name;             /* 调试用 */
} state_t;

🖥️ 代码:用状态模式重构蓝牙连接管理

第一步:定义状态接口

c 复制代码
/* state_machine.h */
#ifndef STATE_MACHINE_H
#define STATE_MACHINE_H

#include <stdint.h>
#include <stdbool.h>

/* 事件定义 */
typedef enum {
    EVT_POWER_ON,
    EVT_POWER_OFF,
    EVT_CONNECT_REQ,
    EVT_DISCONNECT,
    EVT_AUDIO_START,
    EVT_AUDIO_STOP,
    EVT_TIMEOUT,
    EVT_ERROR,
} event_t;

/* 状态接口表 */
typedef struct state_s state_t;
struct state_s {
    void     (*enter)(void);
    void     (*exit)(void);
    state_t* (*handle_event)(event_t evt);  /* 返回下一个状态 */
    const char *name;
};

/* 状态机上下文 */
typedef struct {
    state_t *current;
    state_t *previous;
    uint32_t entry_time;   /* 进入当前状态的时间戳,用于超时判断 */
} state_machine_t;

/* 状态机 API */
void   sm_init(state_machine_t *sm, state_t *init_state);
void   sm_dispatch(state_machine_t *sm, event_t evt);
state_t* sm_get_current(state_machine_t *sm);
const char* sm_get_state_name(state_machine_t *sm);

#endif

第二步:实现状态机引擎

c 复制代码
/* state_machine.c */
#include "state_machine.h"
#include <stdio.h>

void sm_init(state_machine_t *sm, state_t *init_state) {
    sm->current  = init_state;
    sm->previous = NULL;
    sm->entry_time = 0;  /* 实际项目中用硬件定时器 */
    printf("[SM] Init -> %s\n", init_state->name);
    if (init_state->enter) {
        init_state->enter();
    }
}

void sm_dispatch(state_machine_t *sm, event_t evt) {
    if (!sm || !sm->current) return;

    state_t *next = sm->current->handle_event(evt);

    if (next && next != sm->current) {
        /* 状态转换 */
        printf("[SM] %s --[%d]--> %s\n",
               sm->current->name, evt, next->name);

        if (sm->current->exit) {
            sm->current->exit();
        }
        sm->previous = sm->current;
        sm->current  = next;
        sm->entry_time = 0;  /* 重置计时 */
        if (next->enter) {
            next->enter();
        }
    }
    /* 如果 next == NULL,表示该事件在当前状态被忽略 */
    /* 如果 next == current,表示留在当前状态(比如重复的通知) */
}

state_t* sm_get_current(state_machine_t *sm) {
    return sm ? sm->current : NULL;
}

const char* sm_get_state_name(state_machine_t *sm) {
    return sm && sm->current ? sm->current->name : "NULL";
}

第三步:定义具体状态

power_off 状态:

c 复制代码
/* bt_state_power_off.c */
#include "state_machine.h"
#include <stdio.h>

/* 前置声明其他状态 */
extern state_t bt_state_advertising;
extern state_t bt_state_connected;

static state_t* on_power_off(event_t evt) {
    switch (evt) {
    case EVT_POWER_ON:
        printf("  [PowerOff] Power ON -> start HCI\n");
        hci_init();
        return &bt_state_advertising;  /* 转换到 advertising 状态 */
    case EVT_TIMEOUT:
        printf("  [PowerOff] Timeout -> enter deep sleep\n");
        // enter_deep_sleep();
        return NULL;  /* NULL = 忽略,留在当前状态 */
    default:
        printf("  [PowerOff] Ignore event %d (not powered on yet)\n", evt);
        return NULL;
    }
}

static void enter_power_off(void) {
    printf("  >>> Enter POWER_OFF\n");
    hci_shutdown();
    led_set_color(LED_OFF);
}

static void exit_power_off(void) {
    printf("  <<< Exit POWER_OFF\n");
}

state_t bt_state_power_off = {
    .enter        = enter_power_off,
    .exit         = exit_power_off,
    .handle_event = on_power_off,
    .name         = "POWER_OFF"
};

advertising 状态:

c 复制代码
/* bt_state_advertising.c */
#include "state_machine.h"
#include <stdio.h>

extern state_t bt_state_power_off;
extern state_t bt_state_connected;
extern state_t bt_state_sleep;

static state_t* on_advertising(event_t evt) {
    switch (evt) {
    case EVT_CONNECT_REQ:
        printf("  [Adv] Connect request received\n");
        hci_accept_connection();
        return &bt_state_connected;
    case EVT_TIMEOUT:
        printf("  [Adv] Advertising timeout\n");
        return &bt_state_sleep;
    case EVT_POWER_OFF:
        printf("  [Adv] Power OFF\n");
        return &bt_state_power_off;
    default:
        return NULL;
    }
}

static void enter_advertising(void) {
    printf("  >>> Enter ADVERTISING\n");
    hci_start_advertising();
    led_set_color(LED_FAST_BLINK);  /* 快闪表示可配对 */
}

static void exit_advertising(void) {
    printf("  <<< Exit ADVERTISING\n");
    hci_stop_advertising();
}

state_t bt_state_advertising = {
    .enter        = enter_advertising,
    .exit         = exit_advertising,
    .handle_event = on_advertising,
    .name         = "ADVERTISING"
};

connected 状态:

c 复制代码
/* bt_state_connected.c */
#include "state_machine.h"
#include <stdio.h>

extern state_t bt_state_advertising;
extern state_t bt_state_streaming;
extern state_t bt_state_power_off;

static state_t* on_connected(event_t evt) {
    switch (evt) {
    case EVT_DISCONNECT:
        printf("  [Connected] Disconnected\n");
        return &bt_state_advertising;
    case EVT_AUDIO_START:
        printf("  [Connected] Audio stream start\n");
        a2dp_open_stream();
        return &bt_state_streaming;
    case EVT_POWER_OFF:
        printf("  [Connected] Power OFF\n");
        hci_disconnect();
        return &bt_state_power_off;
    default:
        return NULL;
    }
}

static void enter_connected(void) {
    printf("  >>> Enter CONNECTED\n");
    led_set_color(LED_SLOW_BLINK);
}

static void exit_connected(void) {
    printf("  <<< Exit CONNECTED\n");
}

state_t bt_state_connected = {
    .enter        = enter_connected,
    .exit         = exit_connected,
    .handle_event = on_connected,
    .name         = "CONNECTED"
};

streaming(A2DP 流传输)状态:

c 复制代码
/* bt_state_streaming.c */
#include "state_machine.h"
#include <stdio.h>

extern state_t bt_state_connected;
extern state_t bt_state_power_off;

static state_t* on_streaming(event_t evt) {
    switch (evt) {
    case EVT_AUDIO_STOP:
        printf("  [Streaming] Audio stream stop\n");
        a2dp_close_stream();
        return &bt_state_connected;
    case EVT_DISCONNECT:
        printf("  [Streaming] Disconnected during streaming\n");
        a2dp_close_stream();
        return &bt_state_advertising;
    case EVT_POWER_OFF:
        printf("  [Streaming] Power OFF\n");
        a2dp_close_stream();
        hci_disconnect();
        return &bt_state_power_off;
    default:
        return NULL;
    }
}

static void enter_streaming(void) {
    printf("  >>> Enter STREAMING\n");
    led_set_color(LED_SOLID);
}

static void exit_streaming(void) {
    printf("  <<< Exit STREAMING\n");
}

state_t bt_state_streaming = {
    .enter        = enter_streaming,
    .exit         = exit_streaming,
    .handle_event = on_streaming,
    .name         = "STREAMING"
};

第四步:使用

c 复制代码
/* main.c */
#include "state_machine.h"

state_machine_t g_bt_sm;
extern state_t bt_state_power_off;

int main(void) {
    system_init();
    sm_init(&g_bt_sm, &bt_state_power_off);

    while (1) {
        event_t evt = get_next_event();
        sm_dispatch(&g_bt_sm, evt);
    }
}

📊 重构前后对比

维度 重构前 重构后
单个函数长度 200+ 行 每个状态 < 50 行
新增状态 改 switch,加 case,担心影响其他 case 新建一个 .c 文件,注册到状态表
新增事件 每个 case 加一个 if 只改关心的状态的函数
非法转换 无拦截,运行时可能崩溃 每个状态自行决定合法事件,非法事件直接忽略
多人协作 都在改同一个函数,merge conflict 各改各的文件,零冲突
调试 printf 只能在函数里散落添加 enter/exit 有统一 hook,转换有日志

🔄 进阶:层次状态机

项目做大了你会发现一个问题------多个状态有相同的行为

例如 BT_CONNECTEDBT_STREAMING 都会响应 EVT_DISCONNECT,代码是重复的。

解法:层次状态机(HSM,Hierarchical State Machine)

c 复制代码
/* 父状态:处理所有"已连接"状态的公共行为 */
static state_t* on_connected_base(event_t evt) {
    switch (evt) {
    case EVT_DISCONNECT:
        /* 公共断开处理 */
        return &bt_state_advertising;
    case EVT_POWER_OFF:
        return &bt_state_power_off;
    default:
        return NULL;
    }
}

具体状态处理自己的专属事件,处理不了的传给父状态:

c 复制代码
static state_t* on_streaming(event_t evt) {
    switch (evt) {
    case EVT_AUDIO_STOP:
        return &bt_state_connected;
    default:
        return on_connected_base(evt);  /* 交给父状态处理 */
    }
}

层次状态机在蓝牙协议栈里用得很多------HCI、GATT、A2DP 的协议状态机几乎全是 HSM。


✍️ 练习

  1. 加一个新状态: 给这个状态机加一个 BT_RECONNECTING(断线重连)状态。从 BT_CONNECTED 收到 EVT_LINK_LOSS 进入,尝试重连成功后回到 BT_CONNECTED,失败回到 BT_ADVERTISING

  2. 思考题: 你现在的项目里,哪个 switch-case 最大?如果用状态模式重构,会拆成几个文件?

  3. 可选: 给状态机加一个 state_history[10] 环形缓冲区,记录最近 10 次状态转换,调试时打印出来


总结

状态模式是嵌入式开发最重要的模式,没有之一。

蓝牙协议栈、按键处理、充电管理、OTA 升级------几乎每个嵌入式模块本质上都是一个状态机。用状态模式来组织代码,不是"锦上添花",而是"基本操作"。

核心:每个状态一个文件,状态转换由状态自己决定。


下篇预告:策略模式(Strategy Pattern)------ 如何优雅地切换 EQ、编解码器、ANC 模式?

相关推荐
qcx238 小时前
Warp源码深度解析(七):Token预算策略——双轨计费、上下文溢出与摘要压缩
人工智能·设计模式·rust·wrap
Cosolar1 天前
提示词工程面试题系列 - Zero-Shot Prompting 和 Few-Shot Prompting 的核心区别是什么?
人工智能·设计模式·架构
geovindu1 天前
go:Template Method Pattern
开发语言·后端·设计模式·golang·模板方法模式
钝挫力PROGRAMER1 天前
贫血模型的改进
java·开发语言·设计模式·架构
byte轻骑兵1 天前
【HID】规范精讲[7]: 蓝牙HID底层核心——基带与LMP依赖深度解析
网络·人工智能·人机交互·蓝牙·键盘·hid
qcx231 天前
Warp源码深度解析(二):自研GPU UI框架——WarpUI的ECH模式与渲染管线
人工智能·ui·设计模式·rust
Evand J1 天前
【MATLAB代码介绍】基于RSSI的蓝牙定位程序,N个锚点、二维平面
开发语言·matlab·蓝牙·定位·rssi
qcx231 天前
Warp源码深度解析(三):Block-Based终端引擎——Grid模型、PTY与Shell Integration
人工智能·设计模式·架构·wrap
mounter6251 天前
Linux Kernel Design Patterns (Part 2):从经典链表到现代 XArray,拆解内核复杂数据结构的设计哲学
linux·数据结构·链表·设计模式·内存管理·kernel