4. 状态机模式建模连接状态

4.1 原理与设计模式意图

BLE 连接的生命周期本质是一个有限状态机(FSM)。连接建立、断开、参数更新等事件驱动状态迁移。将连接状态显式建模为状态机,可以消除 if-else 层级爆炸,使状态转换逻辑可测试、可维护。

核心意图:

  • 封装状态行为:每个状态(Idle、Connecting、Connected、Updating)拥有独立的进入/退出/处理逻辑。
  • 事件驱动迁移:外部事件(连接成功、超时、断开、参数更新请求)触发一次状态变迁。
  • 避免非法转换:状态机严格定义合法边,运行时拒绝非法事件。

4.2 状态转换图

图4-1 BLE 连接状态机(4 个状态,6 条转换边)
BLE 连接状态机 Idle Connecting Connected ParamUpdate connect() connected timeout / reject update request update complete disconnect disconnect 注:ParamUpdate 状态内可多次调整连接间隔、延迟、超时参数,完成后返回 Connected。

4.3 状态机代码实现(C 语言示例)

以下为 Zephyr RTOS 风格的状态机实现,包含状态枚举、事件枚举、状态表和状态机执行函数。

/* ble_conn_fsm.h */
#ifndef BLE_CONN_FSM_H
#define BLE_CONN_FSM_H

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

/* 状态枚举 */
typedef enum {
    CONN_STATE_IDLE = 0,
    CONN_STATE_CONNECTING,
    CONN_STATE_CONNECTED,
    CONN_STATE_PARAM_UPDATING,
    CONN_STATE_COUNT
} conn_state_t;

/* 事件枚举 */
typedef enum {
    CONN_EVT_CONNECT_REQ,        /* 发起连接请求 */
    CONN_EVT_CONNECTED,          /* 连接成功 */
    CONN_EVT_CONNECT_FAIL,       /* 连接失败或超时 */
    CONN_EVT_DISCONNECT,         /* 收到断开事件 */
    CONN_EVT_PARAM_UPDATE_REQ,   /* 收到或发起参数更新请求 */
    CONN_EVT_PARAM_UPDATE_DONE,  /* 参数更新完成 */
    CONN_EVT_COUNT
} conn_evt_t;

/* 状态机上下文 */
typedef struct {
    conn_state_t  state;         /* 当前状态 */
    uint16_t      conn_handle;   /* 连接句柄 */
    uint16_t      conn_interval; /* 当前连接间隔 */
    /* 其他连接参数 */
} conn_fsm_t;

/* 状态迁移表(条目表示:当前状态 + 事件 -> 下一状态) */
typedef struct {
    conn_state_t  current;
    conn_evt_t    event;
    conn_state_t  next;
} conn_transition_t;

static const conn_transition_t trans_table[] = {
    { CONN_STATE_IDLE,            CONN_EVT_CONNECT_REQ,       CONN_STATE_CONNECTING },
    { CONN_STATE_CONNECTING,      CONN_EVT_CONNECTED,         CONN_STATE_CONNECTED },
    { CONN_STATE_CONNECTING,      CONN_EVT_CONNECT_FAIL,      CONN_STATE_IDLE },
    { CONN_STATE_CONNECTED,       CONN_EVT_DISCONNECT,        CONN_STATE_IDLE },
    { CONN_STATE_CONNECTED,       CONN_EVT_PARAM_UPDATE_REQ,  CONN_STATE_PARAM_UPDATING },
    { CONN_STATE_PARAM_UPDATING,  CONN_EVT_PARAM_UPDATE_DONE, CONN_STATE_CONNECTED },
    { CONN_STATE_PARAM_UPDATING,  CONN_EVT_DISCONNECT,        CONN_STATE_IDLE },
};

/* 状态机初始化 */
void conn_fsm_init(conn_fsm_t *fsm)
{
    fsm->state = CONN_STATE_IDLE;
    fsm->conn_handle = 0xFFFF;
    fsm->conn_interval = 0;
}

/* 状态机执行事件处理(返回 true 表示迁移成功) */
bool conn_fsm_handle_event(conn_fsm_t *fsm, conn_evt_t evt)
{
    for (size_t i = 0; i < sizeof(trans_table)/sizeof(trans_table[0]); i++) {
        if (trans_table[i].current == fsm->state &&
            trans_table[i].event == evt) {
            /* 执行状态退出动作(可在此回调) */
            conn_state_exit(fsm->state);
            /* 更新状态 */
            fsm->state = trans_table[i].next;
            /* 执行状态进入动作 */
            conn_state_enter(fsm->state, evt);
            return true;
        }
    }
    /* 非法事件:打印日志或触发断言 */
    return false;
}
#endif /* BLE_CONN_FSM_H */

4.4 配置/参数/命令

在 Zephyr 工程中,通过 Kconfig 配置连接参数更新相关选项:

参数名 默认值 说明
CONFIG_BT_CONNy启用连接管理器
CONFIG_BT_CONN_LOG_LEVEL_DBGn连接状态机调试日志
CONFIG_BT_LL_SW_CONN_PARAM_UPDATEy支持连接参数更新请求
CONFIG_BT_SMPy启用安全管理(配对影响连接)
CONFIG_BT_MAX_CONN1最大并发连接数

运行时的连接参数可通过 API 设置:

/* 示例:请求更新连接间隔 */
struct bt_le_conn_param param = {
    .interval_min = 24,     /* 30 ms */
    .interval_max = 40,     /* 50 ms */
    .latency = 0,
    .timeout = 400,         /* 4 s */
};
err = bt_conn_le_param_update(conn, &param);
if (err) {
    printk("param update failed: %d\n", err);
}

4.5 调试日志分析

开启调试日志(CONFIG_BT_CONN_LOG_LEVEL_DBG=y)后,UART 输出示例:

D: bt_hci_cmd: opcode 0x200d status 0x00
D: bt_conn: state IDLE -> CONNECTING (evt CONNECT_REQ)
D: bt_conn: LE link established (handle 0x001)
D: bt_conn: state CONNECTING -> CONNECTED (evt CONNECTED)
D: bt_conn: param update request received (interval 24..40)
D: bt_conn: state CONNECTED -> PARAM_UPDATING (evt PARAM_UPDATE_REQ)
D: bt_conn: param update complete (interval 30)
D: bt_conn: state PARAM_UPDATING -> CONNECTED (evt PARAM_UPDATE_DONE)
D: bt_conn: disconnected, reason 0x16 (remote user term)
D: bt_conn: state CONNECTED -> IDLE (evt DISCONNECT)

每行包含当前状态→下一状态及事件名称,与状态机定义严格对应。

4.6 易错点与优化建议

易错点:
  • 状态迁移表漏写某条合法边(如 ParamUpdate 中的异常断开),导致非法事件触发断言。
  • 在状态退出/进入函数里执行阻塞操作(如等待 flash 写入),会阻塞 BLE 协议栈线程。
  • 连接参数更新未使用蓝牙规范规定的有效范围(如 interval 必须是 1.25ms 的整数倍)。
优化建议:
  • 将状态机执行放在 HCI 命令接收的低优先级线程中,避免在 ISR 中直接调用。
  • 使用状态机表驱动方式(如函数指针数组),减少 switch-case 嵌套。
  • 记录状态迁移历史(如循环缓冲区),供后期问题定位。
  • 连接参数更新前,先检查对端是否支持 L2CAP Connection Parameter Update 请求。