4. 事件驱动的多级菜单系统
任务定义
本案例实现一个基于事件驱动的事件状态机,用于处理按键事件与定时器事件驱动的设备状态转换。目标设备为一个简化版门禁控制器,包含 LOCKED(锁定)、UNLOCKED(解锁)两个状态,支持 BUTTON_PRESS(按键按下)、TIMEOUT(超时)两类事件。核心要求:状态转换完全由事件触发,状态机核心逻辑与事件产生机制解耦,便于后续扩展新状态或新事件类型。
状态机设计
状态转换规则如下:
- LOCKED + BUTTON_PRESS → UNLOCKED(按键开门)
- UNLOCKED + BUTTON_PRESS → LOCKED(按键关门)
- UNLOCKED + TIMEOUT → LOCKED(超时自动上锁)
- LOCKED + TIMEOUT → LOCKED(锁定态超时无动作,状态不变)
事件通过事件队列异步投递,状态机主循环从队列取出事件并分发处理。该设计保证事件产生者(按键扫描、定时器)与状态处理逻辑互不阻塞。
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 事件类型定义 */
typedef enum {
EVENT_BUTTON_PRESS = 0,
EVENT_TIMEOUT,
EVENT_MAX
} EventType;
/* 状态定义 */
typedef enum {
STATE_LOCKED = 0,
STATE_UNLOCKED,
STATE_MAX
} State;
/* 事件结构体 */
typedef struct {
EventType type;
int param; /* 预留参数,当前未使用 */
} Event;
/* 简单事件队列(环形缓冲) */
#define QUEUE_SIZE 8
typedef struct {
Event buf[QUEUE_SIZE];
int head;
int tail;
int count;
} EventQueue;
static EventQueue queue;
void queue_init(EventQueue *q) {
memset(q, 0, sizeof(EventQueue));
}
int queue_push(EventQueue *q, Event ev) {
if (q->count >= QUEUE_SIZE) {
return -1; /* 队列满 */
}
q->buf[q->tail] = ev;
q->tail = (q->tail + 1) % QUEUE_SIZE;
q->count++;
return 0;
}
int queue_pop(EventQueue *q, Event *ev) {
if (q->count <= 0) {
return -1; /* 队列空 */
}
*ev = q->buf[q->head];
q->head = (q->head + 1) % QUEUE_SIZE;
q->count--;
return 0;
}
/* 状态机上下文 */
typedef struct {
State current_state;
EventQueue *eq;
} StateMachine;
/* 状态转换表: [当前状态][事件类型] = 目标状态 */
static const State transition_table[STATE_MAX][EVENT_MAX] = {
/* STATE_LOCKED */
{ STATE_UNLOCKED, /* BUTTON_PRESS */
STATE_LOCKED /* TIMEOUT */
},
/* STATE_UNLOCKED */
{ STATE_LOCKED, /* BUTTON_PRESS */
STATE_LOCKED /* TIMEOUT */
}
};
/* 状态名映射 */
const char* state_name(State s) {
switch (s) {
case STATE_LOCKED: return "LOCKED";
case STATE_UNLOCKED: return "UNLOCKED";
default: return "UNKNOWN";
}
}
const char* event_name(EventType e) {
switch (e) {
case EVENT_BUTTON_PRESS: return "BUTTON_PRESS";
case EVENT_TIMEOUT: return "TIMEOUT";
default: return "UNKNOWN";
}
}
/* 事件处理核心 */
void sm_handle_event(StateMachine *sm, Event ev) {
State next = transition_table[sm->current_state][ev.type];
printf("[事件] %-12s | %s --> %s\n",
event_name(ev.type),
state_name(sm->current_state),
state_name(next));
sm->current_state = next;
}
/* 状态机主循环 */
void sm_run(StateMachine *sm) {
Event ev;
while (queue_pop(sm->eq, &ev) == 0) {
sm_handle_event(sm, ev);
}
}
/* 模拟外部事件产生(按键/定时器回调) */
void post_button_press(StateMachine *sm) {
Event ev = { EVENT_BUTTON_PRESS, 0 };
if (queue_push(sm->eq, ev) != 0) {
printf("[错误] 事件队列已满,丢弃按键事件\n");
}
}
void post_timeout(StateMachine *sm) {
Event ev = { EVENT_TIMEOUT, 0 };
if (queue_push(sm->eq, ev) != 0) {
printf("[错误] 事件队列已满,丢弃超时事件\n");
}
}
int main(void) {
StateMachine sm;
queue_init(&queue);
sm.current_state = STATE_LOCKED;
sm.eq = &queue;
printf("=== 门禁状态机演示 ===\n");
printf("初始状态: %s\n\n", state_name(sm.current_state));
/* 模拟事件序列 */
post_button_press(&sm); /* 按键开门 */
post_timeout(&sm); /* 超时上锁 */
post_button_press(&sm); /* 按键开门 */
post_button_press(&sm); /* 按键关门 */
post_timeout(&sm); /* 超时(锁定态无变化) */
sm_run(&sm);
printf("\n最终状态: %s\n", state_name(sm.current_state));
return 0;
}
编译与运行
gcc -o state_machine state_machine.c
./state_machine
验证方法
程序运行后,观察输出中每个事件触发的状态转换是否符合设计规则。判定标准:
- 初始状态为 LOCKED;
- 第1个 BUTTON_PRESS 使状态变为 UNLOCKED;
- 第2个 TIMEOUT 使状态变为 LOCKED;
- 第3个 BUTTON_PRESS 使状态变为 UNLOCKED;
- 第4个 BUTTON_PRESS 使状态变为 LOCKED;
- 第5个 TIMEOUT 在 LOCKED 态不改变状态;
- 最终状态为 LOCKED。
若输出与上述规则一致,则状态机实现正确。可修改 main 中事件序列来测试其他转换路径。
预期输出
=== 门禁状态机演示 ===
初始状态: LOCKED
[事件] BUTTON_PRESS | LOCKED --> UNLOCKED
[事件] TIMEOUT | UNLOCKED --> LOCKED
[事件] BUTTON_PRESS | LOCKED --> UNLOCKED
[事件] BUTTON_PRESS | UNLOCKED --> LOCKED
[事件] TIMEOUT | LOCKED --> LOCKED
最终状态: LOCKED