DeMars是一个面向火星种植场景的农业控制原型,它把密闭舱内的温度、湿度、光照、CO2浓度、土壤水分等环境变量统一接入Node.js服务,再根据作物的生长阶段输出灌溉、补光、通风等控制指令。Node.js的事件驱动模型很适合处理这种多源高频数据,既可以用EventEmitter做内部调度,也能通过MQTT、WebSocket等协议与外部设备保持长连接。本文会按照可运行原型的方式展开,先定义系统模块,再逐个实现传感器模拟、生长模型和接口推送。

一、DeMars系统的整体架构与模块划分
DeMars的核心可以拆成四个模块:环境传感器模拟器、数据接入层、生长模型决策器、API与实时推送层。传感器模拟器负责生成接近火星温室特征的虚拟数据,因为真实火星表面气压低、昼夜温差大,植物必须种在带有环境控制的密闭舱内。模拟器需要模拟昼夜周期和偶发的沙尘天气,让数据不是一条直线,而更像真实传感器输出。数据接入层通过MQTT接收这些数据,并写入内存队列,方便后续处理。
生长模型决策器是整个系统的大脑。它维护一个作物阶段状态机,比如发芽期、营养生长期、开花期和成熟期,每个阶段对环境参数的期望区间不同。例如发芽期土壤水分要稳定在较高水平,营养生长期对光照和CO2需求上升,开花期则对温度更敏感。决策器每隔一段时间读取最近的环境数据,与当前阶段的目标区间比对,生成是否需要开启水泵、补光灯或CO2发生器的指令。这些指令会通过WebSocket推送给前端控制台,同时也可以通过MQTT下发到执行器。
Node.js在处理这些逻辑时有一个明显优势:所有模块可以共用同一种语言和数据结构,不需要在多个服务之间转换格式。下面是一个典型的项目目录,用来组织这些模块。
demars/ ├── src/ │ ├── simulator.js │ ├── mqtt-client.js │ ├── growth-model.js │ └── server.js ├── package.json └── README.md
这个结构很轻量,核心服务只依赖express、mqtt和ws三个包。传感器模拟器可以直接用Node.js内置的events模块,不需要额外引入定时任务库。对于想要快速验证想法的开发者来说,先用这种单进程架构跑通数据流,再考虑拆分成微服务会更实际。
二、用Node.js实现火星环境传感器模拟与数据采集
传感器模拟器的目标不是产生随机噪声,而是带有可解释规律的实时数据。火星温室通常保持接近地球海平面的气压,但外部辐射和昼夜节律仍然会影响内部环境。我们可以让温度在白天缓慢上升,夜间下降,并且在沙尘天气时降低光照并升高湿度。下面这段代码封装了一个环境模拟器,每两秒触发一次数据生成事件。
const { EventEmitter } = require('events');
class MarsGreenhouseSimulator extends EventEmitter {
constructor() {
super();
this.hour = 0;
this.soilMoisture = 62;
this.timer = null;
}
start() {
this.timer = setInterval(() => {
this.hour = (this.hour + 0.5) % 24;
const isDay = this.hour >= 6 && this.hour < 20;
const sandstorm = Math.random() < 0.04;
const temperature = isDay
? 22 + Math.sin((this.hour - 13) / 7 * Math.PI) * 5 + (Math.random() - 0.5)
: 12 + Math.sin((this.hour - 1) / 7 * Math.PI) * 3 + (Math.random() - 0.5);
const humidity = isDay ? 45 + Math.random() * 12 : 55 + Math.random() * 12;
const light = isDay
? Math.max(80, 700 * Math.sin((this.hour - 6) / 14 * Math.PI))
: 0;
const co2 = sandstorm ? 1500 + Math.random() * 200 : 800 + Math.random() * 160;
this.soilMoisture -= 0.6 + Math.random() * 0.9;
if (this.soilMoisture < 30) this.soilMoisture = 30;
this.emit('sample', {
timestamp: Date.now(),
temperature: Number(temperature.toFixed(2)),
humidity: Number(humidity.toFixed(2)),
light: Number(light.toFixed(2)),
co2: Number(co2.toFixed(2)),
soilMoisture: Number(this.soilMoisture.toFixed(2)),
sandstorm
});
}, 2000);
}
stop() {
clearInterval(this.timer);
}
}
module.exports = MarsGreenhouseSimulator;
模拟器通过EventEmitter的sample事件把数据交给下游。这样做的好处是数据采集和业务逻辑解耦,后续如果接入真实传感器,只需要让真实硬件也发出sample事件即可。土壤水分的持续下降模拟了植物蒸腾和基质蒸发,当水分低于30时截断,代表需要立即补水。沙尘暴事件用4%的随机概率触发,会拉高CO2并降低光照,让决策器有机会判断异常环境。
数据接入层使用MQTT协议把样本发布到本地代理。MQTT在物联网场景中非常常见,因为它开销低,适合带宽受限的设备。下面这段代码展示了模拟器和MQTT发布端的连接方式,使用mqtt包连接到本地broker,并订阅控制指令。
const mqtt = require('mqtt');
const MarsGreenhouseSimulator = require('./simulator');
const client = mqtt.connect('mqtt://127.0.0.1:1883');
const simulator = new MarsGreenhouseSimulator();
client.on('connect', () => {
console.log('DeMars MQTT client connected');
simulator.start();
simulator.on('sample', (data) => {
client.publish('demars/environment', JSON.stringify(data), { qos: 1 });
});
client.subscribe('demars/commands');
});
client.on('message', (topic, payload) => {
if (topic === 'demars/commands') {
const command = JSON.parse(payload.toString());
console.log('Command received:', command);
}
});
这样一条完整的数据链路就建立起来了:模拟器产生sample事件,MQTT客户端把环境数据发布到demars/environment主题,同时监听demars/commands主题获取执行器指令。使用JSON作为消息格式虽然比二进制大,但可读性好,适合原型阶段调试。生产环境可以考虑使用更紧凑的编码,例如MessagePack。
三、DeMars生长模型与灌溉决策引擎
生长模型需要解决两个问题:判断当前作物处于哪个阶段,以及该阶段需要什么样的环境目标。可以设计一个GrowthModel类,内部保存生长累积天数,根据天数自动切换阶段。火星农业中常用的候选作物是土豆和拟南芥,这里以土豆为例,因为它的生长阶段划分清晰,块茎形成期对水分和温度要求较高。
决策器不会每收到一条传感器数据就立刻改变控制状态,而是设置一个决策周期,比如每五秒评估一次。这样既能避免执行器频繁开关,又能减少系统抖动。评估逻辑会读取最新环境数据,和当前阶段的目标区间比较。如果土壤水分低于目标下限,就生成灌溉指令;如果光照低于阈值且处于白天,就开启补光;如果CO2低于下限,就启动CO2发生器。下面这段代码展示了核心的生长模型和决策逻辑。
class GrowthModel {
constructor(startDate = Date.now()) {
this.startDate = startDate;
this.stageThresholds = [
{ days: 0, name: 'germination', soilMin: 55, soilMax: 75, lightMin: 300, co2Min: 600 },
{ days: 10, name: 'vegetative', soilMin: 45, soilMax: 70, lightMin: 500, co2Min: 800 },
{ days: 30, name: 'flowering', soilMin: 40, soilMax: 65, lightMin: 600, co2Min: 900 },
{ days: 55, name: 'maturation', soilMin: 35, soilMax: 55, lightMin: 500, co2Min: 700 }
];
}
getStage(daysSinceStart) {
let current = this.stageThresholds[0];
for (const stage of this.stageThresholds) {
if (daysSinceStart >= stage.days) {
current = stage;
}
}
return current;
}
evaluate(environment) {
const days = (Date.now() - this.startDate) / (24 * 60 * 60 * 1000);
const stage = this.getStage(days);
const commands = [];
if (environment.soilMoisture < stage.soilMin) {
commands.push({ actuator: 'water_pump', action: 'on', duration: 4000 });
}
if (environment.light < stage.lightMin && environment.light >= 0) {
commands.push({ actuator: 'led_panel', action: 'on', intensity: 70 });
}
if (environment.co2 < stage.co2Min) {
commands.push({ actuator: 'co2_generator', action: 'on', rate: 2 });
}
return { stage: stage.name, days: Number(days.toFixed(2)), commands };
}
}
module.exports = GrowthModel;
这里的getStage方法根据从播种到现在的天数返回当前阶段,evaluate方法对比环境数据与阶段阈值,生成需要执行的动作。灌溉指令包含执行时长,补光灯指令包含光照强度,CO2发生器指令包含释放速率。这些参数可以根据实际传感器反馈进一步调整。阈值设计参考了温室栽培中的常见范围,但在真实火星环境中还需要考虑低气压对水分蒸发和植物蒸腾的影响,这部分可以作为后续校正项。
执行器指令不会直接写入传感器模拟器,而是通过MQTT发布到demars/commands主题。执行器端收到指令后可以控制继电器、水泵或LED驱动板。在原型阶段,我们可以在MQTT客户端里打印收到的指令,验证决策是否合理。如果出现振荡,可以考虑给控制动作增加冷却时间,比如水泵开启后至少十分钟内不再触发同一动作,这正是真实农业控制中常见的防抖策略。
四、构建REST API与WebSocket实时推送
控制中心还需要对外提供查询和展示能力。REST API适合让前端定时拉取历史数据和设备状态,WebSocket适合把实时传感器数据和决策结果推送到浏览器。Node.js的Express框架可以快速搭建HTTP服务,ws库则提供轻量级WebSocket支持。我们可以在同一个HTTP端口上同时提供REST和WebSocket能力,减少部署复杂度。
服务端会维护一个内存数组,保存最近的环境样本,例如最多保留最新500条。MQTT客户端收到环境数据后,除了交给GrowthModel评估,也会把数据存入数组,并通过WebSocket广播给所有连接的客户端。下面这段代码把前面几个模块串起来,形成一个完整的DeMars控制服务。
const express = require('express');
const http = require('http');
const { WebSocketServer } = require('ws');
const mqtt = require('mqtt');
const MarsGreenhouseSimulator = require('./simulator');
const GrowthModel = require('./growth-model');
const app = express();
const server = http.createServer(app);
const wss = new WebSocketServer({ server });
const simulator = new MarsGreenhouseSimulator();
const growthModel = new GrowthModel();
const samples = [];
const MAX_SAMPLES = 500;
const mqttClient = mqtt.connect('mqtt://127.0.0.1:1883');
mqttClient.on('connect', () => {
simulator.start();
simulator.on('sample', (data) => {
samples.push(data);
if (samples.length > MAX_SAMPLES) samples.shift();
const decision = growthModel.evaluate(data);
if (decision.commands.length > 0) {
mqttClient.publish('demars/commands', JSON.stringify(decision.commands));
}
const payload = JSON.stringify({ environment: data, decision });
wss.clients.forEach((client) => {
if (client.readyState === 1) client.send(payload);
});
});
});
app.get('/api/environment/latest', (req, res) => {
res.json(samples.length ? samples[samples.length - 1] : null);
});
app.get('/api/environment/history', (req, res) => {
const limit = Math.min(Number(req.query.limit) || 100, samples.length);
res.json(samples.slice(-limit));
});
app.get('/api/stage', (req, res) => {
const days = (Date.now() - growthModel.startDate) / (24 * 60 * 60 * 1000);
res.json({ stage: growthModel.getStage(days).name, days: Number(days.toFixed(2)) });
});
wss.on('connection', (ws) => {
ws.send(JSON.stringify({ message: 'connected to DeMars control center' }));
});
server.listen(3000, () => {
console.log('DeMars API and WebSocket server running on port 3000');
});
这个服务启动后会同时监听3000端口的HTTP请求和WebSocket连接。前端通过/api/environment/latest获取最新一条数据,通过/api/environment/history?limit=200获取历史曲线,通过/api/stage查询当前生长阶段。WebSocket客户端一旦连上,就会持续收到环境数据和决策结果。内存数组只保留最近500条,避免长时间运行导致内存无限增长。如果希望持久化历史数据,可以定时把样本写入SQLite或InfluxDB,这部分逻辑可以加在simulator的sample事件处理函数中。
部署时可以使用PM2管理Node.js进程,并配置自动重启。MQTT broker可以选择Mosquitto或EMQX,在本地开发时用Docker启动一个Mosquitto容器即可满足需求。测试阶段可以先用Node.js内置的assert模块验证GrowthModel的阶段切换是否正确,再通过手动发布MQTT消息模拟传感器数据,检查API返回是否符合预期。DeMars原型跑通后,传感器模拟器可以逐步替换为真实硬件驱动,执行器指令也可以接入真实的继电器模块。整套架构保持轻量,后续扩展成边缘计算节点或微服务也不会太困难。