2. 农业自动化数据流建模基础
在 Node-RED 中做温室环境监测,最核心的不是“把节点拖到画布上”,而是理解数据在流程里以什么形态流动、节点之间靠什么连接、以及跨消息的状态保存在哪里。这三件事分别对应 msg 消息结构、节点连接(wires) 和 上下文(context)。把这三者理清,后面无论是接 DHT22 传感器、做阈值报警还是推送数据到仪表盘,都会变得有章可循。
一、msg 是流程中流动的唯一数据载体
Node-RED 中每个节点处理的对象都是一个 JavaScript 对象,习惯上命名为 msg。它有两个关键约定:msg.payload 是节点的“主数据”,大多数节点默认读写它;其余字段则是随消息一起传递的附加信息,例如 msg.topic 常用于标识数据来源,msg.timestamp 可记录采集时刻。节点之间传递的是同一个 msg 对象的引用,因此上游节点写入的字段,下游节点可以直接读取。
以温室采集为例,一个典型的温湿度消息可以长这样:
{
"topic": "greenhouse/zone1",
"payload": {
"temperature": 24.6,
"humidity": 63.2
},
"timestamp": 1718000000000
}
这里把温湿度放进 payload 的对象里,而不是拆成两个独立字段,是为了让一条消息同时携带两个物理量,便于后续一起显示或一起判断。如果传感器是分两次上报的,也可以让 payload 只放一个数值,用 topic 区分是温度还是湿度,例如 topic: "sensor/temperature"。两种组织方式没有绝对优劣,关键是整条流程内保持一致,避免下游节点一会儿读 payload.temperature、一会儿读 payload 本身。
二、节点连接决定数据流向与分支
节点之间的连线在流程 JSON 中表现为 wires 数组,它描述“这个节点的输出端口连到哪些下游节点”。一个节点可以有多个输出端口,每个端口对应一个 wires 数组,因此同一份数据可以同时送往显示节点和存储节点,形成并行分支。这正是温室监测流程的常见结构:采集节点输出后,一路进入调试/显示,一路进入阈值判断,一路写入上下文做历史记录。
需要特别注意的是,连线传递的是消息本身,而不是“调用关系”。上游节点只要调用 node.send(msg),消息就会沿所有连出的 wires 各发送一份(严格说是同一对象的引用被多次投递)。因此如果下游某个节点修改了 msg.payload,其他分支可能看到被改过的值。实践中,若不同分支需要不同的数据形态,应使用 change 节点在分支前复制或重组字段,而不是让多个分支共享并互相改写同一个对象。
三、上下文解决“消息之外的状态保存”
msg 是瞬时的:一条消息被处理完就结束了,下一个采集周期又是全新的 msg。但温室监测往往需要“记住上一次的值”——比如判断温度是否在 5 分钟内持续上升,或者计算最近 10 次采样的平均值。这类跨消息的状态就应放进 context。Node-RED 提供三种作用域:节点上下文(node.context())、流程上下文(flow.context())和全局上下文(global.context())。
在 function 节点中,读写方式非常直接:
// 读取上一次温度,若不存在则用当前值初始化
let last = flow.get("lastTemp");
if (last === undefined) {
last = msg.payload.temperature;
}
// 计算变化量并写回
msg.delta = msg.payload.temperature - last;
flow.set("lastTemp", msg.payload.temperature);
return msg;
这段代码体现了上下文的核心价值:lastTemp 不随消息消失,下一次采集时仍能取到。选择作用域的原则是“谁需要看见它”——只有当前节点用的计数器放节点上下文;同一流程内多个节点共享的最近值放流程上下文;多个温室流程都要汇总的全局指标才放全局上下文。作用域放得过大,容易造成命名冲突和意外覆盖;放得过小,又无法共享。
四、把三者组合成一个可运行的采集显示流程
下面给出一个可直接在 Node-RED 中搭建的最小流程设计(示例参数,非实测数据)。它由五个节点组成:inject 每 5 秒触发一次;function 模拟生成温湿度并写入 msg.payload;switch 判断温度是否超过 28℃;debug 输出完整消息;function 用流程上下文记录最近值。连线关系为:inject → 生成 function → 分支到 debug、switch 和记录 function。
搭建时的关键检查点有三个。第一,确认生成节点输出的 msg.payload 是对象且字段名与下游读取一致,可在 debug 节点选择“完整 msg”查看实际结构。第二,switch 节点的判断属性要写成 payload.temperature,运算符选 >,比较值填 28,否则会因读到 undefined 而永远不触发。第三,记录节点中 flow.get 与 flow.set 的键名必须完全相同,且首次运行时 get 返回 undefined,需要像上面的代码那样做初始化判断。
JSON.parse 或 change 节点转换为对象,否则 payload.temperature 取不到值。五、常见建模错误与判断依据
初学者最容易犯的错误是把“节点连接”当成“函数调用”,以为下游节点会等上游返回结果。实际上消息是异步投递的,多个分支之间没有先后保证。如果显示节点依赖记录节点先写入上下文,就可能读到旧值。解决办法是把有顺序依赖的逻辑合并进同一个 function 节点,或用 msg 字段显式传递,而不是依赖上下文的时间差。
另一个高频问题是上下文键名拼写不一致,导致每次读到的都是 undefined,表现为“平均值一直是 NaN”或“变化量异常”。判断方法是:在 function 节点里临时加一行 node.warn(flow.keys()),查看当前流程上下文里实际存在哪些键,再与代码中的键名逐一比对。只要养成“先看 msg 结构、再查连线、最后核对上下文键名”的排查顺序,绝大多数数据流问题都能在几分钟内定位。
掌握这三层概念后,温室监测流程就不再是一堆孤立的节点,而是一条有明确数据形态、明确流向和明确状态存储位置的完整链路。后续接入真实传感器、增加历史曲线或联动执行机构时,只需在这条链路上替换或追加节点,而不必推翻整体结构。