💡 划重点

  • 用 ESP32 驱动 WS2812 灯环,门口三色显示会议室状态:绿色空闲、红色占用、黄色即将开始。设备通过 Wi-Fi 连接 MQTT,订阅飞书日历同步服务下发的状态,也支持本地按键手动切换。整机成本约 60 元,不依赖云端大屏,不打扰办公,安装即用。

需求洞察

目标用户是办公室员工、行政人员和会议室管理者。真实痛点是:会议室门口没有实时状态,员工走到门口才发现被占用;飞书日历里已经排了会,但门口没有同步标识;临时会议冲突时,里面的人不方便被打断,外面的人也不知道能不能进。行政统计会议室利用率时,也缺少轻量数据来源。

为什么值得做?会议室是高频共享空间,状态不透明会直接增加找房时间、沟通成本和会议迟到率。一个 60 元以内的三色灯,比智能门牌、电子屏更容易部署,不破坏墙面,不采集隐私,还能把飞书日历真正延伸到物理空间。它解决的是“最后一米”的信息差。

方案设计

感知层:飞书日历由一台常开电脑或服务器定时拉取,判断当前会议状态;ESP32 本地按键用于人工纠偏;MQTT 指令可作为远程控制入口。

控制层:ESP32 解析状态为三种:空闲、占用、即将开始。状态优先级为“占用 > 即将开始 > 空闲”。按键短按循环切换,长按 3 秒可恢复自动模式。控制层还负责心跳、离线重连和状态去重。

执行层:WS2812 灯环或灯板安装在门口,绿色呼吸表示空闲,红色常亮表示占用,黄色慢闪表示 10 分钟内即将开始。亮度自动限制在 80,避免夜间刺眼。

日志与联网层:ESP32 通过 Wi-Fi 连接 MQTT,状态发布到 meeting/room1/state,订阅 meeting/room1/cmd。MQTT 使用保留消息,新订阅者立即拿到当前状态。串口输出连接日志、按键日志和错误码,便于现场排查。

物料清单

物料 数量 用途 估算单价 合计
ESP32 DevKit 开发板 1 主控与联网 18 元 18 元
WS2812B 8 位灯板 1 三色状态显示 9 元 9 元
6×6 轻触按键 1 本地切换状态 1 元 1 元
Type-C 数据线 1 供电与烧录 5 元 5 元
5V 1A 电源适配器 1 长期供电 10 元 10 元
亚克力或 3D 打印外壳 1 门口固定与遮光 12 元 12 元
杜邦线、端子、电阻、电容 1 套 接线与滤波 5 元 5 元
合计 60 元

飞书开放平台接口和 MQTT 服务器可复用现有资源,不计入硬件成本;若用公共测试 Broker,成本为 0 元。

接线与结构

WS2812 的 DIN 接 GPIO13,5V 接 5V,GND 接 GND。数据线串联 330Ω 电阻,灯板电源两端并联 1000μF 电容,降低上电冲击。ESP32 的 3.3V 数据电平驱动 WS2812 可能偏低,若出现首灯闪烁,可加 74HCT245 电平转换,或缩短数据线到 10cm 以内。

按键一端接 GPIO0,另一端接 GND,使用内部上拉,按下为低电平。GPIO0 是 ESP32 的启动模式引脚,上电瞬间不能按住,否则会进入下载模式。结构上建议按键放在外壳侧面,并加 100nF 电容消抖。外壳背面留 Type-C 口和散热孔,灯板朝外,半透明亚克力做柔光。

核心代码

以下为 Arduino 骨架,使用 Wi-Fi、PubSubClient 和 Adafruit_NeoPixel。


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

#define LED_PIN 13
#define KEY_PIN 0
#define LED_COUNT 8

const char* WIFI_SSID = "你的WiFi";
const char* WIFI_PASS = "你的密码";
const char* MQTT_HOST = "broker.example.com";
const int MQTT_PORT = 1883;
const char* TOPIC_STATE = "meeting/room1/state";
const char* TOPIC_CMD = "meeting/room1/cmd";

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

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

void showColor(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) showColor(0, 180, 0);
  else if (state == BUSY) showColor(180, 0, 0);
  else showColor(180, 120, 0);
}

const char* stateText() {
  if (state == FREE) return "free";
  if (state == BUSY) return "busy";
  return "soon";
}

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 reconnect() {
  while (!mqtt.connected()) {
    String cid = "esp32-room-" + String((uint32_t)ESP.getEfuseMac(), HEX);
    if (mqtt.connect(cid.c_str())) {
      mqtt.subscribe(TOPIC_CMD);
    } else {
      delay(2000);
    }
  }
}

void setup() {
  strip.begin();
  strip.setBrightness(80);
  applyState();
  while (WiFi.status() != WL_CONNECTED) delay(500);
  mqtt.setCallback(callback);
}

void loop() {
  if (!mqtt.connected()) reconnect();
  mqtt.loop();

  if (digitalRead(KEY_PIN) == LOW && millis() - lastKey > 300) {
    lastKey = millis();
    state = (RoomState)((state + 1) % 3);
    applyState();
  }

  if (millis() - lastReport > 30000) {
    lastReport = millis();
  }
  delay(20);
}

飞书日历同步在服务端完成:定时读取日历事件,判断当前是否有会、是否 10 分钟内开始,再向 MQTT 发布 free、busy 或 soon。设备端只负责显示和本地纠偏,逻辑更稳定。

调试步骤

  1. 不接灯板,先上电测 ESP32 串口和 Wi-Fi 连接。
  2. 接 WS2812,确认 GPIO13 输出,灯板依次显示红、绿、黄。
  3. 接按键,测试短按切换和消抖,观察串口状态变化。
  4. 用 MQTT 客户端手动发布 free、busy、soon,确认设备订阅和显示同步。
  5. 启动飞书日历同步脚本,模拟会议开始、结束、即将开始三种场景。
  6. 装入外壳,固定在会议室门口,连续运行 24 小时观察重连和发热。

设计图

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