💡 划重点

  • 门口三色灯环实时显示会议室空闲、占用、即将释放,减少无效推门。
  • ESP32 通过 WiFi 拉取飞书日历,同时支持 MQTT 远程同步和按键手动切换。
  • WS2812 接 GPIO13,按键接 GPIO0,整体成本控制在 65 元内。
  • 核心逻辑是状态机:日历优先、手动覆盖、MQTT 兜底,灯效直观不刺眼。

需求洞察

目标用户是办公室行政、会议室高频使用团队,以及临时找会议室的项目组。真实痛点很具体:飞书日历上明明显示空闲,走到门口却发现有人临时开会;或者会议已结束,日历没释放,别人不敢进。纸质登记、白板擦写、群里问“有人吗”都依赖人工,信息滞后且容易冲突。会议室门口缺一个低成本的“物理状态指示器”。这个项目值得做,因为它不改造门禁、不侵入现有系统,只用一盏灯把日历数据变成一眼可见的信号,安装简单,维护成本低,还能扩展到多个会议室统一管理。

方案设计

感知层负责获取状态来源。第一路是飞书日历开放平台,通过应用凭证和会议室 ID 拉取当前日程,判断是否处于占用时段;第二路是 MQTT 订阅,接收其他系统或手动控制端发来的状态指令;第三路是 GPIO0 按键,用于临时会议、设备故障或日历不同步时手动切换。控制层由 ESP32 承担,运行一个有限状态机,状态包括空闲、占用、即将释放、未知。优先级设计为:手动按键短时覆盖日历,MQTT 指令可远程覆盖,日历数据作为默认依据。执行层使用 WS2812 灯环,绿色表示空闲,红色表示占用,黄色表示即将释放或数据待确认,灯效采用呼吸或缓慢闪烁,避免走廊里过于刺眼。日志与联网层负责 WiFi 连接、MQTT 上报、串口调试和异常重连。ESP32 定时向 MQTT 主题发布当前状态、日历来源和时间戳,方便后台排查。若网络断开,设备进入未知状态,黄灯慢闪,同时保留按键切换能力。

物料清单

物料 数量 用途 估算单价 合计
ESP32 开发板 1 主控与联网 25 元 25 元
WS2812 灯环 1 三色状态显示 12 元 12 元
轻触按键 1 手动切换状态 0.5 元 0.5 元
10k 电阻 1 GPIO0 上拉 0.1 元 0.1 元
330 欧电阻 1 WS2812 数据限流 0.1 元 0.1 元
5V 电源适配器 1 供电 8 元 8 元
亚克力外壳或 3D 打印件 1 门口安装 10 元 10 元
杜邦线、洞洞板 若干 接线 5 元 5 元
合计 60.7 元

接线与结构

WS2812 的 DIN 接 ESP32 GPIO13,VCC 接 5V,GND 共地。数据线串 330 欧电阻,灯环电源两端并联 1000 微法电容更稳。按键一端接 GPIO0,另一端接 GND,GPIO0 通过 10k 电阻上拉到 3.3V。GPIO0 是启动模式引脚,上电瞬间不能按住按键,否则会进入下载模式;正常运行时按键可安全使用。若现场安装不便,可微调为 GPIO4,但本方案按给定接线。结构上,灯环朝走廊方向,按键放在外壳下方,ESP32 和电源藏在盒内。外壳留出散热孔和 USB 调试口。

核心代码

以下为 Arduino 骨架,包含 WiFi、MQTT、WS2812 和按键状态切换。飞书日历部分用 HTTP 请求占位,实际需替换应用凭证和会议室 ID。


#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_NeoPixel.h>

#define LED_PIN 13
#define KEY_PIN 0
#define LED_COUNT 12

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
WiFiClient espClient;

enum RoomState { FREE, BUSY, SOON, UNKNOWN };
RoomState state = UNKNOWN;
unsigned long lastKey = 0;

void setColor(uint8_t r, uint8_t g, uint8_t b) {
  for (int i = 0; i < LED_COUNT; i++) strip.setPixelColor(i, strip.Color(r, g, b));
  strip.show();
}

void applyState() {
  if (state == FREE) setColor(0, 80, 0);
  else if (state == BUSY) setColor(120, 0, 0);
  else if (state == SOON) setColor(120, 80, 0);
  else setColor(60, 60, 0);
}

void callback(char* topic, byte* payload, unsigned int len) {
  String msg;
  for (unsigned int i = 0; i < len; i++) msg += (char)payload[i];
  if (msg == "free") state = FREE;
  else if (msg == "busy") state = BUSY;
  else if (msg == "soon") state = SOON;
  applyState();
}

void checkKey() {
  if (digitalRead(KEY_PIN) == LOW && millis() - lastKey > 300) {
    lastKey = millis();
    state = (RoomState)((state + 1) % 3);
    applyState();
    client.publish("room/status", state == FREE ? "free" : state == BUSY ? "busy" : "soon");
  }
}

void fetchFeishuCalendar() {
  // 使用 HTTPClient 请求飞书日历接口,解析当前日程。
  // 若存在进行中的会议,state = BUSY;若 10 分钟内开始,state = SOON;否则 FREE。
  // 建议每 60 秒轮询一次,并加入 token 缓存与失败重试。
}

void setup() {
  strip.begin();
  strip.show();
  while (WiFi.status() != WL_CONNECTED) delay(500);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) client.connect("room-esp32");
  client.loop();
  checkKey();
  static unsigned long last = 0;
  if (millis() - last > 60000) { last = millis(); fetchFeishuCalendar(); applyState(); }
  delay(10);
}

调试步骤

第一步,不接灯环,先烧录串口测试,确认 ESP32 能连接 WiFi 并打印 IP。第二步,接入 WS2812,运行红绿黄三色测试,检查数据线和供电是否稳定。第三步,配置 MQTT 服务器,用手机或电脑发布 free、busy、soon,观察灯环变化和串口日志。第四步,测试按键,确认短按能循环切换状态,且上电时不误触。第五步,申请飞书开放平台应用,获取 tenant_access_token,读取会议室日历,先用串口输出日程判断结果。第六步,把飞书结果映射到状态机,设置 60 秒轮询。第七步,整机装壳,固定在门口,观察一天,重点检查网络断开、会议延长、临时取消三种异常。

设计图

三色灯智能会议室状态结构flow
1
感知输入
→
2
控制处理
→
3
执行反馈
→
4
数据上报