智能厨房并不是什么遥远的概念,把家里那台烤箱、冰箱、抽油烟机接上网,再配一套能够统一调度的软件系统,就成了一个最基础但足够实用的智能厨房。DeKitchen就是这样一个项目:以Node.js为核心,用MQTT协议连接各类厨房设备,用WebSocket把设备状态实时推送到浏览器面板,同时内置一套自动化规则引擎,处理类似“烤箱温度过高自动断电”这种安全场景。这篇文章会把整个系统的架构设计和关键代码逐一拆解,看完你完全可以照着搭一套自己的。

为什么选Node.js:事件驱动模型与物联网的天然契合
厨房设备有个共同特点:单个设备的数据量很小,但设备数量多、事件频率高。一个温度传感器可能每两秒上报一次数据,一次只有几十个字节,如果家里有十几个这样的传感器,用传统的一请求一线程模型,服务器很快就会不堪重负。
Node.js的单线程事件循环在这个场景下几乎是量身定做的。设备上报数据属于典型的I/O密集型操作,Node.js的事件驱动模型可以在一个线程内高效处理成千上万个并发连接,内存占用也远低于多线程方案。而且前后端可以共用JavaScript,设备通信协议的解析逻辑甚至可以直接复用到浏览器端。
DeKitchen的技术选型可以概括为三层:接入层用MQTT(基于mosca或EMQX作为Broker,Node.js端用mqtt客户端库),服务层用Express提供REST API,实时推送层用socket.io实现WebSocket通信。三层之间通过一个中央设备管理器(DeviceManager)衔接,所有设备的注册、状态、指令都经过它统一调度。
设备接入层:MQTT通信与设备模型设计
MQTT是物联网领域的事实标准协议,它的发布订阅模型非常适合厨房设备。每台设备上线后向特定主题发布心跳和状态数据,服务端订阅这些主题即可获取全部信息。我们先定义一个统一的设备模型,让不同类型的设备都能用同一套结构描述:
class KitchenDevice {
constructor(id, type, room) {
this.id = id; // 设备唯一标识,如 oven-01
this.type = type; // 设备类型:oven, fridge, light, sensor
this.room = room; // 所在区域
this.status = {}; // 设备当前状态,如 { power: true, temp: 180 }
this.lastSeen = Date.now();
}
updateStatus(payload) {
this.status = { ...this.status, ...payload };
this.lastSeen = Date.now();
}
}接下来是MQTT客户端的连接与订阅逻辑。DeKitchen约定所有设备向kitchen/device/{id}/state主题发布状态,向kitchen/device/{id}/command主题订阅指令:
const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://127.0.0.1:1883');
client.on('connect', () => {
console.log('DeKitchen接入层已连接MQTT Broker');
client.subscribe('kitchen/device/+/state', { qos: 1 });
});
client.on('message', (topic, message) => {
const parts = topic.split('/');
const deviceId = parts[2];
const payload = JSON.parse(message.toString());
deviceManager.handleReport(deviceId, payload);
});这里的+/code>是MQTT的单层通配符,一条订阅就能覆盖所有设备的状态上报。设备离线检测可以借助心跳时间戳:DeviceManager里跑一个定时器,每30秒扫描一遍lastSeen,超过60秒没有心跳就标记设备离线并推送告警。这个设计简单,但在实际家庭网络环境下非常可靠。
控制指令下发与实时状态推送
设备接入只是第一步,智能厨房的核心价值在于“控制”。用户在Web面板上点击“关闭烤箱”,这条指令要经过API校验、指令下发、设备确认三个环节才算完成。先看指令下发的实现:
// Express路由:下发控制指令
app.post('/api/device/:id/command', async (req, res) => {
const { id } = req.params;
const { action, params } = req.body;
const device = deviceManager.getDevice(id);
if (!device) return res.status(404).json({ error: '设备不存在' });
if (!deviceManager.isOnline(device)) {
return res.status(503).json({ error: '设备当前离线' });
}
// 发布指令到设备专属主题
client.publish(`kitchen/device/${id}/command`,
JSON.stringify({ action, params, ts: Date.now() }), { qos: 1 });
res.json({ ok: true, message: '指令已下发' });
});注意这里用的是QoS 1等级,保证指令至少送达一次。指令下发后设备会执行并回报新状态,服务端收到状态更新后再通过socket.io广播给所有已连接的浏览器面板,形成完整的闭环。WebSocket推送部分的代码如下:
const io = require('socket.io')(httpServer);
io.on('connection', (socket) => {
// 新面板连接时,先推送一次全量设备快照
socket.emit('snapshot', deviceManager.getAllDevices());
});
// DeviceManager状态变化时广播
deviceManager.on('stateChanged', (device) => {
io.emit('deviceUpdate', {
id: device.id,
status: device.status,
online: deviceManager.isOnline(device)
});
});这种“状态变化才推送”的模式比轮询高效得多,面板端只需监听deviceUpdate事件就能实时刷新界面。建议给每条状态消息加上时间戳和版本号,前端收到乱序消息时可以丢弃旧版本,避免界面回跳。
自动化规则引擎:让厨房学会自我保护
智能和遥控的区别就在于自动化。DeKitchen内置了一个简单的规则引擎,用规则表达式描述“当某条件满足时执行某动作”,最典型的应用是厨房安全防护。比如烟雾传感器数值超标时,自动关闭烤箱电源并打开排风扇:
const rules = [
{
name: '烟雾报警联动',
when: (dm) => dm.getDevice('sensor-smoke').status.ppm > 150,
actions: [
{ device: 'oven-01', action: 'power', params: { on: false } },
{ device: 'fan-01', action: 'speed', params: { level: 3 } }
],
cooldown: 60000 // 冷却时间,防止重复触发
},
{
name: '烤箱过热保护',
when: (dm) => dm.getDevice('oven-01').status.temp > 250,
actions: [
{ device: 'oven-01', action: 'power', params: { on: false } }
],
cooldown: 120000
}
];
// 每次设备状态更新后执行规则检查
deviceManager.on('stateChanged', () => {
const now = Date.now();
rules.forEach(rule => {
if (rule._lastFired && now - rule._lastFired < rule.cooldown) return;
if (rule.when(deviceManager)) {
rule._lastFired = now;
rule.actions.forEach(cmd => {
client.publish(`kitchen/device/${cmd.device}/command`,
JSON.stringify({ action: cmd.action, params: cmd.params }));
});
notifier.push(`规则触发:${rule.name}`);
}
});
});规则里的冷却时间很重要,烟雾传感器数值通常会在阈值附近抖动,没有冷却机制会导致指令被连续下发几十次,设备继电器频繁开关反而带来安全隐患。除了安全规则,还可以扩展烹饪场景类规则,比如“晚上十点后自动关闭厨房所有灯光”这类联动,实现思路完全一致。
数据存储与运行稳定性
设备的历史数据值得留存,一是可以回看温度曲线排查问题,二是为后续的能耗分析打基础。考虑到家用场景数据量不大,SQLite是最务实的选择,配合better-sqlite3这个同步API的库,代码写起来非常直观:
const Database = require('better-sqlite3');
const db = new Database('dekitchen.db');
db.exec(`CREATE TABLE IF NOT EXISTS device_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT NOT NULL,
status TEXT NOT NULL,
created_at INTEGER NOT NULL
)`);
// 状态变化时写入日志,注意对高频设备做采样
function logDevice(device) {
const stmt = db.prepare(
'INSERT INTO device_log (device_id, status, created_at) VALUES (?, ?, ?)'
);
stmt.run(device.id, JSON.stringify(device.status), Date.now());
}对于高频上报的温度类传感器,建议在写入前做采样过滤,比如数值变化超过一定幅度才落库,否则数据库会很快膨胀。最后提两个运行层面的经验:MQTT Broker建议直接用EMQX而非在Node.js进程内嵌mosca,前者的稳定性高出不少;Node.js主进程务必加上进程守护,控制硬件的系统最怕的就是半夜静默退出。用pm2做进程管理,再配一套简单的日志轮转,整套DeKitchen就可以长期稳定跑下去了。