4. 状态机模式建模连接状态
4.1 原理与设计模式意图
BLE 连接的生命周期本质是一个有限状态机(FSM)。连接建立、断开、参数更新等事件驱动状态迁移。将连接状态显式建模为状态机,可以消除 if-else 层级爆炸,使状态转换逻辑可测试、可维护。
核心意图:
- 封装状态行为:每个状态(Idle、Connecting、Connected、Updating)拥有独立的进入/退出/处理逻辑。
- 事件驱动迁移:外部事件(连接成功、超时、断开、参数更新请求)触发一次状态变迁。
- 避免非法转换:状态机严格定义合法边,运行时拒绝非法事件。
4.2 状态转换图
图4-1 BLE 连接状态机(4 个状态,6 条转换边)
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_CONN | y | 启用连接管理器 |
| CONFIG_BT_CONN_LOG_LEVEL_DBG | n | 连接状态机调试日志 |
| CONFIG_BT_LL_SW_CONN_PARAM_UPDATE | y | 支持连接参数更新请求 |
| CONFIG_BT_SMP | y | 启用安全管理(配对影响连接) |
| CONFIG_BT_MAX_CONN | 1 | 最大并发连接数 |
运行时的连接参数可通过 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, ¶m);
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 请求。