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
LOCKED 初始状态 UNLOCKED 临时状态 BUTTON_PRESS BUTTON_PRESS TIMEOUT TIMEOUT 按键事件 超时事件 事件驱动状态转换表驱动