垂直农场作为一种新型的城市农业形态,依靠多层立体种植架和人工光源在有限空间内实现高密度生产。要让它真正稳定运转,离不开一套可靠的监控系统:什么时候浇水、什么时候补光、温度超了怎么处理,这些都不能靠人工盯守。Node.js凭借其异步IO和事件驱动的特性,非常适合处理大量传感器数据的并发采集与实时响应,本文就带大家从零搭建一套基于Node.js的垂直农场监控系统。

系统架构设计与硬件选型
在动手写代码之前,先明确整体架构。一套典型的垂直农场监控系统分为三层:感知层负责采集温度、湿度、光照强度、二氧化碳浓度和营养液pH值等数据;网络层负责把数据传输到服务端;应用层负责数据存储、可视化展示和自动化控制指令的下发。
硬件方面,推荐使用树莓派作为边缘网关节点,搭配DHT22温湿度传感器、BH1750光照传感器、MH-Z19二氧化碳传感器以及土壤湿度探头。每个种植层部署一组传感器,通过I2C或GPIO接口连接到树莓派。通信协议选择MQTT,它轻量、低功耗,而且是物联网领域的事实标准,配合Mosquitto作为消息中间件即可满足绝大多数场景。
软件侧的技术栈如下:Node.js 18以上版本作为运行时,Express提供Web服务,MQTT.js处理消息收发,Johnny-Five操作底层硬件,Socket.IO实现浏览器端的数据实时推送,时序数据则交给InfluxDB存储。这套组合的优势在于全链路JavaScript,前后端可以共享数据模型,开发维护成本都比较低。
传感器数据采集与MQTT接入
数据采集是整个系统的基石。Johnny-Five提供了对常见传感器的封装,不过在实际项目中,直接使用一些专用库调用起来更简洁。下面的示例演示了如何在树莓派上周期性读取DHT22的数据,并通过MQTT发布到指定主题。
const mqtt = require('mqtt');
const sensorLib = require('node-dht-sensor');
// 初始化MQTT客户端,连接本机的Mosquitto服务
const client = mqtt.connect('mqtt://127.0.0.1:1883', {
clientId: 'farm-gateway-01',
clean: true
});
// 读取DHT22温湿度数据
function readDht() {
const result = sensorLib.read(22, 4); // 传感器类型22,GPIO引脚4
return {
temperature: parseFloat(result.temperature.toFixed(2)),
humidity: parseFloat(result.humidity.toFixed(2))
};
}
// 每10秒采集一次并发布
setInterval(() => {
const data = readDht();
const payload = {
layerId: 'layer-1',
...data,
timestamp: Date.now()
};
client.publish('farm/sensor/layer-1', JSON.stringify(payload));
console.log('已发布:', JSON.stringify(payload));
}, 10000);
client.on('connect', () => {
console.log('MQTT网关已连接');
});
这段代码中有几个细节值得注意。首先,传感器读取频率不宜过高,DHT22的最小采样间隔约为2秒,频繁读取会返回脏数据。其次,MQTT主题的设计要有层次感,比如farm/sensor/layer-1表示第一层的传感器数据,后续可以很方便地用通配符farm/sensor/+订阅所有层的数据。最后,每条消息都带上时间戳和层编号,为后续的数据分析打下基础。
光照和二氧化碳传感器的接入方式类似。BH1750走I2C总线,可以使用node-bh1750库;MH-Z19则通过串口通信,用serialport库按协议帧解析即可。如果某个传感器没有现成的Node.js库,也可以考虑通过Python脚本采集后写入MQTT,Node.js端只负责订阅,这样能让各模块各司其职。
自动化控制逻辑与实时监控界面
采集数据只是手段,真正的价值在于根据数据自动执行控制动作。Node.js的事件驱动模型在这里体现得淋漓尽致:订阅传感器主题,根据阈值判断是否触发继电器,从而控制水泵、补光灯和排风扇。下面是一段典型的控制逻辑实现。
const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://127.0.0.1:1883');
// 各层的阈值配置,可从数据库或配置文件加载
const thresholds = {
'layer-1': { tempMax: 28, humidityMin: 60, lightMinHours: 14 }
};
// 执行器状态管理,避免重复开关
const actuatorState = {};
client.on('connect', () => {
client.subscribe('farm/sensor/+');
});
client.on('message', (topic, message) => {
const layerId = topic.split('/').pop();
const data = JSON.parse(message.toString());
const rule = thresholds[layerId];
if (!rule) return;
// 温度超标,启动排风扇
if (data.temperature > rule.tempMax) {
toggleActuator(layerId, 'fan', true);
} else if (data.temperature < rule.tempMax - 2) {
toggleActuator(layerId, 'fan', false); // 回差控制,防止频繁启停
}
// 湿度不足,启动灌溉水泵,灌溉10秒后自动关闭
if (data.humidity < rule.humidityMin) {
toggleActuator(layerId, 'pump', true);
setTimeout(() => toggleActuator(layerId, 'pump', false), 10000);
}
});
function toggleActuator(layerId, device, on) {
const key = `${layerId}:${device}`;
if (actuatorState[key] === on) return;
actuatorState[key] = on;
client.publish(`farm/control/${layerId}/${device}`, on ? 'ON' : 'ON'.toLowerCase());
console.log(`${key} 状态变更为 ${on ? '开启' : '关闭'}`);
}
这段代码里有一个重要的工程实践:回差控制。如果只设置单一阈值,当温度在临界点附近波动时,风扇会反复启停,既损伤硬件又浪费能源。设置2摄氏度的回差区间,让设备只在温度明显回落后再关闭,可以显著延长继电器和电机寿命。同样,灌溉采用定时关闭策略而非持续灌溉,避免土壤过涝。
监控界面的搭建推荐Express加Socket.IO的组合。服务端订阅所有传感器主题,将最新数据通过WebSocket推送到浏览器,前端用ECharts绘制实时曲线图。相比轮询方式,WebSocket的延迟只有毫秒级,运维人员盯着页面就能掌握整个农场的状态。历史数据查询则通过InfluxDB完成,它的时序存储引擎对按时间范围的聚合查询做了深度优化,查一周的温度趋势只需几十毫秒。
最后别忘了异常告警。传感器离线、数据长时间无更新、数值超出物理合理范围,这些都应该触发通知。简单场景下可以用钉钉或企业微信的Webhook推送消息,复杂场景则建议接入消息队列做告警分级。整体来看,这套系统的每个模块都是解耦的,后续要扩展新的种植层、接入摄像头做图像识别,只需要增加订阅主题和新增页面组件,核心架构完全不用改动,这正是Node.js在物联网项目中最大的价值所在。