三色灯智能会议室状态
划重点
- 主控芯片:ESP32,支持 Wi-Fi 与蓝牙,适合快速接入 MQTT 物联网协议。
- 核心功能:通过飞书日历实时获取会议室预定状态,使用 WS2812 全彩灯条在门口展示当前状态(红/绿/黄/蓝)。
- 交互方式:门口按键可手动切换状态(空闲、使用中、即将开始),并同步回传日历与 MQTT 服务器。
- 目标成本:控制在 65 元以内,且所有物料均可从常见电子商城购得。
- 使用场景:办公室会议室门口,替代纸质门牌与人工确认流程。
需求洞察
目标用户:行政后勤人员、频繁开会的团队负责人、以及所有需要快速确认会议室可用性的普通员工。
真实痛点:在多数办公环境中,会议预定系统虽然存在,但“预定”与“实际使用”严重脱节。员工到达会议室门口才发现房间被超时占用、临时取消后系统未更新,或空闲状态却无人进入。凑近屏幕看日历既不直观,也浪费了等待时间。更加常见的是,会议提前结束时,后续员工不知道房间已被释放。
为什么值得做:本项目用三色灯(红=占用,绿=空闲,蓝=即将开始)以低成本、高一致性、零学习成本的方式解决高频低效问题。同时通过飞书日历的同步,让“线上预定-线下物理状态”形成闭环,避免了人工敲门或提前抢占的尴尬。是一个实用、可落地、能展示的硬件编程项目。
方案设计
感知层:使用 1 个 DHT22 温湿度传感器(用于会议室环境监测,辅助判断室内是否有人),1 个机械自复位按键(用于手动切换状态)。感知层还包含 ESP32 内置的日历定时器(通过飞书 API 拉取未来 1 小时的预定事件)。
控制层:ESP32 通过 MQTT 协议连接公共 Broker(如 test.mosquitto.org 或自建 EMQX),订阅“会议室/状态”主题。控制逻辑包含状态机——空闲、占用、即将开始、维护中。
执行层:WS2812 灯条(1 米,30 灯/米),采用 GPIO 13 控制。根据状态机显示不同颜色,占用为红色常亮、空闲为绿色、即将开始为蓝色呼吸效果。
日志/联网层:ESP32 定时向 MQTT 发布设备心跳与当前状态。运行日志通过串口输出,并可选推送 Webhook 到飞书机器人,用于排障与历史统计。
物料清单
| 物料名称 | 数量 | 用途 | 估算单价(元) | 合计(元) |
|---|---|---|---|---|
| ESP32 DevKit V1 开发板 | 1 | 主控 | 22 | 22 |
| WS2812 灯条(1米/30灯) | 1 | 状态显示 | 15 | 15 |
| DHT22 温湿度传感器 | 1 | 环境感知 | 8 | 8 |
| 自复位轻触按键 | 2 | 状态切换/复位 | 1 | 2 |
| 5V 2A 电源适配器 | 1 | 供电 | 9 | 9 |
| 杜邦线与面包板 | 1套 | 接线 | 5 | 5 |
| 透明亚克力灯罩(A4尺寸) | 1 | 外壳遮挡,使光线均匀 | 4 | 4 |
| 合计 | 65 |
接线与结构
接线方案:
- WS2812 灯条:
DATA→GPIO 13,VCC→ 5V,GND→ GND。不使用板载 3.3V 供电,因为灯条满亮时电流较大。 - 按键(切换):一端接
GPIO 0,另一端接 GND,启用内部上拉(INPUT_PULLUP)。 - 按键(模式复位):一端接
GPIO 14,另一端接 GND,用于强制将状态设为空闲并同步至日历。 - DHT22:
DATA→GPIO 17,VCC → 3.3V,GND → GND。
结构说明:将 WS2812 灯条贴在亚克力罩内侧一周,利用亚克力导光使整个门牌均匀发亮。亚克力罩以 3D 打印或双面胶固定于门框上。按键安装在门框侧面约 1.2m 高度。ESP32 用塑料盒保护,放置于灯条上方隐藏空间内。供电线从门框顶部走线,不遮挡视线。
核心代码
#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_NeoPixel.h>
#include <DHT.h>
#define LED_PIN 13
#define BUTTON_PIN 0
#define DHT_PIN 17
#define NUM_LEDS 30
const char* ssid = "OFFICE_WIFI";
const char* password = "PASSWORD";
const char* mqtt_server = "test.mosquitto.org";
WiFiClient espClient;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
DHT dht(DHT_PIN, DHT22);
int state = 0; // 0=空闲, 1=占用, 2=即将开始
unsigned long lastPress = 0;
void setColor(uint8_t r, uint8_t g, uint8_t b) {
for (int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, strip.Color(r, g, b));
strip.show();
}
void updateDisplay() {
switch (state) {
case 0: setColor(0, 255, 0); break; // 绿色
case 1: setColor(255, 0, 0); break; // 红色
case 2: setColor(0, 0, 255); break; // 蓝色
}
}
void publishState() {
String payload = "{\"device\":\"room_01\",\"state\":" + String(state) + ",\"temp\":" + String(dht.readTemperature()) + "}";
client.publish("meetingroom/status", payload.c_str());
}
void callback(char* topic, byte* message, unsigned int length) {
String msg;
for (int i = 0; i < length; i++) msg += (char)message[i];
if (String(topic) == "meetingroom/set") {
if (msg == "busy") state = 1;
else if (msg == "free") state = 0;
else if (msg == "soon") state = 2;
updateDisplay();
publishState();
}
}
void reconnect() {
while (!client.connected()) {
if (client.connect("ESP32_Room_01")) client.subscribe("meetingroom/set");
delay(2000);
}
}
void setup() {
Serial.begin(115200);
strip.begin();
strip.setBrightness(50);
dht.begin();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); }
client.setCallback(callback);
updateDisplay();
}
void loop() {
if (!client.connected()) reconnect();
client.loop();
if (digitalRead(BUTTON_PIN) == LOW && millis() - lastPress > 300) {
lastPress = millis();
state = (state + 1) % 3;
updateDisplay();
publishState();
}
static unsigned long lastPublish = 0;
if (millis() - lastPublish > 60000) { // 每分钟上报一次环境数据
lastPublish = millis();
publishState();
}
}
*说明:该代码为 PlatformIO + Arduino 框架骨架,实际部署时可加入飞书日历 API 拉取逻辑(需引入 HTTPClient 并以 JSON 解析)。*
调试步骤
- 硬件上电检查:连接 USB 供电,确认 ESP32 板载 LED 亮起,WS2812 灯条无异常发热。
- 烧录与串口监视:首次烧录后观察串口输出,确认 Wi-Fi 连接成功且 IP 已获取。
- MQTT 联调:使用 MQTTX 客户端连接同一 Broker,发送
busy,观察灯变红,串口返回上报日志。 - 按键功能:按压 GPIO 0 按键,状态依次切换,灯效同步变化。
- 状态异常模拟:断开 Wi-Fi,验证状态保持上电时的默认值,不出现闪烁或复位。重新联网后自动恢复订阅。
- 日历接口调试(可选):登录飞书开放平台,获取租户 Token,在代码中替换后测试实际事件拉取与状态机联动。